Skip to content

Instantly share code, notes, and snippets.

@mitallast
Last active April 19, 2017 12:54
Show Gist options
  • Select an option

  • Save mitallast/b47aada0df0e624dba9c6c6e65855ea6 to your computer and use it in GitHub Desktop.

Select an option

Save mitallast/b47aada0df0e624dba9c6c6e65855ea6 to your computer and use it in GitHub Desktop.
Clocks Are Bad

Clocks Are Bad, Or, Welcome to the Wonderful World of Distributed Systems

Недавний email tread в списке рассылки пользователей Riak выделил один из основных недостатков распределенных систем: согласованность часов.

Первое письмо:

Иногда, riak не сохраняет объект, который я пытаюсь сохранить.
Я запустил tcpdump на узле, получающем запрос, чтобы убедиться,
что он получает http-пакеты с правильным JSON от клиента.
Когда проблема возникает, узел фактически получает запрос
с правильным JSON.

Riak предназначен для устранения сбоев в работе серверов и сетей, не теряя при этом фиксированных записей, поэтому это привело к быстрой реакции инженеров Basho.

После некоторого обсуждения была раскрыта важная информация:

Еще одна вещь, которую стоит упомянуть, это записи, которые я упоминаю,
это обновления существующих объектов.
Объект существует, попытка записать обновление объекта, по-видимому,
получена узлом, но объект сохраняет его исходное значение.

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

Concurrent Updates

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

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

Смущены?

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

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

Итак, вернемся к нашей первоначальной проблеме с потерянными обновлениями:

Узлы были немного не синхронизированы (до 30 секунд, смотрите "почему ntp не работает"!). Это может случиться - лишь вопрос времени.

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

Taming the Beast

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

Background Reading

Некоторые из этих обсуждений требуют понимания siblings, векторных часов и связанной с ними тайны. Если вы хотите больше узнать об этом, более ранняя запись в блоге Basho "Понимание конфигурируемого поведения Riak: часть 1" обеспечивает достаточный контекст. (Вы можете найти ссылки в эпилоге к полной серии, но часть 1 охватывает необходимый фон для этого сообщения.)

Если вместо этого вы решите, что не хотите читать и не хотите разбираться с такими сложностями полностью, перейдите в раздел «Nitty Gritty» в «The Land of Milk and Honey».

Nitty Gritty

Векторные часы (Vector Clocks).

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

See our documentation on vector clocks for more information. And although the details are a bit dated, our blog post "Why Vector Clocks are Easy" makes for a nice overview of the concept.

Для получения дополнительной информации см. нашу документацию по векторным часам. И хотя детали немного устарели, наш блог-пост "Why Vector Clocks Easy" делает хороший обзор концепции.

Forcing the Last Write to Win

Довольно неочевидный подход - это сделать разрешение конфликта по умолчанию "last_write_wins".

As discussed in part 1 of the configurable behaviors blog series, there are two closely-related configuration parameters that determine how Riak approaches conflict resolution: allow_mult and last_write_wins.

The former indicates whether Riak should keep all conflicts for the client to resolve; the latter is our concern at the moment.

Как обсуждалось в первой части серии блога "configurable behaviors", существуют два тесно связанных параметра конфигурации, которые определяют, как Riak подходит к разрешению конфликтов: allow_mult и last_write_wins. Первое указывает, должен ли Riak разрешать все конфликты для клиента; второе интересует нас на данный момент.

Если allow_mult установлено в false, установка last_write_wins в значение true даст указание Riak всегда перезаписывать существующие объекты, игнорируя временные метки, хранящиеся в них.

Итак, номинально, это достигает того, что мы ранее подразумевали как невозможное: последняя запись действительно выигрывает, независимо от clock consistency.

Проблема в том, что мы просто немного изменили задачу. Да, все серверы, которые получают объект, будут вслепую записывать его, но любые серверы, которые не получают его из-за сетевого раздела или отказа сервера, по-прежнему сохраняют более старое значение, и в зависимости от clock consistency старшее значение все равно может выиграть, как только сеть или ошибка сервера исправлена.

В общем, если у вас возникнут проблемы с согласованностью данных, лучше всего, чтобы это было очевидно и легко обнаруживалось на этапах тестирования. Это «решение» сделало бы ситуацию намного труднее для распознавания перед production.

Stopping Last Write Wins

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

Как мы уже видели, Riak не совсем гений при разрешении противоречивых записей. Помимо проблем clock consistency, Riak рассматривает объекты как черные коробки и не знает бизнес-логики.

It’s almost always better to bite the bullet: instruct Riak to retain all conflicting updates as siblings (via allow_mult=true) and write your application to deal with them appropriately.

Лучше поручите Riak сохранять все конфликтующие обновления как siblings (через allow_mult = true) и написать свое приложение, чтобы иметь дело с ними соответствующим образом.

Примечание. Мы планируем изменить значение по умолчанию для allow_mult для true в Riak 2.0, но перед тем как принять какое-либо поведение, проверьте документацию и конфигурацию.

The Land of Milk and Honey

Distributed data types

Создание типов данных, которые могут выжить в сетевых разделах и самовосстанавливаться, уже давно является целью для наших инженеров. С Riak 1.4 Basho представил распределенные счетчики; С 2.0, Riak будет иметь более широкий набор распределенных типов данных, которые могут разрешать конфликты внутри, включая sets и maps.

Также можно определить такие типы данных Riak (известные формально как CRDT) на прикладном уровне. См. серию из двух частей в блоге "Index for Fun and for Profit and Indexing the Zombie Apocalypse With Riak" для получения дополнительной информации.

Strong Consistency

Кроме того, с 2.0, Riak будет включать в себя возможность назначения определенных данных как strongly consistent, что означает, что серверы, на которых хранится часть данных, должны будут согласовать любые обновления этих данных.

Как бы это ни было привлекательно, невозможно гарантировать strong consistency, не вводя накладных расходов на координацию и ограничивая способность Riak продолжать обрабатывать запросы, когда серверы или сети потерпели неудачу.

И не являются ли низкие задержки и высокая доступность причинами, по которым вы используете Riak?

The Silver Bullet: Immutability

Возникновение «big data» связано с возрождением интереса к функциональному программированию, который особенно хорошо подходит для обработки больших наборов данных. (См. «Dean Wampler’s Lambda Jam talk Copious Data» для интересного изложения этой идеи.)

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

Реляционная модель данных не предлагает поддержки для неизменяемых данных, но это мощная концепция. На первой конференции RICON Пэт Хелланд выступил с докладом под названием "Immutability Changes Everything" более подробно.

Хотя не обязательно верно, что immutability решает все проблемы распределенных систем, это отличное начало. Без обновлений данных конфликтов нет.

TL;DR

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

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

Riak предоставляет мощные инструменты для решения проблем, связанных с распределенными данными, но они должны быть использованы, чтобы быть полезными.

John Daily

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment