2014-08-24

О ZeroMQ

Ну вот я и добрался до мимимишной ØMQ.


Сразу отмазываюсь. Хороший наезд на ØMQ на CodeFest 2014 был вполне справедлив. ØMQ — это лишь транспорт, она не способна решить вопросы вашей сервисно-ориентированной архитектуры в одиночку. Мартин Сустрик, один из первоначальных авторов ØMQ, действительно форкнул её, и не один раз. Но мы все же взялись использовать эту чудесную библиотеку, ибо она хороша, и нам подходит.

ØMQ — это сокеты на стероидах. ØMQ — это очередь сообщений без очереди сообщений (ну ладно, с очередями в памяти, но точно без брокера). Позволю себе перевести «Сотню слов о ØMQ» из официального руководства:

ØMQ (также известная как ZeroMQ, 0MQ или zmq) выглядит как встраиваемая сетевая библиотека, но работает как фреймворк для многопоточного программирования. Она дает вам сокеты, которые атомарно передают сообщения через различные транспорты внутри процесса, между процессами, по TCP или мультикастом. Вы можете соединять N к N сокетов согласно таким паттернам как распределение сообщений, публикация и подписка, распределение задач, запрос-ответ. Она достаточно быстра, чтобы строить на ней кластерные продукты. Её асинхронная модель ввода-вывода дает вам масштабируемые многоядерные приложения, построенные из асинхронных задач, обрабатывающих сообщения. Она имеет API на многих языках и работает на большинстве операционных систем. ØMQ вышла из iMatix и она открыта под LGPLv3.

Ну и невозможно не перевести «Как оно начиналось» из того же руководства. (Это руководство полно подобного юмора, замечательное чтиво).

Мы взяли обычные TCP сокеты, примешали радиоактивные изотопы, украденные из секретного советского атомного исследовательского проекта, обработали космическими лучами из 1950-х годов, дали в руки обкуренному автору комиксов, откровенно дико помешанному покрывать вздутые мускулы спандексом. Да, ØMQ сокеты — это мироспасательные супергерои мира сетей.

fig1.png

Меня пока не сильно интересует асинхронная многопоточная модель ØMQ. Хотя обещания строить сложные системы из компонент, обменивающихся сообщениями, сначала из потоков одного процесса, потом разных процессов, потом процессов на разных узлах, объединенных сетью, я где-то уже слышал. Кажется, в Erlang. Может, это и есть главный недостаток ØMQ? В смешении сетевых вещей и работы с потоками.

Ну так чем отличается ØMQ сокет от TCP сокета?

Направление подключения не зависит от роли компонент. В TCP как правило клиент подключается к серверу: сервер слушает порт, а клиент подключается к нему. В ØMQ можно все наоборот: клиент слушает порт, а сервер подключается к нему. К тому же можно подключаться сразу к нескольким слушающим сокетам, тогда сообщения будут распределены между ними, чаще всего поочередно (round-robin). Поэтому для слушания выбираем не тот компонент, который сервер, а тот, чей сетевой адрес будет реже меняться или будет более известен.

PlantUML diagram

ØMQ сама заботится о TCP подключении и переподключении. Даже можно «подключиться» и начать посылать сообщения, когда TCP связности между узлами еще нет. Тут у нас возникают таки очереди в памяти. Впрочем, ØMQ предпочитает в случаях невозможности доставки сообщений (или переполнения очереди) молча уничтожать эти самые сообщения. Об этом нужно помнить. И, в общем, тут есть своя логика.

Кстати, сообщения. Если TCP «доставляет» потоки, то ØMQ доставляет сообщения. Это особенность и ограничение любой очереди сообщений. Сообщения доставляются атомарно. Это значит, что если на принимающей стороне API сообщило вам, что пришло сообщение, то это сообщение уже полностью присутствует в памяти этой самой принимающей стороны. А если в процессе передачи сообщения произошли какие-то проблемы, то принимающая сторона никогда об этом не узнает. Сообщение просто не дойдет. Гарантированной доставки нет. О гарантированной доставке нужно заботиться самостоятельно с помощью нумерации сообщений, дополнительных запросов, таймаутов и т.д и т.п.

ØMQ сообщения — просто наборы байт. Но ØMQ сообщения могут еще делиться на фрагменты. Фрагмент — это часть сообщения, как её выделила отправляющая сторона. Фрагменты — это просто удобный способ разбиения сообщения на части. В HTTP для разделения заголовков от тела запроса используется пустая строка. В ØMQ для подобного разделения не нужно изобретать специальных разделителей, достаточно использовать фрагменты. Внутри себя ØMQ использует фрагменты для добавления адреса отправителя к сообщению в ROUTER сокетах. Также на фрагментах можно строить протоколы.


Почему, собственно «нулевая очередь сообщений»? Потому что «обычные» очереди сообщений (на том же «проклятом» создателями AMQP) требуют выделенного брокера. Это такой компонент, который отвечает за хранение очередей и передачу сообщений. Который, для пущей надежности, еще и хранит все сообщения. Который имеет свои собственные проблемы с масштабируемостью. Который еще надо развернуть и сопровождать.

