07 апреля, 2015

Организация a/b тестирования веб-проектов средставами nginx

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

Определимся с условиями нашей задачи:
  1.  На новую версию сервиса мы должны направить ≈20% всех пользователей
  2.  Есть ip-адреса клиентов, которые должны всенепременно попадать на старую версию
  3.  То же самое, но – всенепременно на новую.
Далее я приведу конфигурационный файл целиком, а после опишу принцип его действия.

geo $mytest_participant {
  default              1;
  10.1.1.1             0;
  192.168.45.2         2;
}

map $mytest_participant $resulting_upstream_name {
  0     "old_version_upstream";
  1     "distributed_upstream";
  2     "new_version_upstream";
}

upstream distributed_upstream {
  ip_hash;
  server 1.1.1.1 weight=8; # Old version server, 80% of clients
  server 2.2.2.2; # New version server
}

upstream old_version_upstream {
  server 1.1.1.1;
}

upstream new_version_upstream {
  server 2.2.2.2;
}

server {
  listen 80;

  server_name example.com;

  location / {
    proxy_pass http://$resulting_upstream_name;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For  $proxy_add_x_forwarded_for;
  }

}

Итак, первый блок (geo) описывает как раз распределение пользователей по апстримам.
ip-адреса (можно использовать CIDR-нотацию, разумеется), для которых выставлено значение 0, будут направляться на старую версию принудительно, 1 - распределяться по вышеуказанному алгоритму (80% – на старую, 20% – на новую версию), ну и 2 - принудительно на новую.

Именно в зависимости от этого значения и присваивается имя апстрима в следующем блоке (map).

Далее все просто: описываются апстримы для старой и новых версий, а также наш распределенный апстрим. Для последнего используется алгоритм балансировки ip_hash, благодаря которому одни и те же ip-адреса будут попадать строго на тот апстрим, на который попали впервые. Если нам нужно изменить процентное соотношение распределения по старой и новым версиям, то правим соответствующим образом параметр weight для старого апстрима.

Вот, собственно, и всё.

Нужно, однако, помнить, что ip_hash использует только первые три октета ip-адреса клиента, т.е. на ту или иную версию попадет сразу весь /24 сегмент. Однако обычно это не является чем-то критичным, тем более, что в блоке geo можно подкорректировать ситуацию для отдельных ip-адресов, при наличии необходимости.

28 октября, 2014

Ubuntu - обновленные библиотеки libicu с последними изменениями временных зон

Октябрьское обновление таймзон уже успело неплохо попить крови системных администраторов. Ведь во многих случаях недостаточно простого обновления tzdata, т.к. некоторые пакеты и библиотеки используют свою базу временных зон.

Наши разработчики используют Symfony2, и кое-где версия фреймворка ниже 2.6, а значит Symfony forms использует icu, и обновлять последний как-то мало кто торопится.
Поводом для написсания данного поста стал пост об этой же проблеме на хабре, но там рассматривалось решение проблемы для CentOS.

Мы используем преимущественно ubuntu, поэтому я сделал PPA с обновленными библиотеками (для 12.04 и 14.04).

Возьмем тестовый скрипт из той статьи на хабре и проверим на Ubuntu с «родным» icu:
<?php
$dateIn = '27.10.2014';
$tz = 'Europe/Moscow';
date_default_timezone_set($tz);
$intlDateFormatter = new \IntlDateFormatter('ru_RU', 2, -1, $tz, 1, 'dd.MM.yyyy');
$timestamp = $intlDateFormatter->parse($dateIn);

var_dump($intlDateFormatter->format($timestamp));
var_dump(date('d.m.Y', $timestamp));
И выполним его:
php ./icutz.php 
string(10) "27.10.2014"
string(10) "26.10.2014"
Всё плохо. Теперь подключим ppa и обновим библиотеку из него:
sudo add-apt-repository -y ppa:rzz/icu && sudo aptitude update
Обновляем libicu
Для Ubuntu 12.04: sudo aptitude safe-upgrade libicu48
Для Ubuntu 14.04: sudo aptitude safe-upgrade libicu52

Снова запускаем наш скрипт:
php ./icutz.php 
string(10) "27.10.2014"
string(10) "27.10.2014"
Готово! Но вообще, конечно, я рекомендую помимо всего прочего обновить версию Symfony, чтобы отвязаться от icu насовсем.

