oProxy release


Не прошло и полгода, как я решил разродиться на релиз прокси. Назовём его “1.1″.

Самой большой моей ошибкой в проектировании была идея использовать POSIX треды. Всё замечательно, но только до тех пор, пока активных соединений не становится больше сотни. Дело в том, что такие треды, по сути, являются почти что отдельными процессами: у них свой стек, их обрабатывает шедулер ядра и т.д. Единственное отличие — у них в пределах одного процесса единое адресное пространство. Так вот, всё это вызывало такие проблемы, как переполнение стека, нелинейность масштабирования и пр. В качестве альтернатиы рассматривалось два варианта:

  1. Библиотека Equeue. Знаменита тем, что предоставляет низкий уровень к управлению очередями обработки дескрипторов. Это и минус — работать с ней не слишком комфортно: “есть только очередь и события, всё остальное на ваше усмотрение”.
  2. Библиотека Lwt. По сути, то же самое, но выполнено совсем, совсем с другого бока. В интерфейсе предоставляется модель монад и псевдопотоков (отсюда название: LightWeight Threads). Быдлокодеру, навроде меня, достаточно лишь описать зависимости между состояниями: что делать, когда я получу данные из вон того сокета, и куда их записать потом.

Выбор пал на Lwt. А вот тут следует сказать несколько восторженных слов о типизации Хиндли — Милнера, используемой, в том числе, в Ocaml. Как я уже сказал, потоки POSIX при разработке выглядят практически как отдельные процессы. Создал поток, и забыл о нём, а он варится сам по себе. В Lwt же надо отдельно позаботиться о последствиях каждой операции IO. Прежде всего, я заменил все вызовы Unix.socket (создание сокета) — благо в коде почти все они были в моей обёртке и реально вызов встречался только в 2-3 местах — на Lwt_unix.socket. И понеслось. Типовычислялка компилятора сразу указала мне, где я, дурень, пытаюсь подсунуть в нормальную сокетную функцию параметр с каким-то странным типом Lwt_unix.file_descr. Следующая неделя прошла за чрезвычайно интеллектуальной работой. Компилятор говорил мне, что нужно сделать чтобы впасть в нирвану, а я всячески пытался её достичь. Таким образом, буквально через несколько дней негритянской работы, передо мной предстал код, который мало того, что не содержал ни одного вызова к стандартным функциям IO, так ещё и компилился.

Каково же было моё удивление, когда с первым запуском оно ещё и заработало! После недели тупой работы, перехода с философии “одно соединение == один самостоятельный псевдопроцесс” на “одно соединение == один конечный автомат”, после исправления тучи кода исключительно по указаниям компилятора! Кривенько, но заработало, результат я увидел. Последующая неделя ушла на исправление совсем новых багов, багов ещё со старой версии и дописывания новых приятных мелочей.

Итого, имеем. Кластер с определённым количеством клиентов (пока меньше, чем на одной машине в среднем, но уже существенно). Прокся заведует как балансировкой нагрузки между HTTP-узлами, так и между MySQL. Т.е. гоняются вполне реальные данные настоящих сайтов. На одно соединение (не запрос) HTTP в среднем уходит чуть меньше миллисекунды CPU (это и проксирование динамики, и отдача статики с помощью системного sendfile). На соединение MySQL уходит несколько больше — около 5 миллисекунд, хотя алгоритм там наоборот в целом проще. Связываю это с

  • sendfile() там не применишь, все проксируемые данные копируются в userspace
  • Имею богатый опыт разглядывания скриптов клиентов. А это совсем другая, удивительная, не поддающаяся научному изучению культура. У них принято сказать SELECT * на большую табличку, затем сложным алгоритмом с циклами тройной вложенности вычленить из многомегабайтного результата 2 строки, затем их отправить обратно в мускуль с целью join’а в непристойной позе с другой табличкой.

Памяти жрёт в рабочем режиме при текущих нагрузках стабильно 12-15MB (один мастер, 4 рабочих).

Планы (или мечты?) на будущее:

  • Пропатчить Lwt на предмет использования epoll() вместо select(). Чтобы в будущем не было мучительно больно обрабатывать 10000 одновременных соединений. Если, конечно, до этого дойдёт.
  • Написать некую глюкалу, которая сильно поможет в поиске причин высоких нагрузок как на сайтах, так и в БД. Пока мне это видится чем-то вроде: oproxyreport “select response_time-start_time, uri from http where host=’ispsystem.com’ and uri begins with ‘/ru/’ save to ‘/tmp/load.log’”. Если будет реализовано — сразу можно будет написать интерфейсик в ISPmanager, чтобы клиенты могли сами это делать.

Я всё сказал.

p.s. Нет, не всё :) С переходом на Lwt, обнаружилась ещё одна приятная особенность: портабельность. Например, во FreeBSD 7.1. поломали те самые POSIX треды, из-за чего мне пришлось отказаться от альфа-тестирования прокси на ней в своё время. Lwt же ни от чего не зависит. Select() есть везде, а не блокирующие сокеты криво реализованы разве что в винде.

, , , , ,

  1. #1 by yumaa on May 22nd, 2009

    забавно пишешь :) улыбнуло про “быдлокодера” и select * :)

  2. #2 by tuupic on May 24th, 2009

    “У них принято сказать SELECT * на большую табличку”
    Ты забыл сказать, что в тех табличках периодически индесов не бывает вообще

    • #3 by John Lepikhin on May 24th, 2009

      Это к делу не относится, от этого прокся больше CPU есть не будет. Да и индексы тоже далеко не всегда хорошо.

(will not be published)

  1. No trackbacks yet.