PlantUML diagram

Если в том же AMQP есть и сообщения, и очереди, и точки обмена, все как отдельные явные сущности, то в ØMQ есть только сообщения и сокеты. Сокеты бывают разных типов и эти типы должны использоваться совместно, чтобы получились определенные паттерны передачи сообщений.

Простейший и тупейший вариант — пара сокетов REQ (request) и REP (reply). Клиент посылает сообщение через REQ сокет и блокируется в ожидании ответа. Сервер блокируется в ожидании поступлении сообщения через REP сокет, получает сообщение и отсылает ответ через тот же сокет. Как уже упоминалось ранее, слушания и коннекты ортогональны к направлению этого диалога. А тупейший это вариант, потому что это единственный синхронный паттерн в ØMQ. Блокировка, завязанная на порядок поступления сообщений, может в грустных случаях длиться вечно.

fig2.png

Сокеты PUSH и PULL предназначены для параллельного пропихивания сообщений. Можно из одного PUSH слать сообщения нескольким PULL (поочередно, round-robin). А можно из нескольких PUSH собирать сообщения в один PULL.

fig5.png

Если нужно слать сообщения одновременно нескольким получателям нужны PUB (publish) и SUB (subscribe) сокеты. На SUB стороне нужно обязательно подписаться на интересующие сообщения. Подписаться можно на префикс сообщения. Т.е. указать некую последовательность байт, и на этот сокет будут приходить только сообщения, начинающиеся с этой последовательности байт. Можно подписаться на несколько префиксов. В частном случае можно указать пустой префикс и получать все сообщения. Ну а в PUB сообщения просто посылаются. И если нет ни одного подключенного и подписанного получателя, сообщение просто уничтожается. Но зато по сети пересылаются только те сообщения, в которых заинтересован данный подписчик.

fig4.png

Пара сокетов DEALER и ROUTER — это такой асинхронный вариант REQ и REP. Без тупых блокировок. Еще их можно соединять один ко многим. А ROUTER еще и может отсылать ответы конкретному получателю. Когда мы читаем сообщение из ROUTER сокета, к началу сообщения добавляется еще один фрейм, содержащий идентификатор (identity) отправителя сообщения. Этот идентификатор либо назначается ØMQ самостоятельно (но будет уникальным для каждого подключившегося к данному ROUTER сокету), либо отправитель (REQ или DEALER) может установить его явно. Идентификатор — обычный фрейм, т.е. произвольная последовательность байт.

И теперь когда мы отсылаем назад сообщение через ROUTER, мы должны добавить этот идентификатор первым фреймом сообщения. И сообщение дойдет не абы куда (как в других сокетах), а конкретному получателю (собственно, некоему конкретному отправителю ранее полученного через ROUTER сообщения). Если получателя с данным идентификатором нет (не подключен), то сообщение молча уничтожается (впрочем, при желании можно поймать тут ексепшен).

На роутере и дилере можно построить брокер, аналогичный выделенному брокеру «классических» очередей сообщений. Даже, при желании, нарисовать и сохранение сообщений.


Вот на роутерах и дилерах мы и построили одну маленькую подсистему. Есть несколько роутеров, которые перенаправляют сообщения. Типа масштабируемое ядро маршрутизации сообщений. Есть различные компоненты, которые одновременно подключаются ко всем этим роутерам. Эти компоненты выполняют определенные роли (о которых знают роутеры) и устанавливают явный идентификатор (identity), одинаковый для всех подключений к роутерам. Ну а роутеры, зная все идентификаторы и роли, и руководствуясь некоторыми правилами, маршрутизируют сообщения.

PlantUML diagram

Получилось вполне удобно и надежно. Еще раз, у нас уже были компоненты и выделены роли. Нам не хватало удобного транспорта. У REST слишком много накладных расходов, и через него плохо передавать уведомления. C голым TCP слишком много морок при асинхронном обмене, да и адресная доставка и переподключения требуют много ручной работы. В общем, пока ØMQ в роли универсального и мощного транспорта мне нравится.

2014-08-16

О тестировании NoSQL

Вот моя статья, опубликованная в майском номере журнала «Хакер». Её оригинальный авторский вариант. О том, как Тамтэк тестировал NoSQL.

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


Изучаем типичных представителей NoSQL и тестируем их производительность.

Все говорят про NoSQL. Все хотят использовать NoSQL. Действительно ли NoSQL так быстр? Омская компания «Тамтэк» провела серию тестов производительности четырех NoSQL БД. О том, как проходило тестирование и какие получились результаты, читайте далее.

Почему NoSQL?


Несколько лет назад оказалось, что SQL базы данных внезапно устарели. И начали появляться и множиться многочисленные NoSQL решения. NoSQL, потому что они отрицали SQL как язык запросов, и реляционную модель, как единственно верную модель хранения данных. Почему?

Во-первых, ради скорости. Парсинг и трансляция SQL запросов, работа оптимизатора, объединение таблиц и прочее сильно увеличивают время ответа. Если взять и выкинуть все эти слои, упростить запросы, читать прямо с диска сразу в сеть, а то и хранить все данные в оперативной памяти, то можно очень существенно выиграть в скорости. В задержках на обработку каждого запроса, и, соответственно, в количестве обрабатываемых запросов в секунду. Так появились key-value БД, самым типичным и широко известным представителем которых является memcached. Да, этот кэш, широко применяемый в веб-приложениях для ускорения доступа к данным, тоже является NoSQL.