UPD, 12 Ноября 2014: в связи с поступившими просьбами сделать обновленный пакет icu 5.2 для 12.04, icu 4.8 переехал в отдельный PPA: ppa:rzz/icu48

23 октября, 2014

Mikrotik - автоматический бэкап на FTP или E-mail

Микротик - отличная штука, чего и говорить. Многие мои знакомые по моей рекомендации купили себе эти роутеры. Настройку их, конечно же, поручали мне, т.к. самое сложное, что они максимум видели до этого - это прошивка для роутеров ASUS "от энтузиастов" (к слову, тоже очень милая вещь, но сейчас не об этом), ну а по сравнению с ней процедура настройки микротика выглядит для таких людей каким-то диким колдунством. Некоторые из вышеуказанных знакомых так и пользуются роутером в том виде, в котором я им его настроил (либо просят внести какие-либо изменения меня, что я делаю удаленно), иные же пытаются вносить изменения в настройки и экспериментировать самостоятельно. Последнее весьма похвально, но иногда приводит к неприятным последствиям. Впрочем, даже если пользователь ничего не правит в настройках роутера, никто не защищен от ситуаций, из-за которых роутер может прийти в негодность (примеров можно придумать массу). Для минимизации возможных неприятных последствий я написал скрипт, который каждый день создает бэкап и заливает его на мой FTP-сервер.

Исходник скрипта на github



Первые четыре строки задают переменные, касающиеся того, что именно и как бэкапить:
saveUserDB - бэкапить ли базу User Manager'a
saveSysBackup - сохранять ли системный бэкап (аналогично тому, который создается по команде /system backup save в консоли)
encryptSysBackup - шифровать ли системный бэкап (поддерживается начиная с RouterOS 6.13)
saveRawExport - сохранять ли бэкап всей конфигурации в "читабельном" виде (.rsc-скрипт)

Следующие четыре строки описывают настройки для подключения к FTP и вряд ли нуждаются в комментировании.

По дополнительным просьбам сделал аналогичный скрипт, отсылающий бэкап на e-mail вместо заливки по FTP. Правда, ввиду ограничений RouterOS, каждый файл из бэкапа отправляется только отдельным письмом (т.е. получается до трех писем за один раз).

Исходник на github



Настройки аналогичны первому скрипту, разве что нужно задать в переменной mailTo адрес, на который будут отправляться бэкапы.
Если вы планируете использовать скрипт, отправляющий резервные копии на почту, обязательно сначала задайте настройки отправки почты (подробно можно почитать в Mikrotik Wiki)

После добавления скрипта (допустим, мы его назвали "backup-script"), необходимо добавить задание в планировщик:
/system scheduler add interval=1d name="Run backup" on-event=backup-script \
policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive start-date=jan/01/1970 start-time=01:00:00

05 сентября, 2014

О кавычках в VARCHAR и о том, как не надо делать в целом

На днях звонят мне наши веб-разработчики и говорят, мол MySQL тормозит адово, сил нет никаких терпеть, а при нескольких одновременных запросах и вовсе транзакции валятся по таймауту.

Привычный осмотр processlist'a показал вполне ожидаемую картину. В таблице чуть более 11 миллионов строк, и уже где-то полминуты висит простенький запрос типа UPDATE some_table SET act_status = 1 WHERE grp_id = 10885882802223, останавливаться он при этом не собирается, а в State красуется "Queried about 5120000 rows".

«Опять индекс не поставили!», - подумал Штирлиц и отправил разработчиков расставлять индексы, попросив попутно не отвлекать более подобными пустяками.

Однако, индекс был. Но всё равно фуллскан. Как так? А очень просто, как оказалось в итоге. В вышеуказанном запросе поле grp_id, если положиться на здравый смысл, должно иметь какой-нибудь целочисленный тип. Но здравый смысл - это для кого-то другого. Тот, кто делал эту прекрасную таблицу, решил что лучше будет, если это поле сделать VARCHAR.

Ну а вишенкой на торте стал самописный недо-ORM, который был написан или тем человеком, который сделал ту прекрасную таблицу, или его сообщником (сообщниками?), ну или сразу группой лиц с особой жестокостью.

Этот самый недо-ORM строил запросы следующим образом: если запрашиваемое значение включало только цифры, то кавычек в запросе не было. Ну а если цифры и буквы, то кавычки заботливо подставлялись.

