Наверх ▲

Про HA кластеры и нашу реализацию в tarantool

Юрий Востриков Юрий Востриков Один из ведущих разработчиков Mail.Ru.

Юрий Востриков: Добрый день, меня зовут Юрий Востриков, я разработчик Mail.Ru Group. В начале доклада я объясню, что такое Tarantool/SilverBox для тех, кто этого не знает.

Так получилось, что на момент, когда нам понадобилась высокопроизводительная база данных, аналогов не было. Redis тогда еще тормозил и "падал". Нам пришлось сделать собственную СУБД. В процессе разработки мы поняли, что такие базы нужны в любой большой компании.

Мы создали некий фреймворк, который позволяет писать такие базы. В первую очередь, это те базы, которые хранят свои данные в оперативной памяти. Соответственно, фреймворк предоставляет механизм write-ahead-логирования, чтобы эти данные можно было каким-то образом на диск сохранять. Иначе кому нужна база, которая собственные данные не может запомнить?

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

Tarantool никаких ограничений на кортеж не накладывает. За исключением того, что в кортеже должны быть те поля, по которым есть индексы.

Для любой базы наступает такой момент, когда пользователь кладет в нее нужные данные и начинает беспокоиться об их сохранности. Естественно, такие данные нашлись и у нас. Они попали в Tarantool, а конкретно – в SilverBox. У нас возник вопрос: как же обеспечить надежность?

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

Об эту проблему "спотыкаются" многие. "Споткнулись" об нее и мы. Естественно, решение – использовать не резервные копии, а репликацию.

Немного о репликации

Какие варианты были у нас, когда мы выбирали решение?

Cинхронная или асинхронная репликация - вот какой выбор стоял перед нами. В случае синхронной репликации предполагается, что все реплики "видят" изменения одновременно с ведущим сервером (master). Это здорово, но это порождает такую проблему: нельзя поставить на реплики "железо" не столь мощное, как ведущий сервер, потому что производительность всего вашего репликационного кластера будет выравниваться по самой медленной реплике.

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


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

В случае использования команд самое известное решение – это MySQL. MySQL в своем базовом варианте передавал именно команды. Чем это хорошо? Тем, что любая команда заведомо меньше тех данных, которые она меняет. Вы экономите на объеме данных, которые попадут в репликацию.

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

В чем беда?

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

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

Если мы откатываем транзакции редко, это кажется мне более правильным решением, чем пересылка команд.

Третье, о чем я хочу сейчас сказать, это варианты схем "ведущий-ведущий" (англ. Master-Master) или "ведущий-ведомый" (англ. Master-Slave).

Вообще говоря, "ведущий-ведущий" выглядит привлекательнее, потому что вы можете распараллелить нагрузку по записи на несколько серверов. У вас нет выделенного сервера, в котором вы будете писать.

Но при репликации по схеме "ведущий-ведущий" возникают конфликты. Простой пример. Представьте, в реляционных базах данных есть таблица, в которой перечислены наши сотрудники: у них там зарплаты. У нас два ведущих сервера. На один из них приходит команда, что Васе Пупкину мы ставим зарплату 10 000 рублей, а на второй – 100 000. Вопрос: какая же зарплата будет в итоге (после того, как две реплики встретятся).

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

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

Что же мы выбрали в итоге для реализации в Tarantool?

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

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


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

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

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

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

Самое первое, о чем я хочу поговорить – законы, которые ограничивают наши способности к написанию таких систем.

Первый закон сформулировал Брюер. Это теорема Брюера, известная как CAP-теорема.

Немного терминологии

О чем, собственно, говорит теорема? Она говорит о согласованности, доступности и устойчивости к разделению.

Согласованность подразумевает следующее: если вы задаете запрос к любой части вашей распределенной системы, любая часть должна ответить корректно. Не получится так, что в одной половине у Васи зарплата 10 000 рублей, а во второй половине – 100 000.

Доступность подразумевает, что для любой несбоенной части есть обязательство, то есть она обязана ответить на запрос пользователя. Это не предполагает, что если сервер сломался, то он обязан отвечать. Естественно, это невозможно. Если он не сломан, то ответить он обязан.

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

Оно включает в себя как ситуацию "split brain" (у вас есть 3 машины в одном data-центре и 3 машины в другом – между ними канал не работает, и они между собой пообщаться не могут), так и потерю вполне конкретной машины. С точки зрения распределенной системы невозможно отличить – то ли это машина сломалась, то ли из нее сетевой провод выпал.