Во-вторых, ради объема хранимых данных. Те самые Big Data. Когда данных становится так много, что они не помещаются ни в ОЗУ, ни на диск одной машины, поэтому приходится либо брать дорогущие системы хранения данных (по сути — общие сетевые диски), либо строить распределенные системы. Вот NoSQL решения и стали распределенными системами хранения больших массивов структурированных данных на кластерах дешевых машин. Возможно, первым тут был Google со своим BigTable. Им вдохновились разработчики Cassandra, HBase и некоторых прочих. Ну и большие данные нужно уметь обрабатывать. Привет, Hadoop и MapReduce.

В-третьих, ради хранения экзотических структур данных. Самой экзотической является граф. Математический граф. Может, кто-то из читателей пробовал хранить иерархические структуры (деревья) в реляционных БД и знает, насколько чудовищно неудобно делать запросы к дереву. А граф ведь — еще хуже. Самой известной графовой СУБД является Neo4j. Кроме графов встречаются еще документы. Чаще в формате JSON. Документы — это структурированные и агрегированные цельные кусочки данных. Цельные, а не размазанные по десятку таблиц. Данные денормализованы и объединены в документы, согласно тому, как они используются. Не плоские строки, а вложенные иерархичные документы. Некоторые хитрые разработчики подметили, что такое слипание данных часто встречается в вебе и создали документо-ориентированную БД MongoDB.

История повторяется. Все это наблюдалось в шестидесятых и семидесятых годах двадцатого века. До реляционных БД существовали иерархические, объектные и прочие, и прочие. Однако хотелось стандартизации и появился SQL. И все серьезные СУБД, каждая из которых поддерживала свой собственный язык запросов и API, переключились на SQL. Язык запросов и реляционная модель стали стандартом. Любопытно, что сейчас тоже пытаются привить SQL к NoSQL, что приводит к созданию как оберток поверх существующих NoSQL, так и совершенно новых БД, которых называют NewSQL.

Типичные представители


Мы взяли четырех типичных представителей мира NoSQL, о которых наиболее часто спрашивали наши заказчики, и протестировали их.

Aerospike (в девичестве Citrusleaf). Очень быстрая key-value БД (а с версии 3.0 — еще и частично документо-ориентированная). В явном виде поддерживает SSD, использует их напрямую, без файловой системы, как блочные устройства. Создавалась для рынка онлайн рекламы (в том числе для так называемого RTB — Real Time Bidding), где требуются быстрые и большие кэши данных.

Couchbase. Возник как симбиоз проектов CouchDB и Membase, и является, таким образом, последователем memcached, т.е. key-value БД (хотя опять-таки, с версии 2.0 появилась поддержка JSON объектов-документов). От memcached в нем остались совместимость на уровне протокола доступа и стремление все хранить в ОЗУ. Отличается от memcached персистентностью и кластерностью, эти новые фичи написаны на Erlang.

Cassandra. Старейшая СУБД в списке. Возникла в недрах Facebook и была отпущена под крылышко Apache в 2008 году. Является колоночно-ориентированной БД, идейным наследником Google BigTable.

MongoDB. Весьма известная документо-ориентированная БД. Пожалуй, является родоначальником жанра документо-ориентированных NoSQL БД. Документы представляют собой JSON (точнее BSON) объекты. Создавалась для нужд веб-приложений.

Конфигурация


В распоряжении у нас было четыре серверных машинки. В каждой: восьмиядерный Xeon, 32 гигабайта ОЗУ, четыре интеловских SSD по 120 гигабайт каждый.

Для создания нагрузки на этот маленький кластер потребовалось восемь клиентских машин. По четырехядерному i5 и 4 гигабайта ОЗУ на каждой. Одного (и двух, и трех, и четырех...) клиентов оказалось недостаточно, чтобы загрузить кластер. Может показаться странным, но факт.

Все это шевелилось в одной гигабитной локальной сети. Пожалуй, было бы интереснее в десятигигабитной сети, но такого железа у нас не было. Интереснее, потому что, когда количество операций в секунду начинает измеряться сотнями тысяч, мы утыкаемся в сеть. При пропускной способности в гигабит в секунду (10^9 бит/c), сеть может пропустить килобайтных пакетов (~10^4 бит) лишь около 100000 (10^5) штук. Т.е. получаем лишь 100k операций в секунду. А нам вообще-то хотелось получить миллион ;)

Сетевые карты тоже имеют значение. Правильные серверные сетевухи имеют несколько каналов ввода-вывода, соответственно, каждый с собственным прерыванием. Вот только по умолчанию в Линукс все эти прерывания назначены на одно ядро процессора. Только ребята из Aerospike озаботились этой тонкостью и их скрипты настройки БД раскидывают прерывания сетевых карт по ядрам процессора. Посмотреть прерывания сетевых карт и то, как они распределены по ядрам процессора можно, например, такой командой: «cat /proc/interrupts | grep eth».