В результате мы имели то, что имели (проблему с фуллсканом и адовые тормоза).

Смотрим внимательно. Что получалось и как быть не должно, если уж мы имеем дело с VARCHAR:


EXPLAIN select * from some_table where grp_id = 10885882802223 LIMIT 1;
+------+-------------+------------+------+---------------+------+---------+------+---------+-------------+
| id   | select_type | table      | type | possible_keys | key  | key_len | ref  | rows    | Extra       |
+------+-------------+------------+------+---------------+------+---------+------+---------+-------------+
|    1 | SIMPLE      | some_table | ALL  | grp           | NULL | NULL    | NULL | 7747809 | Using where |
+------+-------------+------------+------+---------------+------+---------+------+---------+-------------+


А вот как надо было:

EXPLAIN select * from some_table where grp_id = '10885882802223' LIMIT 1;
+------+-------------+------------+------+---------------+-------+---------+-------+------+-------------+
| id   | select_type | table      | type | possible_keys | key   | key_len | ref   | rows | Extra       |
+------+-------------+------------+------+---------------+-------+---------+-------+------+-------------+
|    1 | SIMPLE      | some_table | ref  | grp           | grp   | 99      | const |    1 | Using where |
+------+-------------+------------+------+---------------+-------+---------+-------+------+-------------+


Ситуация на самом деле идиотская донельзя и руки бы отрывать за такие изыски. Какие-нибудь еще выводы оставляю право читателю сделать самостоятельно.

18 сентября, 2013

Plesk 11.5, Horde и Integrity check violation

После обновления Plesk до 11.5, веб-интерфейс почты Horde начал себя безобразно вести. То и дело при входе вываливалась ошибка такого вида:

SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry for key 'PRIMARY'

KB Parallels говорит, что в этом случае нужно установить (либо переустановить) MU #14. Но меня это по какой-то причине не спасло. Вылечить этот косяк удалось путем отключения кэширования Horde, на скорости работы (с почтой через веб-интерфейс работает до 100 пользователей одновременно) никак не сказалось. Возможно, есть более изящное решение, ну а пока в файле /usr/share/psa-horde/config/conf.php находим строку

$conf['cache']['driver'] = 'Sql';

и убираем оттуда Sql, оставив пустые кавычки. Т.е. в итоге должно получиться

$conf['cache']['driver'] = '';

Лично мне помогло, больше меня эти ошибки не тревожили

27 июня, 2013

Реанимируем M-Audio Audiophile USB

Начиналось все тривиально: понадобился мне недавно дополнительный аудиоинтерфейс. Решено было достать с полки второй M-Audio Audiophile USB, который лежал и ждал своего часа, и пустить-таки его в дело. Интерфейс был оперативно найден, не менее оперативно был найден родной блок питания, который выдает не самые стандартные 9 вольт переменного тока на выходе, все это было вместе соединено и подключено к компьютеру иии...

Ничего. Вообще ничего не происходит. На передней панели светится индикатор питания, но компьютер никак не реагирует на подключенный девайс, а ведь когда я его убирал на полку, все было ок. После того, как я перепробовал несколько usb-портов и кабелей, а также попробовал воткнуть карточку в компьютер с Windows, а потом и с Linux, стало ясно, что карточка работать не желает и на контакт ни в какую не идет.

В интернете ничего толком об этом нет, разве что попадаются сообщения о том, что карточки эти иногда вот так "без объявления войны" подыхают. На сайте производителя лишь идиотский FAQ, который заканчивается в моем случае пунктом "Несите туда, где купили", а купил я эту железяку довольно давно, еще и в другой стране, в общем, вариант не подходит.

Сдергиваем кожух и начинаем тыкать в пациента веточкой тестером. Напряжения в норме, +15, -15, +5, +3.3, предохранители целы, даже кварц заводится, но по USB никаких событий не обнаруживается.

Однако, мое внимание привлекла небольшая микросхема в "кроватке". Собственно, это EEPROM данной железяки. Благодаря удачному стечению обстоятельств, у меня под рукой был еще один, заведомо рабочий, Audiophile USB. Переставляю микросхему EEPROM из рабочей карточки в нерабочую, и последняя внезапно оживает.