В Интернете часто говорят: "Выберите 2 из 3 – в этом суть теоремы Брюера". На самом деле, это не совсем так, потому что обычные API-сети, с которыми сталкивается большинство из нас, по своей природе асинхронны и не застрахованы от потерь пакетов.

По сути, за нас уже один пункт выбрали! Это пункт про устойчивость к разделению. Когда мы строим свою систему, мы фактически должны выбрать, что будет, если сеть теряет пакеты. Будет ли наша система доступной (она будет всегда отвечать – может быть, иногда неправильно) или же она будет консистентной (в случае потери пакетов она иногда не будет отвечать на вопросы).

Лично мое мнение: зачем отвечать неправильными данными? Испортить их мы всегда успеем, а в базе портить вообще нехорошо, поэтому мы в Mail.Ru будем выбирать системы, которые консистентны. Amazon, допустим, выбирает доступные системы: свой Dynamo.

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

Три класса ошибок

Существует три класса ошибок – ошибки без восстановления, с восстановлением и "византийские отказы". 

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

Ошибки с восстановлением – это те ошибки, с которыми мы встречаемся наиболее часто. Они более общие. Соответственно, они включают в себя отказы без восстановления. Предполагается, что сервер ушел в подкачку (англ. swapping) на полчаса, а потом снова к нам вернулся, как будто ничего не произошло. Соответственно, он потерял все транзакции за полчаса и пытается продолжить с того момента.

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

"Византийские отказы" называются так из-за одной задачки, которую сформулировал Лесли Лэмпорт (Leslie Lamport).

Немного отклонюсь от темы. Оказалось, это очень хитрый дядька: он еще сделал LaTeX. Я не в курсе, знаете вы про такую систему верстки или нет. Я был удивлен.

Итак, вернемся к "византийским отказам". В исходной задаче говорится, что есть несколько византийских генералов, и среди них находится предатель, который врет. "Византийский отказ" применительно к ИТ-системе значит, что части нашей распределенной системы могут "врать" друг другу.

Вы думаете, что такого не бывает? Нет, бывает. Например, у Amazon не так давно (года два назад) падало "облако". Они долго его чинили. Потом они смогли найти сбоенный компонент… У них был коммутатор (англ. switch), который иногда менял пару битов в сообщениях, причем умудрялся делать это так, что API-сумму не портил. Они долго это искали и чинили.

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

Финальный закон. Распределенная система = консенсус.

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

Соответственно, вариантов всего два. Или мы консенсуса не достигаем (наша система виснет и ничего не коммитит), или она портит данные. Довольно печальная перспектива! Но в целом есть возможность этого избежать, потому что ни CAP-теорема, ни FLP-результат ничего не говорят о вероятности таких событий. Контроль за вероятностью таких событий и есть тот компромисс, с которым мы можем жить.

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

Двухфазный коммит

Это двухфазный коммит. Как следует из названия, у него две фазы. По поводу поддерживаемости сразу хочу сказать, что многие системы из обычных баз данных его поддерживают. Например, PostgreSQL поддерживает. 

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

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

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

После того, как они закоммитили или "откатили", они сообщают о своем решении координатору. Он сообщает клиенту, получилось или не получилось.

Алгоритм, вообще говоря, работает, но у него есть серьезный недостаток. Даже "родовая травма", можно сказать. Возникнет проблема, если у нас случился одновременно сбой двух участников: координатора и одного участника.

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

К примеру, мы его запустили, и он опросил оставшихся. Но решение принять невозможно, потому что ничего неизвестно о голосе сломавшегося участника. Если это был голос "за" откат, то в целом неправильно делать коммит. О голосе "знает" также координатор, но он тоже сломался, поэтому ответить не может.

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

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

С этим можно бороться? Поможет трехфазный коммит!

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

Фаза называется "подготовкой к коммиту" - "prepare to commit" по-английски (она в центре выделена).

Как третья фаза позволяет разбить дилемму?

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

Если это так, значит, координатор точно пришел к 3-й стадии. Если он к ней пришел, значит, у него были все голоса (и все были "за"). Значит, мы можем смело закончить транзакцию и все закоммитить.

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

Решает ли такой алгоритм проблему консенсуса? Да, решает. Но, так как он никогда не блокируется, он иногда может портить данные. Это "иногда" случается, когда у нас ситуация "split brain". Потеряна связь между кластерами – в левой половине появился новый координатор, который принял решение на основании голосов только части участников.

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

Его сделал небезызвестный Лесли Лэмпорт. Решение называется "протокол Paxos". Он позволяет достигнуть консенсуса без потери данных.

В протоколе Paxos участников побольше – их три.

Предполагается, что "Proposer" – это примерно то же самое, что координатор. В кластере он должен быть один.

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

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

