<![CDATA[Блог Загирова Рустама]]> 2013-10-28T18:47:23+04:00 http://www.zagirov.name/ Octopress <![CDATA[Unicorn в capistrano 3]]> 2013-10-28T17:54:00+04:00 http://www.zagirov.name/capistrano3-unicorn Вышла новая версия capistrano под номером 3. Можете прочитать полный анонс от комманды.

Главные изменения:

  • Под капотом теперь SSHKit, и можно использовать разные фишки dsl. В частности появился метод test, которым можно проверить возврат и выполнить в зависимости от этого разные комманды. Что позволило избавится от выполнения такого: “[ -f ] && unicorn; true”
  • Модульность: bundler, rbenv, rvm, maintenance. Даже рельсовые assets и migration развели, можно подключать по отдельности. Идут по пути рельс: подключаешь только то, что тебе нужно.
  • Теперь поддержка multistage из коробки
  • Новые опции linked_files и linked_dirs
  • Сломали —dry-run. К справедливости, это баг SSHKit, но неприятненько

Собственно переписанные правила для unicorn’а:

Capfile:

1
2
3
4
require 'capistrano/setup'
require 'capistrano/deploy'
require 'capistrano/bundler'
Dir.glob('lib/capistrano/tasks/*.cap').each { |r| import r }

deploy.rb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
namespace :deploy do

  desc 'Restart application'
  task :restart do
    invoke 'deploy:unicorn:restart'
  end
end

namespace :unicorn do
  pid_path = "#{release_path}/tmp/pids"
  unicorn_pid = "#{pid_path}/unicorn.pid"

  def run_unicorn
    execute "#{fetch(:bundle_binstubs)}/unicorn", "-c #{release_path}/config/unicorn.rb -D -E #{fetch(:stage)}"
  end

  desc 'Start unicorn'
  task :start do
    on roles(:app) do
      run_unicorn
    end
  end

  desc 'Stop unicorn'
  task :stop do
    on roles(:app) do
      if test "[ -f #{unicorn_pid} ]"
        execute :kill, "-QUIT `cat #{unicorn_pid}`"
        #execute :rm, unicorn_pid
      end
    end
  end

  task :force_stop do


  desc 'Restart unicorn'
  task :restart do
    on roles(:app) do
      if test "[ -f #{unicorn_pid} ]"
        execute :kill, "-USR2 `cat #{unicorn_pid}`"
      else
        run_unicorn
      end
    end
  end
end
]]>
<![CDATA[Софт на Mac OS X]]> 2013-10-21T17:53:00+04:00 http://www.zagirov.name/mac-environments В информации о блоге есть описание моего окружения 2 годичной давности. Сейчас я уже использую Mac OS X, а не Ubuntu Linux.

Не буду утруждать тут дифирамбами о необходимости прямо сейчас бежать за макбуком в магазин, но поверьте система отлично подходит как для php-разработчика, как и для ruby-разработчика (коим я сейчас и являюсь). Это полноценная мощь юникс-консоли вкупе с красивым интерфейсом.

Итак, поехали.

Alfred (~880 рублей) — это мегамощная штука для продуктивной работы, у которой есть одна точка входа в виде строки ввода, из которой можно выполнять разнообразные действия или перенаправлять ввод в другие программы. Это своего рода spotlight на стероидах или консоль 2.0 =) Тут можно запустить программу по названию, быстро посчитать, погуглить, задать свои поисковые системы. Я прикрутил перенаправление поискового запроса в dash (это централизованная документация по различным технологиям, об этой программе будет ниже). alfred

Очень удобно то, что можно назначить свои скрипты по опредённому слову. Например, проиграть http://nooooooooooooooo.com/ по приходу менеджеров в комнату

Настройка

Использование

Тем, кто ещё не использовал эту программу, настоятельно рекомендую это сделать. Уже вышла вторая версия, но я пока использую первую.

Spectacle (бесплатная) — быстрые хоткеи для изменения размера и расположения окон. Очень не хватало со времён убунты.

Bartender (~500 рублей) — позволяет настроить отображение иконок в статус баре. Позволяет скрыть ненужные иконки, но получить доступ к ним по необходимости. Владельцы буков с 11 диагональю меня поймут.

Droplr (бесплатно) — быстрый шаринг скриншота экрана по хоткею ⌥⇧+4. Хотя, вроде бы, такое уже запилили в дробоксе.

Alinof Timer (бесплатно) — простой таймер

CheatSheet (бесплатно) – подсказывает горячии клавиши для программы при удержании

ClipMenu (бесплатно) — менеджер буфера обмена. Запоминает всё, что было занесено в буфер обмена и позволяет выбрать из списка по нажатию ⌘+V

Dash (бесплатна, но настойчиво предлагает купить ~650 рублей) – оффлайный аггрегатор документаций api по языкам и фреймворкам. Список довольно внушительный и обновляется довольно оперативно. Там документация по языкам, базам, js-фреймворкам и многому другому.

gfxCardStatus (бесплатно) — переключалка графических карт между дискретной и встроенной для экономии памяти.

iTerm (бесплатно) – лучший терминал для мака.

KeepassX (бесплатно) — продолжаю пользоватся этой программой, хотя есть более удобный 1password.

Adium (бесплатно) — мессенджер icq, jabber

Archiver (~650 рублей) – архиватор

Keka (бесплатно) – архиватор

Дробпокс, Яндекс.Диск, BitTorrentSync (все бесплатны) – облачная синхронизация данных

Doit.im (бесплатна с ограниченным функционалом, ~65 рублей в месяц) – менеджер задач по GTD

Hipchat (~65 рублей с пользователя в месяц) — прикольный чат для общения между разработчиками.

Path finder (~1300 рублей) – удобный файловый менеджер, умеет открыватся вместо finder

Sequel Pro (бесплатно) – незаменимый менеджер MySQL

RubyMine, PhpStorm (платно) — лучше IDE для кода нет

Sublime Text – ненагруженный редактор для всего

SourceTree (бесплатно) – отличный визуальный клиент для git

Transmit (~1100 рублей) — клиент для загрузки файлов через ftp, sftp, s3.

MPlayerX (бесплатно) – отличный проигрыватель видео-файлов

VLC (бесплатно) – всеядный проигрыватель видео-файлов

]]>
<![CDATA[Бага thinking-sphinx в Mac OS X]]> 2013-09-18T18:33:00+04:00 http://www.zagirov.name/thinking-sphinx-mac-os-x Возникла ошибка при использовании thinking-sphinx под Mac OS X. Убил полдня на её решение. Надеюсь этот пост поможет быстрей справиться с этим багом таким же как и я программистам, которые используют методику google driven development.

Эта ошибка воспроизводилась на Mac OS X 10.8.4, thinking-sphinx 3.0.5, и sphinx 2.0.9.

Началось всё с этой ошибки:

1
undefined method `inject' for nil:NilClass

И стектрейсом

1
2
3
4
5
6
7
8
9
(gem) thinking-sphinx-3.0.5/lib/thinking_sphinx/middlewares/inquirer.rb:49:in `call'
(gem) thinking-sphinx-3.0.5/lib/thinking_sphinx/middlewares/inquirer.rb:14:in `block in call'
(gem) thinking-sphinx-3.0.5/lib/thinking_sphinx/middlewares/inquirer.rb:13:in `call'
(gem) thinking-sphinx-3.0.5/lib/thinking_sphinx/middlewares/geographer.rb:11:in `call'
(gem) thinking-sphinx-3.0.5/lib/thinking_sphinx/middlewares/sphinxql.rb:13:in `call'
(gem) thinking-sphinx-3.0.5/lib/thinking_sphinx/middlewares/stale_id_filter.rb:10:in `call'
(gem) middleware-0.1.0/lib/middleware/runner.rb:31:in `call'
(gem) middleware-0.1.0/lib/middleware/builder.rb:102:in `call'
(gem) thinking-sphinx-3.0.5/lib/thinking_sphinx/search.rb:65:in `populate'

В логах было только видно, что запрос исполняется.

1
Sphinx Query (36.6ms)  SELECT * FROM `index` WHERE MATCH('телеФон*')