SSD — отдельная интересная тема. Мы хотели протестировать работу NoSQL БД именно на твердотельных накопителях, чтобы понять, действительно ли эти диски того стоят, т.е. дают хорошую производительность. Поэтому старались настроить SSD правильно.

SSD требуют действий, называемых непереводимым словом overprovisioning. Дело в том, что в SSD присутствует слой трансляции адресов. Адреса блоков, видимые операционной системе, совсем не соответствуют физическим блокам во флэш памяти. Как вы знаете, число циклов перезаписи у флэш памяти ограничено. К тому же операция записи состоит из двух этапов: стирание (часто — сразу нескольких блоков) и собственно запись. Поэтому, для обеспечения долговечности накопителя (равномерного износа) и хорошей скорости записи, контроллер диска чередует физические блоки памяти при записи. Когда операционная система пишет блок по какому-то адресу, физически запись происходит на некий чистый свободный блок памяти, а старый блок помечается как доступный для последующего (фонового) стирания. Для всех этих манипуляций контроллеру диска нужны свободные блоки, чем больше, тем лучше. Заполненный на 100% SSD может работать весьма медленно.

Свободные блоки могут получиться несколькими способами. Можно с помощью команды hdparm (с ключом '-N') указать количество секторов диска, видимых операционной системе. Остальное будет в полном распоряжении контроллера. Однако это работает не на всяком железе (в AWS EC2, например, не работает). Другой способ — оставить незанятое разделами место на диске (имеются ввиду разделы, создаваемые, например, fdisk). Контроллер достаточно умен, чтобы задействовать это место. Третий способ — использовать файловые системы и версии ядра, которые умеют сообщать контроллеру о свободных блоках. Это та самая команда TRIM. На нашем железе хватило hdparm, мы отдали на растерзание контроллеру 20% от общего объема дисков.

Для SSD важен также планировщик ввода-вывода. Это такая подсистема ядра, которая группирует и переупорядочивает операции ввода-вывода (в основном, записи на диск) с целью повысить эффективность. По умолчанию Линукс использует CFQ (Completely Fair Queuing), который старается переставить операции записи так, чтобы записать как можно больше блоков последовательно. Это хорошо для обычных вращающихся (так и говорят — spinning :) дисков, потому что для них скорость линейного доступа заметно выше доступа к случайным блокам (головки нужно перемещать). Но для SSD линейная и случайная запись — одинаково эффективны (теоретически), и работа CFQ только вносит лишние задержки. Поэтому для SSD дисков нужно включать другие планировщики, например NOOP, который просто выполняет команды ввода-вывода в том порядке, в каком они поступили. Переключить планировщик можно, например, такой командой: «echo noop > /sys/block/sda/queue/scheduler», где 'sda' — ваш диск. Справедливости ради стоит упомянуть, что свежие ядра сами умеют определять SSD накопители и включать для них правильный планировщик.

Любая СУБД любит интенсивно писать на диск. А также интенсивно читать. А ядро Линукс любит делать read-ahead, упреждающее чтение. В надежде, что, раз вы прочитали этот блок, вы захотите прочитать и несколько следующих блоков. Однако в случае СУБД, и особенно в случае случайного чтения (а этот как раз наш случай), этим надеждам не суждено сбыться. В результате имеем никому не нужное чтение и использование памяти. Разработчики MongoDB рекомендуют по возможности уменьшить значение read-ahead. Сделать это можно командой «blockdev --setra 8 /dev/sda», где 'sda' — ваш диск.

Любая СУБД любит открывать много много файлов. Поэтому необходимо заметно увеличить лимиты nofile (количество доступных файловых дескрипторов для пользователя) в файле /etc/security/limits.conf на значение сильно больше 4k.

Также возник интересный вопрос: как использовать четыре SSD? Если Aerospike просто подключает их как хранилища и как-то самостоятельно чередует доступ к дискам, то другие БД подразумевают, что у них есть лишь один каталог с данными. (В некоторых случаях можно указать и несколько каталогов, но это не подразумевает чередования данных между ними.) Пришлось создавать RAID 0 (с чередованием) с помощью утилиты mdadm. Я полагаю, что можно было бы поиграть с LVM, но производители СУБД описывают только использование mdadm.

Естественно, на всех машинах кластера (как серверных, так и клиентских) часы должны быть синхронизированы с помощью ntpd. ntpdate тут не годится, потому что требуется бОльшая точность синхронизации. Для всех распределенных систем жизненно важно, чтобы время между узлами было синхронизировано. Например, Cassandra и Aerospike хранят время изменения записи. И если на разных узлах кластера найдутся записи с разным таймстампом, то победит та запись, которая новее.

Сами NoSQL БД настраивались следующим образом. Бралась конфигурация «из коробки» и применялись все рекоммендации, описанные в документации и касающиеся достижения наибольшей производительности. В сложных случаях мы связывались с разработчиками БД. Чаще всего рекомендации касались подстроек под количество ядер и объем ОЗУ.