Участники типа "Learner" – это самая простая часть. После того, как они получают подтверждение от участника типа "Acceptor", они всего лишь дублируют информацию клиенту и ничего не "помнят", поэтому мы их больше обсуждать не будем.

Как выглядит базовый раунд протокола?

В отличие от двухфазного и трехфазного коммита, Paxos сразу предполагает, что в сети могут быть сбои. Он начинает неявную фазу восстановления. Сообщение "propose", посланное участником типа "Acceptor", или начинает новую фазу, или восстанавливает предыдущий сломавшийся раунд.

Отличается Paxos и тем, что отвечает участник типа "Acceptor" обратно участнику-координатору "Proposer". Если кворум ответил участнику типа "Acceptor" пустыми значениями (никто из них ничего никогда раньше не видел), то это новый раунд. Участник-координатор "Proposer" может "закоммитить" или "откатить" конкретную транзакцию. То есть предложить какое-то значение v. Если участник типа "Acceptor" когда-то это значение видел, то он обязан ответить обратно участнику-координатору "Proposer" этим самым значением. Таким образом, мы восстанавливаем предыдущую сбоенную фазу потерянного или "залипшего" координатора ("Proposer").

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

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

Как выглядит фаза восстановления?

Предположим, у нас есть 2 кворума. В одном кворуме сломался "Proposer". Он у меня выделен красной пунктирной линией. Как он сломался? Он сперва получил сообщение от клиента, попытался разослать его всем участникам типа "Acceptor", чтобы они узнали, о чем мы договариваемся. Кому-то успел разослать, кому-то нет. После этого он сломался.

Мы каким-то магическим образом выбрали нового участника-координатора "Proposer" в левой успешной части. Так как любые два кворума, которые предполагают, что они составляют больше половины, должны иметь общего участника, у нас этот участник в центре, "Acceptor" должен был видеть то самое значение, с которого протокол начался. Он успешно ответит этим сообщением обратно участнику-координатору "Proposer". Тот про него "узнает" и сможет донести свои знания до оставшейся части кластера.

Таким образом, Paxos обеспечивает надежность. Также хочется отметить, что непосредственно Paxos договаривается всего лишь про одно конкретное значение. В случае базы данных это довольно бесполезно: кому нужна база, которая может коммитить одну транзакцию? 

Естественное расширение протокола – это MultiPaxos. Он договаривается не об одном конкретном значении, а о цепочке.

Так как каждый участник типа "Acceptor" после получения значения от участника-координатора "Proposer" никогда про него не "забывает", мы получаем некую последовательность "незабываемых" значений, которые наш кластер "помнит". Даже если мы начнем новый раунд протокола для уже свершившегося консенсуса, все просто обменяются сообщениями. Увеличится метка времени, но значение не будет перевыбрано.

Таким образом, Paxos не теряет данные. Раз он не теряет данные, соответственно, если вы помните, FLP-результат говорит, что он должен иногда не приходить к консенсусу или делать это бесконечно долго. Такое действительно возможно. Беда возникает из-за того, что необходимо магическим образом  восстанавливать сбоенный "Proposer", если он сломался.

Протокол не портится, если координаторов в кластере будет два. Но если их будет два, то мы никогда не придем к консенсусу, так как каждый из участников-координаторов "Proposer" будет сбивать с толку участников типа "Acceptor".

Первый компромисс, который должен принять любой построитель систем на основе Paxos – это алгоритм, на основании которого он выбирает того участника-координатора "Proposer", который "общается" с клиентами, потому что он должен быть один. Если их будет несколько, протокол блокируется.

Факты о Paxos

Также хочется отметить, что относительно Paxos известны следующие доказанные факты. Он не боится асинхронных сетей. Соответственно, он не боится ошибок с восстановлением (англ. fail recover). Это наши наиболее частые ошибки. Они чаще всего приводят к порче данных.

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

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

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

В случае использования Paxos выделенной фазы восстановления нет, потому что протокол изначально предполагает возможность ошибки и всегда начинает с фазы восстановления. Повышение вероятности того, что код будет более надежен, и сокращение вероятности того, что вы допустите неуловимую ошибку в восстановлении, – мне это кажется довольно важным.

Как мы реализовали это на практике?

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

Например, если вы точно знаете, что в вашей сети никогда не бывает ошибок с восстановлением и вообще у вас не бывает ситуаций "split brain", вы можете смело делать двухфазный коммит – он простой. Например, PostgreSQL поддерживает двухфазный коммит, но задачу с выбором координатора они не реализовали – они сделали только обычного участника.

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

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

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


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

