вторник, 19 февраля 2013 г.

Go + C = cgo

       Иногда возникает задача использовать какую-нибуть C библиотеку из Go. К примеру мне понадобилось использовать ImageMagick. Но это в общем-то применимо к любой библиотеке написанной на C. В моем случае уже существовала Go обертка вокруг ImageMagick - Canvas
но в ней не было нужного мне метода - поэтому пришлось написать его самому.
       Попробую в кратце описать как это сделать.  Если вы хорошо владеете английским, лучше пропустить мой эпос и сразу читать оригинал:
    http://golang.org/doc/articles/c_go_cgo.html
    http://golang.org/cmd/cgo/
Если в кратце - то в Go есть специальный псевдо пакет C. Соответсвенно для того чтобы обратится к какой нибуть функции, описанной в C библиотеке, вам нужно подключить .h файл в котором описана эта функция:
/*
#include
*/
import "C"

Последняя строчка говорит Go компилятору что в этом пакете будут вызовы сишных функций и что необходимо специальным образом обрабатывать комментарии начинающиеся с #. Поэтому комментарий #include будет обработан как подключение заголовочного файла, и все символы, объявленные в нем будут добавлены в талицу символов псевдомодуля C. После этого вы сможете вызывать таким образом:
C.fputs(...)

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

// #cgo CFLAGS: -DPNG_DEBUG=1
// #cgo LDFLAGS: -lpng
указать соответсвенно компилятору и линкеру где лежат соответсвующие подключаемые файлы и файлы библиотек.
Еще одна особенность связанная с подключение C библиотек состоит в том что они в отличии от обычных пакетов Go, которые линкуются статически, подключаемые библиотеки линкуются динамически. Соответственно нужно чтобы подключаемая библиотека была установлена на том сервере где вы планируете запускать бинарник.

Еще одна особенность состоит в том что вам придется как в C - самостоятельно управлять памятью. Go runtime не имеет не малейшего представления о памяти которая была выделена с помощью сишных функций или где-то в недрах  сишной библиотеки - и поэтому вам необходимо самим освободить ее.
      Но если быть внимательным и понимать что делаешь - возможность подключать сишные библиотеки дает очень большие возможности. У меня это вот уже с месяц работает в продакшене - и очень даже успешно.

среда, 13 февраля 2013 г.

Clientside optimization

    Классное видео от разработчиков Яндекс Карт о том как оптимизировать загрузку веб приложений:
     http://clubs.ya.ru/yasubbotnik/replies.xml?item_no=706
    Google Speed Tracer -  один из инструментов которые они используют. Правдо сложновато в нем с наскока разобратся.  

Redis EXPIRE

    Нужно чтобы Redis сервер и все сервера с которых к нему обращаются находились в одной временной зоне. Иначе EXPIRE (SETEX) не будет работать корректно:

redis web-d11:6379> TTL orderlock-1421615
(integer) -1
redis web-d11:6379> GET orderlock-1421615
"admin"
Как вы видите TTL уже истек, но операция GET все равно возвращает значение. Это говорит о том что внутри себя Redis хранит TTL в виде абсолютного значения. 
   Чтобы изменить часовой пояс сервера(ubuntu):
sudo dpkg-reconfigure tzdata

Supervisor

    В последней, на данный момент  версии supervisor(3.0b) есть очень досадная бага - при попытке перезагрузить конфигурацию сам supervisor падает:

yvasiyarov@web-d8:~$ supervisorctl reload
Restarted supervisord
yvasiyarov@web-d8:~$ supervisorctl reload
error: , [Errno 111] Connection refused: file: /usr/lib/python2.7/socket.py line: 224
Не смотря на то что supervisor сказал что все ок, он упал во время перезагрузки конфига. Что собственно и всплывает при повторном обращении к нему. Более подробно этот баг описан:

 https://github.com/Supervisor/supervisor/issues/121
    Проблема в том что логгер супервизора пытается записать данные в уже закрытый файл. Пока этот фикс не слит в мастер, можно обновится до предыдущей версии либо установить версию из репозитория:
     sudo pip install -I supervisor==3.0a12

воскресенье, 10 февраля 2013 г.