Разработчики карты не стали извращаться и применили вполне стандартную микруху 24C64, за что им спасибо, т.к. она замечательно шьется с программатора типа PICLIGHT-2, который аналогично моей звуковушке лежал у моего друга и тоже ждал того момента, когда он будет так нужен.


Дальше все было просто - скачали прошивку с рабочего экземпляра, залили в нерабочий, и все заработало сразу. Использовали программу IC-Prog 1.06, чтение и запись прошивки удались с первого раза без каких-либо трудностей.

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

M-Audio USB Audiophile EEPROM dump (23053 bytes)
md5: 77df950625700cf7980e0beb911d5311

21 апреля, 2013

ControllerMate, ремап регулировки громкости

Уже не первый год в качестве звуковой карты я использую уже не самый интересный по нынешним меркам интерфейс Audiophile USB от M-Audio. Он меня более, чем устраивает по всем статьям, поэтому при переходе на Mac я и не думал расставаться с этой железкой.

Однако, меня поджидал неприятный сюрприз: драйверы для этой карточки под OS X не используют системный микшер, следовательно громкость соответствующими клавишами на клавиатуре не регулируется, мол, в вашей карточке нечего крутить. На сайте M-Audio об этом указано, а в качестве решения предлагают крутить либо громкость в приложении, либо регулятор на передней панели аудиоинтерфейса.

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

И хотя я уже знал про существование такого чудо-приложения, как ControllerMate, провернуть описанный выше фокус удалось не сразу. Возможно, сказывается то, что с OS X я до этого толком не работал, а возможно - тот факт, что редко кто не из музыкантов и им сочувствующих пользуется внешней звуковой картой, а уж тем более с маком.

Итак, открываем ControllerMate.

Для начала я убрал действия для клавиш регулировки громкости, чтобы больше не выводилось уведомление о том, что регулировать нечего. На функционал не влияет, сделано сугубо в эстетических соображениях.

Нажимаем "Create Driver Configuration", выбираем нашу клавиатуру (в моем случае используется клавиатура от Logitech)". Хотя в моем случае почему-то выбрать надо было мышь. Это не совсем очевидно, согласен.


Находим пункты "Volume Increment" и "Volume Decrement", меняем поведение с Default на None.

После этого создаем новую группу для нашей клавиатуры (или пишем новые правила в существующую, если у вас уже есть пресеты в ControllerMate).

В качестве плеера я использую Swinsian,  и у него нет шорткатов для регулировки громкости. Конечно, их можно создать через System Preferences, но зачем плодить сущности? При нажатии на клавишу эмулировать нажатие других клавиш, чтобы отрегулировать громкость - это как-то громоздко. Правда, команд "сделать громче/тише" для AppleScript у него тоже нет, только возможность задания значений от 0 до 100. Возиться со счетчиками не хотелось, поэтому почему бы не эмулировать нажатие соответствующего пункта в меню плеера?

Создаем внутри группы пресетов для нашей клавиатуры еще одну группу, я назвал ее аналогично названию плеера,  указывем, что группа будет активна, когда запущен плеер, при этом не забываем справа от названия плеера поменять параметр на "Foreground or Background", иначе ничего не будет регулироваться, если окно плеера не является активным.


Внутри группы создаем новую страницу, добавляем новый блок:

В окне инспектора выбираем нужную нам клавишу (точнее, достаточно один раз на нее нажать и она выберется сама). После этого добавляем новый блок, на этот раз выбираем "AppleScript - AppleScript". Чтобы не плодить файлы, выбираем, что скрипт храниться будет тут же, текст скрипта для Swinsian будет таким:

tell application "System Events" to tell process "Swinsian"
         click menu item "Increase Volume" of menu "Controls" of menu bar 1
end tell

После этого связываем созданные блоки, проделываем аналогичную процедуру для кнопки увеличения громкости, заменив Decrease на Increase соответственно.

Кстати, на кнопки "следующий/предыдущий трек" у меня плеер тоже не реагировал, поэтому пришлось создать еще правила для них, но там все проще, благо у плеера есть команды смены трека.

Надеюсь, объяснение получилось не очень путаным.
Готовый файл конфигурации для ControllerMate можно скачать здесь.

А вообще, всем пользователям OS X, еще не знакомым с AppleScript и Automator рекомендую обратить внимание на два этих прекрасных инструмента, позволяющих автоматизировать, наверное, даже самые нетривиальные процедуры в системе.