Идея простая. Предполагаем, что часы на машинах – это WorldClock, которые выдают обычное человеческое время, сильно от него не отклоняются. NTP или еще что-нибудь. Раз в n секунд запускаем перевыборы. Точнее, в n минус дельта. То есть, дельта – это тот зазор, который мы готовы терпеть. Если лидер есть, то он просто раз в это время переподтверждает свои полномочия. Например, конкретно в наших настройках – раз в 4 секунды, а период его полномочий – это 7 секунд.

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

Этой оптимизацией пользуются многие – она известная.

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

После того, как протокол Paxos выбрал конкретное значение, оно никогда не меняется. Вообще это очевидно: лог транзакции в прошлом у нас только для чтения. В настоящем мы там можем что-то изменять.

Соответственно, если мы знаем, что есть кусок транзакций, для которых выбрано время (кусок линейный), нам не надо выяснять результаты с помощью полноценного протокола. Мы можем "накатить" данные просто с помощью "log replay". Только для остатка, для которого такой информации у нас точно нет, придется войти в кластер и использовать Paxos для "доката", выполняя все эти раунды и теряя производительность на повторных запросах.

Как добиться повышения доступности? 

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

С другой стороны, мы вообще можем читать с любой ноды, но в случае ситуации "split brain" (слева у меня она  красным выделена) мы тоже можем с этой ноды читать, но гарантированно получим данные из прошлого. Она не имеет никакой информации о том, насколько они устарели.

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

Что у нас в итоге получилось?

Получилось следующее. Мы сделали механизм write-ahead-логирования, который использует внутри себя протокол Paxos для обеспечения надежности. В итоге получается синхронный кластер, к сожалению. Соответственно, не получается использовать разнородное "железо" – оно должно быть одинаковым. Но мы можем выбирать степень надежности.

Paxos – это кворум. Если кворум больше, то надежность выше, если кворум меньше, то надежность меньше.

Не сделано, к сожалению, следующее: мы не умеем хранить единую конфигурацию – конфигурация конкретно нашего кластера в самом Paxos не присутствует. Администраторы вынуждены редактировать текстовые файлы на нескольких машинах. Это совсем не здорово.

Естественное желание, имея такой надежный кластер, "пошардить" его, потому что утраивать конфигурацию везде и всегда для достижения надежности – это нездорово. Какие-то части могут быть более требовательными к надежности, какие-то менее требовательными. Но мы, к сожалению, этого пока не умеем. Это у нас в планах.

Проект Open Source

Также хочется отметить, что это проект Open Source. Его можно скачать или написать нам письмо. Мы обязательно на него ответим.

Конкретно Paxos и ветка, по которой ведется разработка, пока не опубликованы, но в ближайшее время, когда мы будем уверены в стабильности и нас устроят результаты (мы будем знать, что у вас не возникнет проблем), мы ее обязательно опубликуем.

Спасибо. Я слушаю ваши вопросы.

Вопросы и ответы

Реплика из зала: Вы сказали, что репликация происходит асинхронно. Возможна ситуация, когда я делаю коммит, и он проходит процедуру кворума асинхронно. То есть, я сделал коммит, нода упала, восстановилась, и ее коммит не был принят кворумом. Я правильно понял?

Юрий Востриков: Paxos синхронный (конкретно Paxos), а обычная репликация у нас асинхронная. 

Реплика из зала: То есть, это просто два разных способа репликации?

Юрий Востриков: Да.

Реплика из зала: Спасибо.

Реплика из зала: Ты говоришь, что у тебя лог. Вы пишете туда стейтменты, которые приходят, прямо как есть?

Юрий Востриков: Да, как есть.

Реплика из зала: Но если мы возьмем, например, Redis… Если ты исполняешь на нем инкремент, он в лог пишет не инкремент, а конкретное значение, которое получится… Если вдруг там что-то пойдет  не так, реплика будет исполнять SET, а не инкремент. Вы такого не делаете?

Юрий Востриков: Мы такого не делаем. Там есть неприятность, связанная с таким подходом. Допустим, у нас есть команда "splice" – это аналог команды "splice" в языке Perl, которая может редактировать часть поля. Если результирующее поле большое, а у нас оно может быть размером мегабайт, то вы обязаны записать в лог этот самый мегабайт. Сама команда маленькая. Соответственно, это неудобно.

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

Реплика из зала: Спасибо.

Юрий Востриков: Есть еще вопросы?

Реплика из зала: Скажите, зачем у вас используется Objective-C в разработке вашей – в Tarantool?