Проще всего настраивается Couchbase. У него есть веб-консоль. Достаточно запустить сервис на всех узлах кластера. Затем, на одном из узлов, создать bucket («корзину» для ключей-значений) и добавить другие узлы в кластер. Все через веб-интерфейс. Особо хитрых параметров настройки у него нет.

Aerospike и Cassandra настраиваются примерно одинаково. На каждом узле кластера нужно создать конфигурационный файл. Эти файлы почти идентичны для каждого узла. Затем запустить демонов. Если все хорошо, узлы сами соединятся в кластер. Нужно довольно хорошо разбираться в опциях конфигурационного файла. Тут очень важной является хорошая документация.

Сложнее всего с MongoDB. У других БД все узлы равнозначны. У Mongo — это не так. Мы хотели поставить все БД по возможности в одинаковые условия, и выставить у всех replication factor в 2. Это означает, что в кластере должно быть две копии данных, для надежности и скорости. В других БД replication factor — это лишь настройка хранилища данных (или «корзины», или «семейства колонок»). В MongoDB количество копий данных определяется структурой кластера. Грамотно настроить кластер MongoDB можно, только дважды прочтя официальную документацию, посвященную этому :) Говоря кратко, нам нужны «shards of replica-sets». Шарды (ну вы наверняка слышали термин «шардинг») — это подмножества всего набора данных, а также узлы кластера, где каждое подмножество будет хранится. Реплика-сеты — это термин MongoDB обозначающий набор узлов кластера, хранящих одинаковые копии данных. В реплика-сете есть главный узел, который выполняет операции записи, и вторичные узлы, на которые осуществляется репликация данных с главного узла. В случае сбоев роль главного узла может быть перенесена на другой узел реплика-сета. Для нашего случая из четырех серверов и желания хранить две копии данных получается, что нам нужно два шарда, каждый из которых представляет собой реплика-сет из двух серверов с данными. Кроме того, в каждый реплика-сет нужно добавить так называемого арбитра, который не хранит данные, а нужен для участия в выборах нового главного узла. Число узлов в реплика-сете, для правильных выборов, должно быть нечетным. Еще нужна маленькая конфигурационная БД, в которой будет хранится информация о шардах и о том, какие диапазоны данных на каком шарде хранятся. Технически это тоже MongoDB, только (по сравнению с основными данными) очень маленькая. Арбитры и конфигурационную БД мы разместили на клиентских машинах. И еще на каждом клиенте нужно запустить демона mongos (mongo switch), который будет обращаться к конфигурационной БД и маршрутизировать запросы от каждого клиента между шардами.

Развертывание MongoDB

Как тестировали


Тестировали мы с помощью YCSB (Yahoo! Cloud Serving Benchmark). Это специальный бенчмарк, выпущенный командой Yahoo! Research в 2010 году под лицензией Apache. Бенчмарк специально создан для тестирования NoSQL баз данных. И сейчас он является единственным и довольно популярным бенчмарком для NoSQL, фактически стандартом. Написан, кстати, на Java. Мы добавили к оригинальному YCSB драйвер для Aerospike, слегка обновили драйвер для MongoDB, а также несколько подшаманили с выводом результатов.

У каждой NoSQL БД свой уникальный способ представления данных и допустимых операций над ними. Поэтому YCSB пошел по пути максимального обобщения любых БД (включая и SQL).

Набор данных, которыми оперирует YCSB, — это ключ и значение. Ключ — это строка, в которую входит 64-битный хэш. Таким образом, сам YCSB, зная общее количество записей в БД, обращается к ним по целочисленному индексу, а для БД множество ключей выглядит вполне случайным. Значение — десяток полей случайных бинарных данных. По умолчанию YCSB генерирует записи килобайтного размера, но, как вы помните, в гигабитной сети это ограничивает нас лишь в 100k операций в секунду. Поэтому, в этих тестах, мы уменьшили размер одной записи до 100 байт.

Операции над этими данными YCSB осуществляет тоже простейшие: вставка новой записи с ключом и случайными данными, чтение записи по ключу, обновление записи по ключу. Никаких объединений таблиц (да и вообще, подразумевается лишь одна «таблица»). Никаких выборок по вторичным ключам. Никаких множественных выборок по условию (единственная проверка — совпадение первичного ключа). Это очень примитивно, но зато может быть произведено в любой БД.

Непосредственно перед тестированием БД нужно наполнить данными. Делается это самим YCSB. По сути — это нагрузка, состоящая лишь из операций вставки. Мы экспериментировали с двумя наборами данных. Первый гарантированно помещается в оперативную память узлов кластера, 50 миллионов записей, примерно 5 гигабайт чистых данных. Второй гарантированно не помещается в ОЗУ, 500 миллионов записей, примерно 50 гигабайт чистых данных.

Сам тест — выполнение определенного набора операций — производится под нагрузкой разного типа. Важным параметром является соотношение операций — сколько должно быть чтений, а сколько обновлений. Мы использовали два типа: интенсивная запись (Heavy Write) 50% чтений и 50% обновлений, и в основном чтение (Mostly Read) 95% чтений и 5% обновлений. Какую операцию выполнить каждый раз выбирается случайно, проценты определяют вероятность выбора операции.