После копаний оказалось, что thinking-sphinx выполняет сразу два запроса: собственно запрос на получение данных из сфинкса и запрос «SHOW META».

Оказалось, что во время второго запроса, сфинкс по неизведанной причине разрывает соединение.

1
Sphinx Retrying query "SELECT * FROM `index` WHERE MATCH('телеФон*'); SHOW META" after error: Lost connection to MySQL server during query

Скачал beta-версию (2.1.1) sphinx’а, причём сайт при загрузке выдал мне сообщение: «Congratulations! It’s official, you’re a Sphinxter!!» =)

На этой версии ошибок не было. Скорее всего это из-за mac, думаю, что на линуксе было бы всё нормально. Но проверять этот вариант уже нет времени.

]]>
<![CDATA[Обновление gem mysql2 на MacOS X при обновлении MySQL до 5.6]]> 2013-03-04T19:02:00+04:00 http://www.zagirov.name/macos-update-mysql2-mysql-5-dot-6 На MacOS X в homebrew появился MySQL 5.6.10.

Поэтому при обновлении MySQL будет выскакивать ошибка о несоответствии библиотек:

1
Incorrect MySQL client library version! This gem was compiled for 5.5.28 but the client library is 5.6.10.

Если ставить так, как написано в readme:

1
gem install mysql2 --with-mysql-config=/usr/local/bin/mysql_config

То возникает ошибка:

1
ERROR: While executing gem ... (OptionParser::InvalidOption) invalid option: --with-mysql-config

Нужно добавить больше тирешек и кавычек

1
gem install mysql2 -- '--with-mysql-config=/usr/local/bin/mysql_config'

UPDATE:

Чтобы bundler всегда использовал данный параметр, выполните команду:

1
bundle config build.mysql2 --with-mysql-config=/usr/local/bin/mysql_config
]]>
<![CDATA[Как установить ruby 2.0.0-p0]]> 2013-02-24T18:56:00+04:00 http://www.zagirov.name/how-to-install-ruby-2-dot-0-0-p0 Сегодня вышел ruby 2.0.0 и я думаю скоро выйдет rails 4.

Если у вас возникли следующие ошибки, то установка простой коммандой rvm install ruby-2.0.0-p0 не получится:

1
2
Error running 'env CFLAGS=-O3 -march=corei7 -O2 -pipe ./configure --disable-install-doc --prefix=/Users/stamm/.rvm/rubies/ruby-2.0.0-p0 --with-opt-dir=/usr/local/opt/libyaml:/usr/local/opt/readline:/usr/local/opt/libxml2:/usr/local/opt/libxslt:/usr/local/opt/libksba:/usr/local/opt/openssl:/usr/local/opt/curl-ca-bundle:/usr/local/opt/sqlite --disable-shared', please read /Users/stamm/.rvm/log/ruby-2.0.0-p0/configure.log 
There has been an error while running configure. Halting the installation.

Или ошибка при запуске bundle install:

1
/Users/stamm/.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/net/http.rb:917:in `connect': SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed (OpenSSL::SSL::SSLError)

Чтобы избавиться от этих ошибок, нужно поставить openssl и gcc:

1
2
rvm pkg install openssl
brew install gcc

И переустановить с нужными флагами:

1
rvm reinstall ruby-2.0.0-p0 --with-gcc=gcc-4.7 --with-openssl-dir=$rvm_path/usr

Ruby 2.0.0 требуется bundler 1.3, который ещё не вышел. Его можно поставить:

1
gem install --pre bundler
]]>
<![CDATA[Парад ссылок №2]]> 2013-02-20T19:48:00+04:00 http://www.zagirov.name/links-parade-2 Yii
  • Вышел плагин для phpstorm, поддерживающий yii. Надеюсь автор не забросит его, и из этого получиться что-то более функциональное.

Тестирование

  • Функиональное тестирования как сервис – поддерживаются почти все популярные языки, все актуальные браузеры. Сам не пробовал, но судя по фичам у сервиса будут свои пользователи.
  • Процент покрытие тестами в ruby on rails. Работает в связке с travis ci. Сейчас процедура такая: пушим на github, travis-ci выполняет тесты, coveralls показывает как изменилось покрытие кода и показывает красивые отчёты об изменениях. При этом не держа у себя сервер и не платя ни копейки.
  • Крутое видео с демо по cucumber. Можно довольно быстро понять как правильно писать свои тесты на cucumber’е (код на github).

Нагрузочное тестирование

Разное

  • Создание бэкапов с помощью ruby – это вам не монструозные конфиги бакулы, тут всё быстро и понятно.
  • Прикольный интерфес для мониторинга показателей, написанные на ruby. Есть несколько предопределённый виджетов для отображения kpi. Добавляется блок очень быстро и приятно. В крайнем случае можно написать свой: ruby + coffee + sass. Работает без ajax-запросов, на основе постоянного соединения.
  • http://runnable.com/ – позволяет выполнить код на скриптовом языке прямо в браузере. Что-то типо сервиса jsfiddle, только для серверных языков. Пока поддерживается nodejs, но обещают поддержку php, ruby.
  • Бесплатный менеджер буфера обмена для MacOS. Теперь работать без неё не могу.
  • Очень интересная новость: гугл запустил маркеры – (видео). В кратце позволяет мне, как администратору сайта кликая и выделяю мышью указывать структуру информации на сайте. Сейчас поддерживаются только мероприятия, но с развитием можно будет указать много другой мета-информации. Но я за то, чтобы использовать микро-форматы. По сути гугл хочет переложить работу с себя на хозяев сайта. Посмотрим что из этого получится.
  • Крутое видео о том, зачем нужен DevOps. Вообще как-то начинает развиваться эта движуха, если кто не знает, советую почитать
  • Cмешная картинка ковёр-самолёт. Это к тому, что все понимают в свою меру понимания. Всегда помнить, когда пишите задачу или ТЗ ;)
  • Забавные гифки, что называется, до слёз: раз, два.
]]>
<![CDATA[Парад ссылок №1]]> 2013-02-11T19:43:00+04:00 http://www.zagirov.name/links-parade-1 Новая рубрика в блоге: парад ссылок. Это что-то наподобие линк-блога, но ориентированного на веб-разработчиков: php, js, администрирование и всё больше о ruby и рельсах. Короче всего, что мне интересно.

Надеюсь, каждый найдёт что-то интересное.

Развёртывание и деплой

С недавних пор подсел на capistrano, оказывается есть хорошее его дополнение caphub от компании railsware, позволяющая структурировать задания для разных приложений.

Отличный цикл статей о том, как начать работать с chef от Алексея Васильего. Chef – это инструмент для автоматизации настроек парка серверов.

PHP

Как сделать асинхронный запрос

Сравнение упаковщиков для php

Использование phing для собирания проекта. Ребята, у кого есть возможность – используйте capistrano или rake.

Новый опкэшер в PHP 5.5.0. Обратите внимание на табличку сравнения: неплохой прирост.

Php как сервис.

Ruby, RoR

Начался новый подкаст rwpod от того же Алексея Васильего и ко. Хороший подкаст, аккумулирующий новости не только из ruby-сообщества, но и веб-новостей. Пока нет харкорных тем, но я думаю, всё впереди.

Вышел RubyMine5. Прошу обратить внимание на видео о его возможностей.

У кого есть свои скрипты-помошники, предлагаю переехать на rake. Возникали проблемы с zsh, передачи параметра в задачу. Вцелом хороший инструмент не только для ruby разработчиков.

Должна быть интересная книга по устранению уязвимостей в rails-приложении. Обязательно посмотрите ролик.

Узнайте, сколько же времени загружаются ваши гемы.

Javascript

JsDB – каталог js-библиотек

CasperJs – тестирование js и не только. Использует PhantomJS.

Рисуем сложные графики. Демки.

Разное

Dash – Offline-документация для macos по многочисленным языкам и фреймворкам: php, yii, ruby, gems, ror, javascript, backbone, mongodb, postgresql, mysql и это далеко не весь перечень. Однозначно в повседневное использование!

Очень дешёвые SSL-сертификаты. Например, на 5 лет за $25.

Поддержка JSON-атрибутов в sphinx 2.1. Sphinx не отстаёт от postgresql.

Spark – интересная визуализация небольших данных. Скарливаем ему ряд значений: spark 0 30 55 80 33 150, и получаем ▁▂▃▅▂▇

Очень крутое обучение от codeschool. Схема такая: вам показывают мини-лекцию, потом вы должны выполнить задание, т.е. писать код прямо в браузуере и он проходит проверку. Также у ребят есть скринкасты. Обучение не скучное и довольно эффективное.

Нравиться ли вам такая рубрика? Продолжать?

]]>
<![CDATA[Ускорение скорости работы grep в Mac OS X]]> 2012-11-28T19:40:00+04:00 http://www.zagirov.name/gnu-grep-faster-than-mac-os-x На монитор попала статья о том, что grep от gnu быстрее стандартного маковского grep’а в 10 раз

Решил проверить у себя. На файле, размером в 720 Мб grep стал быстрей в 36 раз! Неплохо.

1
2
3
4
5
6
7
8
$ brew install grep
$ time /usr/bin/grep "GET /out" nginx-access_log.2 | wc -l
140858
/usr/bin/grep "GET /out" nginx-access_log.2 26.49s user 0.28s system 97% cpu 27.443 total wc -l 0.03s user 0.02s system 0% cpu 27.443 total

$ tmp time grep "GET /out" nginx-access_log.2 | wc -l
140858
grep "GET /out" nginx-access_log.2 0.58s user 0.15s system 98% cpu 0.748 total wc -l 0.03s user 0.01s system 6% cpu 0.747 total
]]>
<![CDATA[Ограничение прав пользователей в git]]> 2012-10-03T19:38:00+04:00 http://www.zagirov.name/restriction-access-users-in-git Бывали ситуации, когда сделали какой-то внерелизный автономный функционал или просто быстрый хотфикс, а в мастере один из коллег уже успел чего изменить, что ещё не протестировано. А это нарушает одно из правил — в мастере должен быть только стабильный код. Поэтому код в мастер не должен попадать непротестированным.

Почему он это сделал — это другой вопрос: просто не переключил ветку или намеряно. Или к джуниору подбежал директор или менеджер и сказал, что нужно быстро исправить. Тут нужно бить такого программиста по рукам, но лучше предупредить болезнь, чем лечить её =)

Ситуация вторая: есть тестировщики, которые пишут автоматические тесты, которые не отделимы от проекта, соответственно им нужно дать право только на конкретную папку, где находятся тесты, чтобы они случайно не сделали изменения в других файлах.

Поэтому нужно ограничивать push в репозиторий на основе этих правил: тестерам дать право изменять файлы только в рамках одной директории. А программистам не давать возможность пушить в мастер, только какой-то группе доверенных программистов, а по сути тим-лиду.

Мы используем gitolite и он позволяет это сделать на уровне конфигурации.

Есть следующие группы пользователей

  • мега-админы – они и только они могут менять историю в репе. Это чревато, но разрешено только одному человеку для непредвиденных ситуаций =)
  • гит-мастера — это те, кто может пушить в мастер (по сути мёржить тоже), тэгировать, создавать и удалять ветки с версией релиза и хотфиксные ветки. Название веток начинается соответственно с v и hf. Т.е: v1.0.1 и hf.1.0.1
  • разработчики — это пользователи, которые не могут пушить в мастер и создавать тэги. Также не могут удалить вертку с версией релиза и хотфиксную ветку. Могут создавать и удалять любые другие ветки.
  • тестеры — ограниченные пользователи. Могут менять файлы в только пределах директории protected/test.
  • только чтение — давать возможность только для чтения репы. Например, для технических писателей.
  • группа аутсорсеров – у них свои порядки, но они не могут удалить ветки master, dev, hotfixes, release

Есть 5 репозиториев: site, mobile, api, common-modules, outsource-site

Только на site распространяются правила для тестировщиков.

На site, mobile, api, common-modules — наши основные диктаторские правила

На outsource-site — «мягкие» правила.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
repo    gitolite-admin
        RW+     =   id_rsa



# ========== Группировка веток или путей =============


#Не разрешать писать в master и создавать тэги
@not_write = master$ refs/tags
# Не разрешать создавать ветки и удалять начинающиеся с v и hf.
@not_create = v hf.
# Тестеры могут писать только сюда
@tester_write_only = NAME/protected/tests/


# Эти ветки не могут удалять обычные программеры
@old_branches = master$ dev$ hotfixes$ realease$



# ========== Группы пользователей ==========


# Админы имеют право писать всюду
@admins = id_rsa
# Гит-админы могут писать в мастер и создавать ветки из @not_create
@git-admins = zagirov
# Разработчики
@developers = @git-admins ivanov petrov sidorov
# Тестеры имеют право на запись только в @tester_write_only
@testers = test-girl
# Только на чтение
@developers-ro = tech-writer
# Аутсорсеры со своими правилами
@outsource = five-hard-dev1 five-hard-dev2



# =========== Правила ===================




# Правила применяются для всех
repo @all
  RW+CD                      = @admins

# Главный сайт с тестами
repo  site
  # Правило для тестировщиков
  # Если оно создаётся, ниже должно быть правило, RWCD NAME/ = @developers
  # Оно разрешает другим писать сюда
  RW                         = @testers
  RW   @tester_write_only    = @testers
  R                          = @testers
  -                          = @testers


#Группа, куда применяются общие правила работы с ветками @git-branch-rules
@git-branch-rules = site mobile api common-modules

repo @git-branch-rules
  # Разрешаем писать в @not_write для @git-admins
  # У @developers на @not_write только чтение
  RWCD @not_write            = @git-admins
  -    @not_write            = @git-admins
  R    @not_write            = @developers
  -    @not_write            = @developers

  # Разрешаем создавать и удалять ветки @not_create для @git-admins
  # @developers не могут создавать удалять @not_create
  RWCD @not_create           = @git-admins
  -    @not_create           = @git-admins
  RW   @not_create           = @developers
  -    @not_create           = @developers

  # @developers могуть писать и создавать в @old_branches, но не удалять их
  RWC  @old_branches         = @developers
  -    @old_branches         = @developers

  # Для всего остального разрешаем доступ для @developers
  RWCD                       = @developers
  # Это нужно, т.к раньше для @testers запретили запись для определённых файлов
  RWCD NAME/                 = @developers

  # @developers-ro могут только читать
  R                          = @developers-ro


# Это обычный реп, без жёстких правил. Здесь просто запрещается удалять ветки master dev hotfixes realease
repo  outsource-site
  RWC  @old_branches         = @outsource
  -    @old_branches         = @outsource
  RWCD                       = @outsource

Если вы используете не gitolite, а другой продукт: gitosis или github, то скорее всего сделать это можно на хуках.

]]>
<![CDATA[Удаление данных из коллекции в Mongodb без блокировки]]> 2012-09-07T19:35:00+04:00 http://www.zagirov.name/remove-data-from-mongo-without-blocking Задача: удалять устаревшие данные из большой коллекции монги. Можно пойти в лоб и удалять так:

1
2
var time = new Date().getTime() - 2*24*60*60;
db.data.remove({updating_time: {$lte: time}})

В этом случае возникнет блокировка, и запросы на чтение будут очень долго выполняется. А система устроена таким образом, что постоянно вставлять и обновлять данные из этой коллекции.

Удаление с указанием лимита в монге не существует, поэтому приходиться писать свои велосипеды.

Скрипт выбирает от 10к до 20к записей и удаляет их по _id (по 3к за раз). Если выборка + удаление длилось больше 10 секунд, то удаляется только 1000 записей. Это сделано на всякий случай, когда идёт активная нагрузка на коллекцию (в моём случае активная вставка по 10к элементов).

Все приведённые значения подобраны импирическим путём под конкретную задачу.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
function microtime() {
  return new Date().getTime() / 1000;
}

/**
 * Удаляет данные из монги частями
 * @param last_time - время последнего запроса
 * @param collection - имя коллекции
 * @param criteria - критерия для удаления
 * @return {Array}
 */
function delete_data(last_time, collection, criteria) {
  var ids = [],
    start_time = microtime(),
    i = 0;
  if (last_time < 10) {
    count = 10000 + Math.floor((Math.random() * (20000-10000)) + 1);
  } else {
    count = 1000;
  }
  db[collection].find(criteria, {_id: 1}).limit(count).forEach(function(u){
    i++;
    ids.push(u._id);
    if (i % 3000 == 0) {
      db[collection].remove({_id: {$in: ids }});
      ids = [];
    }
  });

  if (ids.length) {
    db[collection].remove({_id: {$in: ids }});
  }

  last_time = microtime() - start_time;
  return [i, last_time];
}

function delete(collection, criteria) {
  var i = 0,
    last_count = 1,
    last_time = 1;
  while (last_count && i < 1000) {
    i++;
    result = delete_data(last_time, collection, criteria);
    last_count = result[0];
    last_time = result[1];
    print(last_count + ' ' + last_time);
    if (i % 5 == 0) {
      print("count: " + db[collection].count());
    }
  }
}

var time = new Date().getTime() - 2*24*60*60;
delete('data', {updating_time: {$lte: time});

Вызывать так:

1
mongo -u "user" -p < mongo_remove.js
]]>
<![CDATA[Яндекс.танк — инструмент нагрузочного тестирования]]> 2012-08-02T19:30:00+04:00 http://www.zagirov.name/yandex-tank-load-testing 28 июля на я.субботнике был представлен новый инструмент для нагрузочного тестирования Яндекс.танк. Это внутреняя разработка яндекса, которая наконец-то вышла в свет. Видел я этот танк ещё на YaC 2011, когда были соревнования по конфигурированию nginx.

Это консольный инструмент, пока не имеющий графического интерфейса, но дающий довольно полную картину в этой самой консоли.

Вот сам интерфейс:

yandex-tank-good

Сам проект и документация находится на github’е: https://github.com/yandex-load/yandex-tank

В кратце обрисую возможности:

Можно нагружать одну страницу или сразу список урлов (которые будут запрашиваться примерно в одинаковых соотношениях).

А можно самому составить список запросов со своими заголовками. Например, можно реализовать нагрузку от запросов от анонимных пользователей и залогиненных. Через скрипт (php, python, bash, etc) залогиниться на сайте и получить нужные куки. Сгенерировать в нужном формате данные для танка и запустить тест.

Танк позволяет создать 3 вида нагрузки:

  • Постоянная – указывается количество запросов и время
  • Линейный рост – указывается начальное и конечное значение и время
  • Рост шагами – указывается начальное и конечное значение, шаг увеличения нагрузки и время.

Причём эти виды нагрузки можно комбинировать в одном тесте.

Особенно хочеться отметить простоту и понятность синтаксиса.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#Подогреваем кэш
load = const(1, 10s)
# Линейно увеличиваем нагрузку с одного запроса в секунду до 10 в течение 2 минут
load = line(1, 10, 2m)
# Начинаем нагружать 10 запросами в секунду в течение 1 минуты, потом увеличиваем на 5 запросов и так до 40. Т.е. на каждый шаг будет затрачено по минут
load = line(10, 40, 5, 1m)
# Указываем нагружаемых хост, только ip (!)
address = 192.168.100.254:80
# Посылаемые заголовки
header = [Host: test.server]
header = [Connection: close]
header_http = 1.1
# Нагружаемые страницы
uri = / uri = /another_page/

Вот что случается, когда сервер не справляется:

yandex-tank-good

Думаю, что проект со временем будет обрастаться графическими интерфейсами, разработанными самим яндексом или сообществом.

Я даже уже придумал, что можно сделать. Получать статистику на нагружаемом сервере через vmstat и выводить красивые графики для быстрого нахождения наиболее узкого места: память, cpu, io.

]]>
<![CDATA[Yii: рецепты №2]]> 2012-03-26T19:23:00+04:00 http://www.zagirov.name/yii-cookbook-2 Продолжаю делится интересным о Yii

Шифрование данных

Иногда требуется зашировать данные с возможностью последующей обратной дешифровкой.

В yii есть отличная обёртка для такого рода операций: CSecurityManager::encrypt() и CSecurityManager::decrypt()

Настраиваем алгоритм, режим и ключ шифрования.

1
2
3
4
5
'components'=>array(
  'securityManager' => array(
    'cryptAlgorithm'=>array('rijndael-256', '', 'ofb', ''),
    'encryptionKey' => 'pnkRVLZC6Oj87H2G8qmsNN',
  ),
1
2
$encrypt = Yii::app()->securityManager->encrypt('очень важные данные');
echo Yii::app()->securityManager->decrypt($encrypt);

Примечания:

  • Чтобы использовать шифрованные данные в виде параметров в get-запросе, нужно использовать функцию base64_encode() и base64_decode()
  • Можно использовать разные ключи, передав второй параметр в функции CSecurityManager::encrypt() и CSecurityManager::decrypt()

Организация конфигураций

В Yii существует 3 уровня конфигураций: веб-приложение, консольное приложение, тесты. Требуется прописывать в этих 3 файлах часть одинаковых конфигураций: пути для импорта, конфигурация базы, правила роутов. Так же нужно убрать настройки из системы контроля версии файлы, которые могут быть разные на разных серверах или компьютерах разработчиков: подключение к базе, разных уровень логирования, кэширование. Нужно оставить только “болванку” конфигураций, которые корректируются в связи с необходимостью.

Схемотично такую систему можно изобразить так:

yii-config

Все файлы, имеющие custom в имени, убираются из системы контроля версии (в моём случае добавляются в .gitignore). Вместо них создаются файлы-примеры для настройки: example.main.custom.php, example.console.custom.php, example.test.custom.php.

Для веб-приложения используются 2 файла: main.php и main.custom.php.

В main.custom.php пишутся индивидуальные настройки: подключение к mysql, сервер кэширования. У разработчика включаются gii, yii-debug-toolbar, добавляются вспомогательные режимы логирования, профилирования.

В main.php пишутся все настройки, которые не будут менятся для конкретного сервера: пути импорта, правила роутинга и т.п. И эти настройки сливаются с настройками с main.custom.php через функцию CMap::mergeArray(). Приоритет имеют настройки из main.custom.php

Примеры файлов main.php, main.custom.php

Конфигурация консольного приложения работает схоже: сначала сливаются изменения из main.php ( включая main.custom.php) c console.php, а затем накладываются из console.custom.php. Настройки тестов работают аналогично.

Естественно, у этой схемы есть свои минусы:

В консольном приложении не может быть некоторых параметров, а некоторые нужно удалить. Например, нужно удалить параметр defaultController:

1
unset($aConfig['defaultController']);

Или отключить Yii debug toolbar:

1
2
3
4
5
foreach( $aConfig['components']['log']['routes'] as $k => $v ){
  if( $v['class'] == 'XWebDebugRouter' ){
    unset( $aConfig['components']['log']['routes'][$k] );
  }
}

Пример такой конфигурации

SphinxSQL

Есть 2 способа работы со sphinx из php: через api (используя библиотеку) и воспользоваться SphinxQL: получение данных через протокол mysql и используя запросы, очень схожие с синтаксисом MySQL-запросов.

Для меня более предпочтителен второй вариант, не нужно таскать с собой библиотеку и все новые фишки будут реализованы в первую очередь в SphinxQL.

Включаем в параметрах sphinx прослушивание через протокол mysql

1
2
3
searchd {
listen = 127.0.0.1:9306:mysql41
}

Включаем в настройках yii компонент sphinx

1
2
3
4
sphinx' => array(
    'class' => 'system.db.CDbConnection',
    'connectionString' => 'mysql:host=127.0.0.1;port=9306',
),

Теперь мы можем делать запросы:

1
2
3
4
5
6
7
8
9
10
11
$sSql = 'SELECT post_id
    FROM main
    WHERE
        MATCH(' . Yii::app()->sphinx->quoteValue('yii') . ')
        AND iscomment = 0
    ORDER BY @weight
OPTION field_weights=(title=10,content=1)';

$ids = Yii::app()->sphinx
  ->createCommand($sSql)
  ->queryColumn();
]]>
<![CDATA[Yii: рецепты №1]]> 2012-03-23T19:19:00+04:00 http://www.zagirov.name/yii-cookbook-1 Пакетирование js и css-файлов и использование зависимостей между этими пакетами.

Есть замечательный инструмент для рисования графиков на js — highcharts, но он использует фреймворк jQuery и сам jQuery не подключает. Соответственно, мы создаём наш пакет, где указываем js и css файлы из highcharts и прописываем зависимость от jQuery.

1
2
3
4
5
6
7
8
9
10
'clientScript'=>array(
  'packages' => array(
       'highcharts' => array(
            'baseUrl' => '/js/highcharts/',
            'js'=>array(YII_DEBUG ? 'highcharts.src.js' : 'highcharts.js'),
            'css' => array('highcharts.css'),
            'depends'=>array('jquery'),
        ),
    )
)

Теперь вместо

1
2
3
4
Yii::app()->clientScript
->registerPackage('jquery')
->registerScriptFile('/js/highcharts/highcharts.js')
->registerCssFile('/js/highcharts/highcharts.css');

Пишем

1
2
Yii::app()->clientScript
->registerPackage('highcharts');

YII_DEBUG

Эта константа позволяет включать режим дебага и она же доставляет немного неудобства. Локально нужно устанавливать констатну YII_DEBUG в true, но не комитить это изменение нельзя. Т.е. перед комитом нужно выставлять значение в false. Мы же программисты, народ ленивый, поэтому тем меньше пальцедвижений, тем лучше.

  • Можно воспользовать changelist в PhpStorm
  • Можно не добавлять файл в коммит

Но эти варианты не подходят, когда используется консоль, нельзя будет использовать команду git add .

Решение очевидное: использовать директивы auto_prepend_file При запуске любого php-файла, будет предварительно выполнятся наш файлик с установленной константой.

Теперь пишем в файл с настройками сайта php-fpm /etc/php5/fpm/pool.d/super-site-on-yii.conf

1
2
[php]
php_admin_value[auto_prepend_file] = /var/www/yii_debug.php

Создаём файл /var/www/yii_debug.php

1
2
3
4
<?php
// Если включен профайлер xdebug
//define('YII_DEBUG', empty($_GET['XDEBUG_PROFILE']));
define('YII_DEBUG', true);

Автодополнение в PhpStorm

При подключении стороннего компонента или расширении стандартного хотелось бы «научить» IDE подсказывать

1
2
3
4
5
6
7
8
'components'=>array(
  'user'=>array(
    'class' => 'WebUser',
  ),
  'myext'=>array(
    'class' => 'MyExt',
  ),
)

Создаём файлик в любом месте, из которого он не сможет подключится автолоадерем. Например, в protected/autocomplete.php

1
2
3
4
5
6
<?php
/**
 * @property WebUser $user
 * @property MyExt $myext
 */
class CApplication {}

Теперь по вводу Yii::app()–>myext PhpStorm будет посказывать методы из класса MyExt.

Ещё можно заставить PhpStorm подсказывать в файлах представления (views)

Обычно хватает стандартного набора ($this и $form), но в каждой конкретной view может быть свой набор переменных

1
2
3
/** @var $this Controller */
/** @var $form CActiveForm */
/** @var $model User */

Последний метод можно полу-автоматизировать через шаблоны gii. Это остаётся в качестве самостоятельной работы читателя.

]]>
<![CDATA[Редирект при вставки сайта через iframe]]> 2012-02-02T19:17:00+04:00 http://www.zagirov.name/redirect-iframe Довольно долго бился c запрещением вставки сайта в iframe с указанием белого списка сайтов, которые могу это сделать

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (top != self) {
    var white_list = ['yandex.ru', 'google.com'];
    var isFriend = false;
    var hostname = document.referrer.split("/")[2].split(":")[0];
    for (var i=0; i < white_list.length; i++) {
        if (hostname == white_list[i] || hostname == "www." + white_list[i]) {
            isFriend = true;
            break;
        }
    }
    if ( ! isFriend ) {
        window.top.location = window.location.href;
    }
}
]]>
<![CDATA[Эмуляция хоткея с участием Alt в midnight commander]]> 2011-12-14T19:15:00+04:00 http://www.zagirov.name/mc-alt-mac После перехода на Mac OS X, не как не мог отвыкнуть от использования хоткеев с участием клавиши Alt.

Нашёл наконец-то способ, как съэмулировать хоткеи в сочении с Alt. Например, чтобы сделать действие по Alt+c нужно последовательно нажать Esc и c. «А ларчик просто открывался» ©.

Тех, кто не знаком с хоткеями в mc, предлагаю ознакомиться с листингом горячих сочетаний mc.

]]>
<![CDATA[Bacula - резервное копирование: быстро, бесплатно, без смс]]> 2011-11-27T19:08:00+04:00 http://www.zagirov.name/bacula-backup Не секрет, что админы делятся на 2 типа: кто ещё не делает бэкапы и кто их уже делает. Я совсем недавно перешёл на сторону светлых админов, хочу поделиться реально работающими конфигами. Теперь седина пропала и мои волосы вновь мягкие и шелковистые =)

Использовать буду систему под названием bacula. Соответственно всё проверялось и работает под ОС GNU/Debian 6.

В интернете видел много довольно полных мануалов, где описывается конфигурация. Я описывать почти ничего не буду, просто приведу рабочие конфиги и скажу что копировать, чтобы начать бэкапить с ещё одного сервера. Можно считать статью предназначенной для тех, кто не осилил man, ну или хочет съэкономить своё время и получить работающую систему резервирования “побыстрее”.

Имеется 2 машины

  • home.zagirov.name (1.1.1.1) – сам сервер bacula (bacula director и storage director), mysql + www
  • www.zagirov.name (2.2.2.2) – mysql + www

Для mysql будем использовать собственный скрипт, который запускает xtrabackup.

Устанавливаем на обоих серверах sudo (чтобы запускать бэкапилку базы от рута через sudo) и file director

1
aptitude install sudo bacula-fd openssh-server

А на сервере, который будет хранить все бэкапы (home.zagirov.name – 1.1.1.1) установим ещё bacula director и storage director:

1
aptitude install bacula-director-mysql bacula-sd-mysql bacula-console

Выполняем на обоих серверах:

1
usermod -s /bin/bash bacula

Выполняем на home.zagirov.name (1.1.1.1)

1
2
3
4
5
6
su - bacula
mkdir -p ~/.ssh
chmod 700 ~/.ssh
cd ~/.ssh
ssh-keygen -b 4096 -t rsa -f bacula -N ""
chmod 400 ~/.ssh/*

Добавляем правило для пользователя bacula запускать xtrabackup под sudo без запрашивания пароля.

1
echo "bacula ALL=NOPASSWD: /usr/bin/xtrabackup" > /etc/sudoers.d/bacula

Создаём файлы для создания бэкапов. Храниться они будут централизовано на сервере бэкапов, а запускаться на удалённых машинах будут через sudo bash -s

/etc/bacula/scripts/xtrabackup.sh

1
2
3
4
5
6
7
8
9
10
#!/bin/sh

DIR="/bacula/create/xtrabackup"
if [ -d "$DIR" ]; then
  rm -r $DIR
fi
mkdir -p $DIR

sudo xtrabackup --defaults-file=/etc/mysql/my.cnf --datadir=/var/lib/mysql \
 --target-dir=$DIR --backup

/etc/bacula/scripts/xtrabackup_rm.sh

1
2
3
4
#!/bin/sh

DIR="/bacula/create/xtrabackup"
rm -r $DIR

home.zagirov.name: /etc/bacula/bacula-dir.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
Director {
  Name = home.zagirov.name-dir
  DIRport = 9101
  QueryFile = "/etc/bacula/scripts/query.sql"
  WorkingDirectory = "/var/lib/bacula"
  PidDirectory = "/var/run/bacula"
  Maximum Concurrent Jobs = 1
  Password = "password-director"
  Messages = Daemon
  DirAddress = 1.1.1.1
}

Catalog {
  Name = MyCatalog
  dbname = "bacula"; DB Address = ""; dbuser = "bacula"; dbpassword = "password_for_db"
}

Console {
  Name = home.zagirov.name-mon
  Password = "password-console"
  CommandACL = status, .status
}


Messages {
  Name = Daemon
  mailcommand = "/usr/lib/bacula/bsmtp -h localhost -f \"\(Bacula\) \<%r\>\" -s \"Bacula daemon message\" %r"
  mail = root@localhost = all, !skipped            
  console = all, !skipped, !saved
  append = "/var/lib/bacula/log" = all, !skipped
}

Messages {
  Name = Standard
  mailcommand = "/usr/lib/bacula/bsmtp -h localhost -f \"\(Bacula\) \<%r\>\" -s \"Bacula: %t %e of %c %l\" %r"
  operatorcommand = "/usr/lib/bacula/bsmtp -h localhost -f \"\(Bacula\) \<%r\>\" -s \"Bacula: Intervention needed for %j\" %r"
  mail = rustam@zagirov.name = all, !skipped            
  operator = rustam@zagirov.name = mount
  console = all, !skipped, !saved
  append = "/var/lib/bacula/log" = all, !skipped
  catalog = all
}

Storage {
  Name = File
  Address = 1.1.1.1
  SDPort = 9103
  Password = "password-storage"
  Device = FileStorage
  Media Type = File
}

#Расписания
Schedule {
  Name = "WeeklyCycle"
  Run = Full 1st sun at 02:05
  Run = Differential 2nd-5th sun at 02:05
  Run = Incremental mon-sat at 02:05
}

Schedule {
  Name = "WeeklyCycleAfterBackup"
  Run = Full sun-sat at 02:10
}


Pool {
  Name = Default
  Pool Type = Backup
  Recycle = yes                       # Bacula can automatically recycle Volumes
  AutoPrune = yes                     # Prune expired volumes
  Volume Retention = 365 days         # one year
}

Pool {
  Name = File
  Pool Type = Backup
  Recycle = yes                       # Bacula can automatically recycle Volumes
  AutoPrune = yes                     # Prune expired volumes
  Volume Retention = 365 days         # one year
  Maximum Volume Jobs = 1
  Maximum Volume Bytes = 10G          # Limit Volume size to something reasonable
  Maximum Volumes = 100               # Limit number of Volumes in Pool
}


# Scratch pool definition
Pool {
  Name = Scratch
  Pool Type = Backup
}



@/etc/bacula/client_home.zagirov.name.conf
@/etc/bacula/client_www.zagirov.name.conf

home.zagirov.name: /etc/bacula/client_home.zagirov.name.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
### backup for home.zagirov.name


Client {
  Name = home.zagirov.name-fd
  Address = 1.1.1.1
  FDPort = 9102
  Catalog = MyCatalog
  Password = "password-fd-home.zagirov.name"
  File Retention = 30 days            # 30 days
  Job Retention = 6 months            # six months
  AutoPrune = yes                     # Prune expired Jobs/Files
}


JobDefs {
  Name = "DefaultJob"
  Type = Backup
  Level = Incremental
  Client = home.zagirov.name-fd
  Schedule = "WeeklyCycle"
  Storage = File
  Messages = Standard
  Pool = File
  Priority = 10
  Write Bootstrap = "/var/lib/bacula/%c.bsr"
}

Job {
  Name = "RestoreFiles"
  Type = Restore
  Client=home.zagirov.name-fd
  FileSet="home.zagirov.name-www"
  Storage = File
  Pool = Default
  Messages = Standard
  Where = /bacula/restore/
}


Job {
  Name = "BackupCatalog"
  JobDefs = "DefaultJob"
  Level = Full
  FileSet="Catalog"
  Schedule = "WeeklyCycleAfterBackup"
  RunBeforeJob = "/etc/bacula/scripts/make_catalog_backup.pl MyCatalog"
  RunAfterJob  = "/etc/bacula/scripts/delete_catalog_backup"
  Write Bootstrap = "/var/lib/bacula/%n.bsr"
  Priority = 11
}

FileSet {
  Name = "Catalog"
  Include {
    Options {
      signature = MD5
    }
    File = "/var/lib/bacula/bacula.sql"
  }
}


Job {
  Name = "home.zagirov.name-www"
  JobDefs = "DefaultJob"
  FileSet = "home.zagirov.name-www"
}


FileSet {
  Name = "home.zagirov.name-www"
  Include {
    Options {
      signature = MD5
    }
    File = /var/www
  }
}



Job {
  Name = "home.zagirov.name-MySQL"
  JobDefs = "DefaultJob"
  Level = Full
  FileSet="home.zagirov.name-MySQL"
  Schedule = "WeeklyCycle"
  RunBeforeJob = "/etc/bacula/scripts/xtrabackup.sh"
  RunAfterJob  = "/etc/bacula/scripts/xtrabackup_rm.sh"
  Write Bootstrap = "/var/lib/bacula/%n.bsr"
  Priority = 11                   # run after main backup
}

FileSet {
  Name = "home.zagirov.name-MySQL"
  Include {
    Options {
      signature = MD5
    }
    File = "/bacula/create/xtrabackup"
  }
}

home.zagirov.name: /etc/bacula/client_www.zagirov.name.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
### backup from www.zagirov.name

Client {
  Name = www.zagirov.name-fd
  Address = 2.2.2.2
  FDPort = 9102
  Catalog = MyCatalog
  Password = "password-fd-www.zagirov.name"
  File Retention = 30 days            # 30 days
  Job Retention = 6 months            # six months
  AutoPrune = yes                     # Prune expired Jobs/Files
}

JobDefs {
  Name = "DefaultJob-www.zagirov.name"
  Type = Backup
  Level = Incremental
  Client = www.zagirov.name-fd
  Schedule = "WeeklyCycle"
  Storage = File
  Messages = Standard
  Pool = File
  Priority = 10
  Write Bootstrap = "/var/lib/bacula/%c.bsr"
}

Job {
  Name = "Restore-www.zagirov.name"
  Type = Restore
  Client=www.zagirov.name-fd
  FileSet="www.zagirov.name"
  Storage = File
  Pool = Default
  Messages = Standard
  Where = /bacula/restore/
}



Job {
  Name = "www.zagirov.name-www"
  JobDefs = "DefaultJob-www.zagirov.name"
  FileSet = "www.zagirov.name-www"
}


FileSet {
  Name = "www.zagirov.name-www"
  Include {
    Options {
      signature = MD5
    }
    File = /var/www/www.zagirov.name/www
  }
}



Job {
  Name = "www.zagirov.name-MySQL"
  JobDefs = "DefaultJob-www.zagirov.name"
  Level = Full
  FileSet="www.zagirov.name-MySQL"
  Schedule = "WeeklyCycle"
  RunBeforeJob = "ssh -i /var/lib/bacula/.ssh/bacula bacula-www.zagirov.name 'sudo bash -s' </etc/bacula/scripts/xtrabackup.sh"
  RunAfterJob  = "ssh -i /var/lib/bacula/.ssh/bacula bacula-www.zagirov.name 'sudo bash -s' < /etc/bacula/scripts/xtrabackup_rm.sh"
  Write Bootstrap = "/var/lib/bacula/%n.bsr"
  Priority = 11
}

FileSet {
  Name = "www.zagirov.name-MySQL"
  Include {
    Options {
      signature = MD5
      compression = GZIP
    }
    File = "/bacula/create/xtrabackup"
  }
}

home.zagirov.name: bacula-sd.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Storage {
  Name = home.zagirov.name-sd
  SDPort = 9103
  WorkingDirectory = "/var/lib/bacula"
  Pid Directory = "/var/run/bacula"
  Maximum Concurrent Jobs = 20
  SDAddress = 1.1.1.1
}

Director {
  Name = home.zagirov.name-dir
  Password = "password-storage"
}

Director {
  Name = home.zagirov.name-mon
  Password = "password-mon"
  Monitor = yes
}

Device {
  Name = FileStorage
  Media Type = File
  Archive Device = /bacula/
  LabelMedia = yes;                   # lets Bacula label unlabeled media
  Random Access = Yes;
  AutomaticMount = yes;               # when device opened, read it
  RemovableMedia = no;
  AlwaysOpen = no;
}

Messages {
  Name = Standard
  director = home.zagirov.name-dir = all
}

home.zagirov.name: /etc/bacula/bacula-fd.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Director {
  Name = home.zagirov.name-dir
  Password = "password-fd-home.zagirov.name"
}

Director {
  Name = home.zagirov.name-mon
  Password = "password-mon-fd-home.zagirov.name"
  Monitor = yes
}


FileDaemon {
  Name = home.zagirov.name-fd
  FDport = 9102
  WorkingDirectory = /var/lib/bacula
  Pid Directory = /var/run/bacula
  Maximum Concurrent Jobs = 20
  FDAddress = 1.1.1.1
}

Messages {
  Name = Standard
  director = home.zagirov.name-dir = all, !skipped, !restored
}

home.zagirov.name: /etc/bacula/bconsole.conf

1
2
3
4
5
6
Director {
  Name = localhost-dir
  DIRport = 9101
  address = 1.1.1.1
  Password = "password-director"
}

А на удалённом настраиваем только fd

www.zagirov.name: /etc/bacula/bacula-fd.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Director {
  Name = home.zagirov.name-dir
  Password = "password-fd-www.zagirov.name"
}

Director {
  Name = stamm-server-mon
  Password = "password-mon-fd-www.zagirov.name"
  Monitor = yes
}

FileDaemon {
  Name = www.zagirov-name-fd
  FDport = 9102
  WorkingDirectory = /var/lib/bacula
  Pid Directory = /var/run/bacula
  Maximum Concurrent Jobs = 20
  FDAddress = 109.234.152.187
}

Messages {
  Name = Standard
  director = home.zagirov.name-dir = all, !skipped, !restored
}

Внимание!

Не забываем поменять пароли на свои!

Запускаем в консоле bconsole

1
2
3
4
5
6
7
8
9
10
11
12
13
14
*label
Automatically selected Storage: File
Enter new Volume name: backup
Defined Pools:
     1: Default
     2: File
     3: Scratch
Select the Pool (1-3): 2
Connecting to Storage daemon File at 95.31.29.182:9103 ...
Sending label command for Volume "backup" Slot 0 ...
3000 OK label. VolBytes=210 DVD=0 Volume="backup" Device="FileStorage" (/bacula/)
Catalog record for Volume "backup", Slot 0  successfully created.
Requesting to mount FileStorage ...
3906 File device "FileStorage" (/bacula/) is always mounted.

Восстановление происходит тоже через команду bconsole. Например, мы захотели восстановить файлы веб-сайта с www.zagirov.name на home.zagirov.name:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
*restore
Automatically selected Catalog: MyCatalog
Using Catalog "MyCatalog"

First you select one or more JobIds that contain files
to be restored. You will be presented several methods
of specifying the JobIds. Then you will be allowed to
select which files from those JobIds are to be restored.

To select the JobIds, you have the following choices:
     1: List last 20 Jobs run
     2: List Jobs where a given File is saved
     3: Enter list of comma separated JobIds to select
     4: Enter SQL list command
     5: Select the most recent backup for a client
     6: Select backup for a client before a specified time
     7: Enter a list of files to restore
     8: Enter a list of files to restore before a specified time
     9: Find the JobIds of the most recent backup for a client
    10: Find the JobIds for a backup for a client before a specified time
    11: Enter a list of directories to restore for found JobIds
    12: Select full restore to a specified Job date
    13: Cancel
Select item:  (1-13): 5
Defined Clients:
     1: home.zagirov.name-fd
     2: www.zagirov.name-fd
Select the Client (1-2): 2
The defined FileSet resources are:
     1: www.zagirov.name
     2: www.zagirov.name-MySQL
Select FileSet resource (1-2): 1
+-------+-------+----------+------------+---------------------+------------+
| JobId | Level | JobFiles | JobBytes   | StartTime           | VolumeName |
+-------+-------+----------+------------+---------------------+------------+
|    66 | F     |    3,327 | 32,960,325 | 2011-11-19 03:47:28 | Backup     |
|   138 | D     |        7 |  5,973,688 | 2011-11-27 02:08:41 | Backup     |
+-------+-------+----------+------------+---------------------+------------+
You have selected the following JobIds: 66,138

Building directory tree for JobId(s) 66,138 ...  ++++++++++++++++++++++++++++++++++++++++++++
2,959 files inserted into the tree.

You are now entering file selection mode where you add (mark) and
remove (unmark) files to be restored. No files are initially added, unless
you used the "all" keyword on the command line.
Enter "done" to leave this mode.

cwd is: /
$ add *
3,327 files marked.
$ done
Bootstrap records written to /var/lib/bacula/home.zagirov.name-dir.restore.1.bsr

The job will require the following
   Volume(s)                 Storage(s)                SD Device(s)
===========================================================================
   
    Backup                    File                      FileStorage              

Volumes marked with "*" are online.


3,327 files selected to be restored.

The defined Restore Job resources are:
     1: home.zagirov.name-restore
     2: www.zagirov.name-restore
Select Restore Job (1-2): 1
Run Restore job
JobName:         home.zagirov.name-restore
Bootstrap:       /var/lib/bacula/home.zagirov.name-dir.restore.1.bsr
Where:           /bacula/restore/
Replace:         always
FileSet:         home.zagirov.name-105
Backup Client:   www.zagirov.name-fd
Restore Client:  www.zagirov.name-fd
Storage:         File
When:            2011-11-27 02:56:27
Catalog:         MyCatalog
Priority:        10
Plugin Options:  *None*
OK to run? (yes/mod/no): y
Job queued. JobId=143
You have messages.

Для добавления нового сервера необходимо добавить в конфиг bacula director инклуд нового файла, который будет содержать следующие директивы:

  • Client, в который прописываем имя, ip и пароль
  • JobDefs – исходный джоб, от которого все будут наследоваться
  • Job восстановления впринципе не обязательно прописывать, но это удобно, когда нужно восстанавливать прямо на нужный сервер
  • Job и FileSet – собственно сам джоб и список файлов для этого джоба Думаю, теперь bacula кажется не такой уж и страшной как в начале?

Ссылки:

]]>
<![CDATA[Nginx: общие принципы конфигурации]]> 2011-10-30T18:59:00+04:00 http://www.zagirov.name/nginx-config На днях посмотрел видео с участием Игоря Сысоева – отца русского nginx =)

В выступлении Игорь говорит не о мастабировании в привычном для всех понимании (высокая нагрузка), а в плане рекомендаций к написанию конфигурации для nginx, чтобы при росте конфигурации не было проблем с его редактированием.

Виды location:

  1. Описанные простыми строками (статические)
1
2
3
location /dir/ {} - обычный префиксный location
location = /dir/ {} - точное совпадение по запросу
location ^~ /dir/ {} - префиксный location, но после него не идёт проверка по location на регулярных выражениях
  1. Описанные регулярными выражениями
1
2
location ~ /dir/ {} - с учётом регистра
location ~* /dir/ {} - без учёта регистра
  1. Именнованные location
1
location @php {}

Расположение статических location не играет роли.

Location’ы, написанные с помощью регулярных выражений, выполянются в порядке написания.

По возможности используйте статические location’ы. Например, вместо

1
location ~ .(css|js|jpe?g|png)$ {}

выносить все статические файлы в определённый каталог

1
location /static/ {}

Заворачивание location на регулярном выражении в статический location

1
2
3
location ~* ^/i/(.)(.-\.gif)(
  alias /images/$1$1$2
)
1
2
3
4
5
6
location /i/ {
  location ~* ^/i/(.)(.-\.gif)$ {
    alias /images/$1$1$2
  }
  return 404
}

Понимание директив root и alias:

В общем смысле, root – добавляем в запрос путь в качестве префикса, а alias – заменяет location на указанный в директиве alias

1
2
3
location /dir/ {
    root /path/to/data;
}
1
2
/dir/a/image.gif
/path/to/data/dir/a/image.gif
1
2
3
location /dir/ {
     alias /path/to/data/;
}
1
2
/dir/a/image.gif
/path/to/data/a/image.gif
1
2
3
location ~ ^/dir/ {
  root /path/to/$subdomain;
}
1
2
/dir/a/image.gif
/path/to/shop/dir/a/image.gif
1
2
3
location ~ ^/dir/.+/(.)(.*)$ {
    alias /path/to/data/$1/$1$2;
}
1
2
/dir/a/image.gif
/path/to/data/i/image.gif
1
2
3
location ~ ^/dir/ {
    alias /path/to/data/0.gif
}
1
2
/dir/a/image.gif
/path/to/data/0.gif

Обратите внимание на конечных слэш в proxy_pass

1
2
3
location /dir/ {
  proxy_pass http://back
}
1
2
/dir/a/image.gif
/dir/a/image.gif
1
2
3
location /dir/ {
    proxy_pass http://back/
}
1
2
/dir/a/image.gif
    /a/image.gif

Большинство директив носит декларативный характер, а это значит нет разницы, где их определять.

1
2
3
4
5
6
server {
    location / {}
    location /images/ {}
    
    root /path/;
}

Не рекомендуется использовать if. В примере сработает только последний if. Игорь рекомендует использовать if в связке с return.

1
2
3
4
5
6
7
8
9
10
location / {

  if (1) {
    gzip off;
  }

  if (1) {
    keepalive off;
  }
}

Распространённые ошибки:

В этом примере break вообще не нужен, а expires можно вынести в location

1
2
3
4
5
6
7
location ~* \.(gif|jpe?g|png)$ {
  root /data;
  if (-e $request_filename) {
     expires 1y;
     break;
  }
}

Break тут не нужен.

1
2
3
4
location ~* \.(gif|jpe?g|png)$ {
   root /data;
   break;
}

Такое возникает у переходящих с apache. Тут нужно заменить всё на статические location.

1
2
3
4
5
location / {
  if ($uri ~ ^/login.php$) { }

  if ($uri ~ ^/image.php$) { }
}
]]>
<![CDATA[Yii: автодополнение в консоли]]> 2011-10-23T18:57:00+04:00 http://www.zagirov.name/yii-console-completion Очень не хватало автодополнения комманд при вызове консольных комманд yii, чувствовал какую-то неполноценность yii в bash.

На просторах интернета была найдена статья, позволяющая реализовать автодополнение с помощью родной unix-утилиты bash_completion.

Если у вас проект находиться под управлением git, то просто добавляем сабмодуль:

1
git submodule add git://github.com/Stamm/yii-console-completion protected/extensions/complete/

Или создайте файл LCompleteCommand.php в protected/extensions/complete/

Теперь подключаем класс в конфигурационном файле для консольного приложения (обычно это console.php):

1
2
3
4
5
6
'commandMap' => array(
    'complete' => array(
        'class' => 'ext.complete.LCompleteCommand',
        //'bashFile' => '/etc/bash_completion.d/yii_applications' //Defaults to </etc/bash_completion.d/yii_applications>. May be changed if needed
    ),
),

Пути до директории bash-completion могут различаться в зависимости от системы. Для Debian и Ubuntu можно оставить стандартный путь. В Mac OS X bash-completion был установлен с помощью homebrew, так что путь нужно сменить на /usr/local/etc/bash_completion.d/yii_applications

Теперь выполняем комманду для создания bash-completion файла от root:

1
sudo ./yiic complete install

Теперь при создании новой сессии в bash будет работать автодополнение для yiic:

  1. Для приложения — набор возможных команд
  2. Для команды — набор возможных действий (actions) и именованых параметров действия по умолчанию
  3. Для действия — подсказки по ее именованым параметрам

Источник: http://habr-sandbox.livejournal.com/230319.html

]]>
<![CDATA[Организация рабочего потока: Phpstorm, Redmine, Changelists]]> 2011-10-23T18:53:00+04:00 http://www.zagirov.name/phpstorm-redmine-changelist-workflow Сегодня я покажу как у меня получается сделать работу удобной с использованием Phpstorm, Redmine, Teamcity.

  • Есть задача в redmine.
  • Я начинаю её выполнять.
  • Всё проверяю локально и коммичу в репозиторий.
  • Выливаю изменения на тестовый сервер.

Есть несколько ньюансов. Бывают задачи очень объёмные и/или не очень срочные, которые я делаю в перерывах между задачами с более высокими приоритетами. Или спокойно делаю задачу, но прибегает менеджер с огромными глазами и криками, что сайт выдаёт ошибку, и нужно сделать быстрый hotfix.

В этом случае очень помогают changelist в PhpStorm.

Смысл changelist’а заключается в логическом разделении группы файлов для коммита. Т.е. файлы “прикрепляются” к определённому changelist’у. И при коммите мы выбираем нужный changelist и коммитим только файлы из этого changelist’а. Changelist есть всегда, по-умолчанию название default.

Настраиваем Redmine:

Нужно включить в параметрах redmine пункт Enable REST web service

phpstorm-changelist

Добавляем новый Task server в PhpStorm. Api token можно найти в личном разделе в redmine.

phpstorm-changelist

И добавляем парсинг номера задачи из описания коммита для отображения её как ссылки на задачу в redmine.

Project Settings → Version Control → Issue Navigation

Вводим паттерн регулярного выражения #(\d+) и генерируемую ссылку: **http://redmine.company.com/issues/$1**

phpstorm-changelist

Теперь открываем задачу Tools → Task → Open. Тут работает автодополнение для имени задачи.

Если были открытые файлы, то они все скроются. Сейчас мы работаем в своём наборе открытых файлов. Если откроем changelist под названием default, то все открытые файлы вернуться в редактор. Нужно понимать, что изменения в скрытых файлах остаются (это вам не git stash), а просто скрываются из вида.

Мы можем создать множество changelist’ов. Они не обязательно создаются из задачи, можно создать свой changelist. Я, например, создаю changelist NO для временных файлов, файлов своих настроек, декларативные скрипты для работы автодополнения, которые мне не нужно включать в коммит.

Теперь при коммите будут выбраны только файлы, которые находяться в выбранном changelist’е.

Откройте в разделе changes (alt+9 или ⌘+9) вкладку Local. Тут показаны изменённые файлы, сгруппированные по changelist’ам. Здесь файлы можно перемещать между changelist’ами.

phpstorm-changelist

Кликнув правой кнопкой по changelist, вы можете изменить его комментарий. Этот комментарий потом автоматически вставиться в описание коммита.

Для запуска сборки проекта в TeamCity из PhpStorm нужно поставить соответсвующий плагин.

Получилась вот такая схема работы. Ребята из JetBrains очень крутые: развивают продукт очень стремительно, вводять очень удобные фишки. Кто ещё не пробывал PhpStorm – обязательно попробуйте. А на него перешёл с Netbeans и нисколько не жалею.

]]>
<![CDATA[Yii: используем кэш в CActiveDataProvider]]> 2011-10-17T18:48:00+04:00 http://www.zagirov.name/yii-cache-cactivedataprovider Репост моей заметки из wiki: http://www.yiiframework.com/wiki/233/using-cache-in-cactivedataprovider/.

Первый параметр в конструкторе CActiveDataProvider может быть строковым значением с названием модели или экземпляр класса модели.

Поэтому можно использовать CActiveRecord::cache() для кэширования, но нужно установить значение 3 у третьего параметра, потому что мы должны закэшировать 2 запроса: получение количества записей и само получение записей.

Не забудьте использовать зависимости для принудительного протухания кэша.

1
2
3
4
5
6
7
8
9
10
11
$dependecy = new CDbCacheDependency('SELECT MAX(update_time) FROM {{post}}')

CActiveDataProvider(Post::model()->cache($duration, $dependecy, 2), array (
    'criteria' => array (
        'condition' => 'status = 1',
        'order' => 'DESC create_time',
    )
    'pagination' => array (
        'pageSize' => 20,
    )
));
]]>