GlusterFS

     Не так давно я открыл для себя GlusterFS. Если бы я сделал это на пару месяцев раньше - я бы съэкономил себе туеву кучу времени и нервов. Возможно мои записки помогут кому-то избежать этой ошибки. 
     Дело в том что в жизни каждого успешного проекта наступает момент когда он ну никак уже не умещается на одном фронте - поставить второй сервер и раскатать на него исходники - не большая проблема. Но как быть с загружаемым контентом ? Ведь почти в каждом приложении пользователям нужно заливать какие-нибуть файлики - которые должны быть доступны на всех фронтах. Как с ними быть ?
     1. Можно закинуть rsync(или другой его клон) в крон, и синхронизировать  таким образом загружаемый контент. Но это накладывает кучу ограничений - чтобы загрузка контента все время шла на один сервер,  нужно довольно большое  время на синхронизацию файлом между серверами, и чем больше размер загружаемого контента - тем больше времени будет требоватся rsync. В общем костыль он и есть костыль.
     2. Можно обрабатывать 404 запросы, и в случае отсутсвия какого-то файла - загружать его с главного сервера и скармливать nginx-у через X-Accel-Redirect
     3. Можно положить все файлы на nfs раздел - как-то худо бедно оно будет работать. До поры до времени так сказать. Медленность nfs помоему всем известна.
     4. Можно настроить GlusterFS. Не знаю как в режиме распределенного хранилища - но в режиме репликации чтение с GlusterFS должно быть таким же быстрым как и с локальной файловой системы. 
     Если немного подробнее - то GlusterFS может работаь в нескольких режимах. 
1. Распределенный режим - когда один файл записывается на первую ноду, второй на вторую, третий на третью и так далее.
2. Режим репликации - когда каждая нода хранит весь набор файлов. В этом режиме файловая система становится немного медленней на запись - за то скорость на чтение не отличается от чтения с локальной FS (оно так и происходит по факту), плюс вы получаете отказоустойчивать в подарок.  
  3. Также возможны промежуточные варианты - но об этом читайте подробнее в документации :-)

суббота, 9 февраля 2013 г.

HTTPie

    Нашел классную замену стандартному curl - HTTPie В общем делает тоже самое что curl,  но все сделано для человека - понятные опции, подсветка синтаксиса в ответах и прочие плюшки. Написана на python, поэтому скорость думаю будет ниже чем у оригинала - но для отладки разного рода сервисов гораздо удобнее: 
    http get http://dev.butik.ru/some/fucking/ajax/backend
Установка также очень проста:
    easy_install httpie

пятница, 8 февраля 2013 г.

Redis Can't create socket: Too many open files

    Redis может легко и непринужденно упереться в ограничение по максимальному количеству файлов которые может открыть один процесс. Лечится так:
1. /etc/security/limits.conf добавляем:
* soft nofile 65535
* hard nofile 65535
2. В /etc/pam.d/common-session  и /etc/pam.d/common-session-noninteractive добавляем: 
session required pam_limits.so
3. Добавляем в /etc/init.d/redis-server
ulimit -n 65535
 
Проверяем вот так: 
redis-benchmark -c 1024 -I
Внимание - redis-benchmark нужно тоже запускать от пользователя redis - иначе аналогичное сообщение об ошибке будет генерироваться на стороне клиента

вторник, 5 февраля 2013 г.

Sphinx - threads VS prefork

   Сегодня завершил перенос Sphinx на отдельный сервер. Сравнивал производительность при использовании разных типов распаралеливания: threads VS prefork. Если коротко - prefork сосет и очень сильно. Возможно у меня руки не от туда растут - но при равных конфигах в режиме threads sphinx работает намного быстрее - я бы сказал на порядок. 
    Из минусов - threads воркеры постоянно падают - но так как watchlog включен по умолчанию - пока вы не заглянете в лог вы об этом можете и не узнать вообще - пока не заглянете в лог. Мне пришлось туда заглянуть после того как sphinx неожиданно упал. Как потом оказалось - один из крашей воркеров привел к перезапуску сфинкса, во время которого он не смог прочитать все причитающиеся бинлоги и счел за лучшее - тупо умереть.  При чем если прочитать логи то sphinx успешно читал потерянный бинлог файл  за 15 минут до этого. Такое впечатление что sphinx умер во время слияния бинлог файлов, так что файл бинлога был удален с диска - но в мета информация так и не была обновлена, в результате при перезапуске сфинкс пытался прочитать удаленный файл бинлога и умирал.
    В качестве временного решения отрубил запись бинлогов вообще - благо объем записи в RT индекс у меня не большой, и rt_flush_period + полная переиндексация раз в сутки меня пока вполне устраивает.