YCSB может использовать различные алгоритмы для выбора записи (ключа) для выполнения операции. Это может быть равномерное распределение (любой ключ из всего множества данных может быть выбран с одинаковой вероятностью), экспоненциальное распределение (ключи «в начале» набора данных будут выбираться значительно чаще) и некоторые другие. Но типичным распределением команда Yahoo выбрала так называемое zipfian. Это равномерное распределение, в котором, однако, некоторые ключи (небольшой процент от общего количества ключей) выбираются значительно чаще, чем другие. Это симулирует популярные записи, скажем, в блогах.

Как работает YCSB

YCSB стартует с несколькими потоками, запуская цикл выполнения операций в каждом из них. Однако все это запускается на одной машине. Имея лишь четыре ядра на одной клиентской машине, довольно грустно пытаться запускать там более четырех потоков. Поэтому мы запускали YCSB на восьми клиентских машинах одновременно. Для автоматизации запуска мы использовали fabric и cron (точнее at). Небольшой скрипт на Python формирует необходимые команды для запуска YCSB на каждом клиенте, эти команды помещаются в очередь at на одно и то же время на ближайшую минуту в будущем на каждом клиенте. Потом срабатывает at и YCSB успешно (или не очень, если ошиблись в параметрах) запускается в одно и то же время на всех восьми клиентах. Чтобы собрать результаты (лог файлы YCSB), снова используется fabric.

Результаты


Итак, исходные результаты — это логи YCSB, с каждого клиента. Выглядят эти логи примерно так (показан финальный кусочек файла):

[READ], Operations, 1187363
[READ], Retries, 0
[READ], AverageLatency(us), 3876.5493619053314
[READ], MinLatency(us), 162
[READ], MaxLatency(us), 278190
[READ], 95thPercentileLatency(ms), 12
[READ], 99thPercentileLatency(ms), 22
[READ], Return=0, 1187363
[OVERALL], Reconnections, 0.0
[OVERALL], RunTime(ms), 303574.0
[OVERALL], Operations, 1249984.0
[OVERALL], Throughput(ops/sec), 4117.5594747903315

Как видите, здесь есть количество операций определенного типа (в данном примере — чтения), средняя, минимальная и максимальная задержки, задержка, в которую уложились 95% и 99% процентов операций, количество успешных операций (код возврата 0), общее время теста, общее количество всех операций и среднее количество операций в секунду. Нас больше всего интересует средняя задержка (AverageLatency) и количество операций в секунду (Throughput).

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

Скорость вставки в память

Скорость вставки на SSD

Производительность при интенсивной записи в памяти

Производительность при интенсивной записи на SSD

Производительность при 95% чтения в памяти

Производительность при 95% чтения на SSD

Выводы


NoSQL БД разделились на две группы: быстрые и медленные. Быстрыми, как, собственно, и ожидалось, оказались key-value БД. Aerospike и Couchbase сильно опережают соперников.

Aerospike действительно очень быстрая БД. И нам почти получилось дойти до миллиона операций в секунду (на данных в памяти). Aerospike весьма неплохо работает и на SSD, особенно если учитывать, что Aerospike в этом режиме не использует кэширование данных в памяти, а на каждый запрос обращается к диску. Значит, в Aerospike действительно можно поместить большое количество данных (пока хватит дисков, а не ОЗУ).

Couchbase быстр, но быстр только на операциях в памяти. На графиках с тестами SSD показана скорость работы Couchbase на объеме данных лишь чуть больше объема ОЗУ, лишь 200 миллионов записей. Это меньше 500 миллионов, с которыми тестировались другие БД. В Couchbase просто не удалось вставить больше записей, он отказывался вытеснять кэш данных из памяти на диск и прекращал запись (операции записи завершались с ошибками). Это хороший кэш, но лишь для данных, помещающихся в ОЗУ.

Cassandra — единственная БД, которая пишет быстрее, чем читает :) Это оттого, что запись в ней успешно завершается (в самом быстром варианте) сразу после записи в журнал (на диске). А вот чтение требует проверок, нескольких чтений с диска, выбора самой свежей записи. Cassandra — это надежный, и довольно быстрый, масштабируемый архив данных.

MongoDB довольно медленна на запись, но относительно быстра на чтение. Если данные (а точнее то, что называют working set — набор актуальных данных, к которым постоянно идет обращение) не помещаются в память, она сильно замедляется (а это именно то, что происходит при тестировании YCSB). Также нужно помнить, что у MongoDB существует глобальная блокировка на чтение/запись, что может доставить проблем при очень высокой нагрузке. В целом же MongoDB — хорошая БД для веба.



Кроме YCSB, тестировать производительность NoSQL БД можно с помощью, например, JMeter — популярного инструмента функционального и нагрузочного тестирования веб-приложений. Нужно лишь написать соответствующие классы для направления запросов не по HTTP, а в БД. Также ничто не мешает написать свой собственный набор тестов (не обязательно на Java) или расширить YCSB. Может быть, в ближайшие годы появятся новые инструменты, которые станут новым стандартом. Тем более, что NoSQL обзаводятся новыми фичами (вроде поддержки JSON документов, вторичных индексов или встроенного map-reduce), которые тоже нужно тестировать и сравнивать.