Юрий Востриков: Зачем… Это хорошая штука. Она сочетает в себе быстродействие Smalltalk и надежность "Си". Нам нравится! В целом Objective-C – маленький и перпендикулярный конкретно самому "Си". Он позволяет использовать объектность именно там, где она нужна, и также поддерживает дешевые исключения, их не нужно эмулировать самостоятельно.

Реплика из зала: Вы не анализировали, какова стоимость доставки сообщения в Paxos в терминах времени отклика? Двухфазный коммит – вам надо послать 4 сообщения, а это два времени отклика. В Paxos это как?

Юрий Востриков: Точно так же. В успешном раунде, если у нас не было ошибок и с учетом этой оптимизации заранее открытой первой стадии получается то же самое. Один в один.

Реплика из зала: Спасибо.

Реплика из зала: Подскажите, для каких проектов и какие данные вы используете для Tarantool? Что вы храните в Tarantool и в каких проектах это используется?

Юрий Востриков: Мне неудобно перечислять все проекты "Mail.Ru".

Реплика из зала: Ну, так – несколько основных.

Юрий Востриков: "Почта", "Мой Мир".

Реплика из зала: То есть, сообщения сами?

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

Если говорить про историю, ленту друзей, там задержка может быть больше. Если мы идем куда-то в прошлое – на год назад – пользователь, наверное, готов будет подождать. Если вы вызываете список друзей, то вы не готовы ждать 5 минут, пока что-нибудь на диске найдется. Поэтому для "горячих" данных в большинстве систем в том или ином виде используется Tarantool SilverBox.

Реплика из зала: Спасибо.

Реплика из зала: Если я правильно понял, у вас сейчас транзакция ограничена одним ключом. Есть ли планы делать транзакции с несколькими ключами? Если да, то как планируете решать проблему транзакций между "шардами"?

Юрий Востриков: Желание есть, а вот понимания пока нет.  

Реплика из зала: Подскажите, пожалуйста. У вас язык запросов для аналитики имеется? Какова производительность всей системы?

Юрий Востриков: Языка запросов для аналитики нет. Конкретно мы в таких случаях пользуемся вот чем. Так как базовый сервер позволяет делать сники состояния на диск (полный снимок сервера, который хранится на диске, и который потом можно парсить внешними средствами – не средствами самого сервера), то у нас используется именно этот механизм.

Почему? Сервер однопоточный, а аналитика предполагает, что нам нужно много вычислительных ресурсов на длительное время. В текущую архитектуру это довольно плохо укладывается.

Реплика из зала: Если вы очень много пишете и вдруг оказывается, что вы пишете совершенно не то, у вас есть возможность как-то изменить данные задним числом?

Юрий Востриков: Изменить задним числом – нет, но можно будет загрузиться не со всех логов. Можно сказать, "отрезать" неправильный кусок и загрузиться без него.

Реплика из зала: А если эти данные важны? Они просто немного некорректны. Их можно поправить?

Реплика из зала: Один час бились пакеты, но как-то очень интересным образом!

Юрий Востриков: Я думаю, легко это сделать никак не получится. С такой задачей не справится ни одна база данных, потому что…

Реплика из зала:  У нас была несколько иная проблема. Я вкратце контекст расскажу. У нас была задача – мы, скажем так, анализируем много трафика. У нас была задача очень много его писать по разным ключам, анализировать, и желательно в реальном времени.

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

Приходится это делать какими-то административными средствами, потому что выгрузить это в PostgreSQL и там написать обновление просто невозможно – она будет выгружаться полгода.

Юрий Востриков: Как я вижу – только понять, что вы испортили, и заново обновить этих "битых" пользователей. Самый простой способ.

Реплика из зала: Да. Просто проблема в том, что за одну минуту можно испортить очень много данных – там очень много сводных статистик и так далее. Не победим.

Юрий Востриков: Если вам хочется, мы можем пообщаться после доклада.

Видимо, все. Спасибо большое за внимание!

Комментарии

Нет ни одного комментария

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

Возможно, вам будет интересно:

Юрий Насретдинов

Юрий Насретдинов

Ведущий разработчик в Badoo, работаю в отделе «платформы». Один из основных разработчиков «облака» (системы для распределенного запуска cli-скриптов по расписанию). Также занимался deployment и системой переводов. В веб-разработке около 10 лет, из которых 3 года в Badoo.

Илья Агеев и Юрий Насретдинов (Badoo) освещают проблемы выкладки кода на серверы, с которыми сталкивается любой проект с количеством серверов больше одного.

Михаил Волович

Михаил Волович

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

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

Сергей Рыжиков

Сергей Рыжиков

Генеральный директор компании "1С-Битрикс"

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