Типы NoSQL БД


Ключ-значение (key-value). Большая хэш-таблица, где допустимы только операции записи и чтения данных по ключу.

Колоночные (column). Таблицы, со строками и колонками. Вот только в отличие от SQL, количество колонок от строки к строке может быть переменным, а общее число колонок может измеряться миллиардами. Также каждая строка имеет уникальный ключ. Можно рассматривать такую структуру данных как хэш-таблицу хэш-таблицы, первым ключом является ключ строки, вторым — имя колонки. При поддержке вторичных индексов возможны выборки по значению в колонке, а не только по ключу строки.

Документо-ориентированные (document-oriented). Коллекции структурированных документов. Возможна выборка по различным полям документа, а также модификация частей документа. К этой же категории можно отнести поисковые движки, которые являются индексами, но, как правило, не хранят сами документы.

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

NewSQL


Если NoSQL решили отказаться от «тяжелого наследия» SQL, пересмотреть подходы к хранению данных и создали совершенно новые решения, то термином NewSQL нызвают движение по «возрождению» SQL. Взяв идеи из NoSQL, ребята воссоздали SQL базы на новом уровне. Например, в мире NewSQL часто встречаются БД с хранением данных в памяти, но с полноценными SQL запросами, объединениями таблиц и прочими привычными вещами. Чтобы все же хранить много данных, в эти БД встраивают механизмы шардинга.

К NewSQL причисляют: VoltDB, TokuDB, MemDB и других. Запомните эти имена, возможно, скоро о них тоже будут говорить на каждой ИТ конференции.

Fabric


Fabric — чудесная библиотека для удаленного выполнения команд. Написана на Python. Работает через SSH. Вы можете заливать и скачивать файлы, выполнять любые команды сразу на множестве узлов. Например, создав такой fabfile.py:

from fabric.api import *

env.roledefs = {
'server': ['192.168.1.6', '192.168.1.7', '192.168.1.8', '192.168.1.9'],
}

@roles('server')
def df():
run('df -h')

И выполнив команду «fab df», вы узнаете, сколько свободного места на дисках всех ваших серверов.

RTB


Словами Real Time Bidding называют способ показа рекламных баннеров на веб-страницах, при котором на каждый показ баннера осуществляется аукцион между поставщиками рекламы. Да, за сотню-другую миллисекунд, пока загружается веб-страница, сервер RTB опрашивает сервера рекламодателей и выбирает того, кто предложит больше денег за этот конкретный баннер, показываемый конкретному пользователю. Пользователь идентифицируется по cookie. Рекламодатели учитывают тематику сайта, место размещения баннера, угадывают по cookie, собранным из других источников, предпочтения пользователя и выставляют на торги наиболее подходящий баннер по наиболее подходящей цене. Для хранения соответствий между запросами с биржи и баннерами, и быстрого поиска по этому большому массиву данных часто используются NoSQL решения.

2014-08-09

О карьере

Поговорим о невозможности карьеры программиста. Программиста в узком смысле. А не тыжпрограммиста, как называют всех работников ИТ отрасли люди со стороны. Во всех-то ИТ возможностей для карьеры весьма много.

Что делает программист? Он пишет код. Он думает о том, как писать код. Его мало волнует зачемдля кого и к какому сроку нужно написать код. Писать код — это очень круто. Это — власть над машиной. Это — возможность войти в поток. Я уже давно не пишу код регулярно. Но я искренне наслаждаюсь теми редкими часами, когда мне удается непрерывно покодить.


Новорожденная личинка программиста ничего не знает о компьютерах. Потом происходит столкновение с вычислительной техникой. И человек на несколько лет погружается в компьютерные игры. Озарение происходит в момент понимания факта, что игру можно написать и самому. И какая-то игруля пишется на Бейсике (или теперь на Scratch, а то и под мобильную платформу?). Это и есть рождение программиста.

В школе программиста в лучшем случае учат основам алгоритмов. Заставляют рисовать блок-схемы и что-то кодить на том же Бейсике или Лого. В вузе учат классическим заплесневелым языкам программирования и заставляют реализовывать хитрые алгоритмы решения систем дифференциальных уравнений методом Рунге — Кутты. Хороший будущий программист за это время пишет пару игрулек, создает пяток домашних страничек для себя, одногруппников, и даже для строительной фирмы двоюродного дяди маминого брата (первый коммерческий проект!). Некоторые умудряются придумать свой ЯП и нескучные обои.

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

Программист растет. Изучает нужные языки и фреймворки. Кодит. Много кодит. Кодит на настоящем боевом проекте и становится джуниором. Начинает сам понимать, что собственно он кодит, и становится специалистом. Кодит так много, что начинает понимать, как надо кодить правильно, и становится сениором.



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

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

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

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

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


Конечно, можно расти и при этом продолжать быть программистом. Можно расти вглубь, становясь абсолютным экспертом в выбранной технологии. Можно расти вширь, изучать все новые языки, все новые фреймворки. ИТ развивается стремительно. Появляются все новые пространства для развития. Совершенствоваться можно бесконечно.

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

2014-08-01

О больнице

Угораздило меня попасть в больницу. На 10 с половиной дней. Ничего страшного, просто лишился аппендикса. И, в общем-то, не очень по нему скучаю. Осталось еще только зарастить дырки и набраться сил.


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

Сочетание боли в животе и температуры не вызывает других вопросов у скорой. Приезжают. Через часик. Запомните: болит (как угодно) живот и температура — сразу вызывайте скорую. Это может быть очень опасно. Всегда предоставляют выбор: остаешься дома, на свой страх и риск, или едешь в больницу, на свой страх и риск. С подозрением на аппендицит я решил все же поехать.

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

При этом никого это все не волнует, никто не бегает с выпученными глазами, никто не кричит, никто никуда не торопится. Ходишь сам? Сходи сдай кровь, помочись в баночку, на ЭКГ, на УЗИ... Потом приходи к нам сюда снова, решим, что с тобой делать. Через пару часов. Если окажется, что дело срочное — неспешно повезут на операцию. Если не срочное — предложат еще один выбор: идти домой, на свой страх и риск, или ложиться в больницу, на свой страх и риск. С подозрением на аппендицит я решил все же лечь.


Ночью понаставили капельниц. Утром на обходе назначили диагностическую лапароскопию, и, по результатам, возможно, аппендэктомию. А ближе к вечеру состоялось хирургическое действо.

Лапароскопия — это весело. По крайней мере диагностическая. Под местным наркозом протыкают брюшную стенку и начинают ревизию внутренностей. При этом стол, на котором лежишь, еще и наклоняют, чтобы все скатилось в нужный угол. И еще с тобой разговаривают, видимо, чтобы ты не скучал. Десять минут искали мой аппендикс. Нашли. Воспаленный. Тут же на этом же столе начали организовывать операцию.

Саму операцию, естественно, не помню. Договорились с анестезиологом, что, когда проснусь, он попросит меня поднять голову. И как только я подниму голову, он вытащит трубку из горла, через которую я буду дышать. Потом нацепили маску и сказали дышать глубоко. Последней мыслью, где-то на третьем вдохе, было: «Что-то наркоз не действует». А потом взгляд потерял фокусировку и все.

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

БСМП — это конвейер. Есть история болезни, её ведет лечащий врач. Есть еще заведующий отделением, который делает помпезные (с девочками-практикантками) обходы два раза в неделю. Есть назначения. Сестры, которые ставят капельницы и уколы, знают только, что, кому и когда сегодня вколоть. Сестры, которые делают перевязки, знают только, кого сегодня перевязать, а кому снять швы. Дамы, развозящие еду, знают только, кому можно, а кому нельзя есть. Кто приходит за анализами только знает, что у тебя надо взять кровь. Дежурная сестра знает только, кто в какой палате лежит и когда надо ставить градусники. Поэтому бесполезно негодовать по поводу того, что никто ничего не знает, назначают всякую фигню, лечить не умеют, грубые и невнимательные. Никто никому ничего не обязан кроме того, что прописано в назначениях. Хочешь подробностей или поговорить — ищи врача и пытай его. Но у врача тоже дел навалом: у него десятки таких как ты, а ему еще операции делать. Нормальный такой муравейник.

Это вам не доктор Хаус, который душу вытянет из пациента, чтобы понять, что с ним не так. Тут все ясно, что не так. И задача больницы — не уменьшить страдания, а спасти жизнь.

Время реакции муравейника — сутки. Врач осматривает тебя раз в сутки и меняет назначения. Если что-то изменится, то изменится только завтра. Суббота и воскресенье — выходной. Если в пятницу тебя не выписали, значит остаешься здесь минимум до понедельника. Впрочем, уколы ставят и в выходные :)

Отделение чистой хирургии — не потому что есть еще какая-то грязная (хотя есть гнойная) хирургия. А потому что это чисто хирургия, не нейрохирургия, не урология, не гинекология, не неврология, не кардиология...


После интенсивной терапии слегка потусовали по палатам. И собрались в одной палате все больные с проблемами желудочно-кишечного характера.

Много приступов холецистита и желчнокаменной болезни. Съешь так что-нибудь не то, или то, но не вовремя, и скрутит. День в реанимации. Три дня в стационаре. Холод, голод и покой. И много много капельниц. Если все ок, то выписывают. Это ж хирургия: если ничего резать не надо, тут выписывают быстро.

Много всяких осложнений на фоне приема алкоголя. Как прямых, вроде желудочного кровотечения. Так и косвенных, начинающихся со слов: «Выпил я и решил домой поехать».

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

Кстати, дальнобойщики (в том числе и бывшие) — очень интересные люди. Они очень общительны и добродушны. Им всегда есть, что рассказать. Лучше бы в палате были одни дальнобойщики, чем еще и алкоголики-матершинники :)


Вот так десять дней и куковал. Дочитал мангу про Наусику. Начитался Станислава Лема. Не прикасался к компьютеру. Не обдумал ни одной философской проблемы. Снял швы и выписался. Осталось еще чуть-чуть зарастить дырки и набраться сил.