- PVSM.RU - https://www.pvsm.ru -
В распределенных системах есть ряд фундаментальных проблем: эффективные распределенные транзакции, exactly-once обработка данных, точная синхронизация физических часов. Для решения последней проблемы были изобретены [1] разные виды логических часов [2].
Тем не менее, векторные часы [3] обладают неприятными свойствами: они вводят условную зависимость между событиями там, где ее нет, и теряют ее там, где она на самом деле есть.
Однако, можно придумать нечто более надежное — Kronos. В статье мы посмотрим на алгоритм учета причинно-следственной связи и его применение для построения Key-Value хранилища с распределенными транзакциями.

Как уже было сказано, с логическими часами есть ряд проблем:
Несуществующие зависимости возникают потому, что логические часы вводят полный порядок на событиях — т. е. любые про любые два события можно сказать, какое условно-раньше, а какие условно-позже. Подряд условный, поскольку точно определить взаимосвязь событий во времени определить невозможно, в том числе в силу Специальной Теории Относительности.
С другой стороны, логические часы учитывают взаимосвязь только через сообщения внутри системы. Если какие-то два события связаны, но вне системы, например, через пользователя (добавление товара в корзину в одной части системы -> оплата заказа), то логические часы могут упустить такую взаимосвязь.
К логическим часам невозможно получить доступ извне, а также сложно связать между собой несколько независимых компонент (распределенная файловая система, сервисы обработки запросов, аналитика).
В статье 2014 года Kronos: The Design and Implementation of an Event Ordering Service [4] предлагается решение — отдельностоящий сервис, который будет заниматься учетом причинно-следственных связей в событиях.
Основная абстракция внутри Kronos — событие, на которых вводится частичный порядок. Отношение причинно-следственной связи является транзитивным — т. е. если, например, мы знаем, что создание файла предшествует его изменению, а изменение предшествует удаление, можно сделать закономерный вывод, что создание произошло до удаления.
Минимальное API можно определить следующим набором методов:
| Метод | Результат | Комментарий |
|---|---|---|
create_event() |
e |
Создает новое событие в Kronos |
query_order([(e_1, e_2), ...]) |
[<-, concurrent, ->, ...] |
Для каждой пары из запроса возвращает направление причинно-следственной связи, или одновременность событий |
assign_order([(e_1, e_2, must), (e_3, e_4, prefer), ...]) |
OK/FAIL |
Для каждой пары из запроса устанавливает направление причинно-следственной связи |
acquire_ref(e) |
OK |
Увеличивает счетчик ссылок для данного события |
release_ref(e) |
OK |
Уменьшает счетчик ссылок для данного события |
Вполне логично, что в основе системы лежит ориентированный граф событий, с эффективным поиском в ширину для проверки взаимосвязи событий, механизмом устойчивости при отказе и сборкой мусора.
Как видно из API, запрос assign_order принимает также тип причинно-следственной связи: must или prefer. must соответствует строгим инвариантам — например, создание_объекта->удаление_объекта, prefer же может быть не применен, если он конфликтует с must связями. Пример использование prefer — запросы, которые пришли раньше, лучше обратыватывать раньше, но это не влияет на корректность.
В нашем случае, граф может быть большим, но события, для которых будут выполняться запросы проверки, как правило, будут располагаться близко. Поэтому необходимо выполнять BFS быстрее для таких случаев.
В стандартной реализации самое долгое место — инициализация массива посещенных вершин, которая всегда занимает время, равное количеству вершин в графе. Вместо этого можно использовать хэш-таблицу или применить другие трюки.
Как видно из таблицы, есть еще два метода: acquire_ref и release_ref.
Внутри Kronos для каждого события хранится счетчик ссылок. Пока какой-то сервис обрабатывает событие, или оставляет за собой возможность добавлять новые события, которые случаются после текущего, он хранит ссылку. Когда такая необходимость пропадает, сервис вызывает release_ref.
Kronos удалит событие, когда все условия будут выполнены:
Такой подход не ограничивает возможные запросы, но экономит память внутри Kronos.
Рассмотрим использование системы на примере Key-value хранилища с распределенными транзакциями.
Пусть есть несколько серверов, каждый сервер отвечает за диапазон ключей.
Каждой транзакции соответствует событие в Kronos. Для каждого ключа сервер должен хранить номер последней транзакции, в которой участвовал этот ключ. Клиент создает событие и рассылает его номер по всем серверам, чьи ключи затронуты данной транзакцей. Сервер пытается создать зависимость в Kronos между текущим номером транзакции и предыдущим событием, которое сохранено для этого ключа. Если создать зависимость не получается, то транзакция признается неуспешной (заметим, что до текущего момента никакакого взаимодействия с данными еще нет).
Если же все операция добавления зависимостей завершились успешно — это значит, что транзакция состоится и ее можно выполнять. Сервера узнают об этом от клиента и начинают выполнять части транзакции.
Заметим, что такие транзакции будут ACID [5]:
Реализация такого KV-хранилища действительно может быть эффективной. В оригинальной статье приводятся данные, что описаная реализация KV-хранилища превосходит по скорости транзакций реализацию на основе блокировок в 4 раза.
Более того, в сравнении с MongoDB система поверх Kronos уступает всего на 6%, при том, что в MongoDB не используются распределенные транзакции.
Тем не менее, эксплуатация Kronos несет ряд недостатков.
Тем не менее, описанная система позволяет гибко управлять причинно-следственной связью между событиями, обеспечивая предсказуемое соблюдение необходимых инвариантов.
Примерно такому мы в Школе GoTo учим студентов и школьников на направлении Распределенных Систем.
А еще есть Алгоритмы и Приложения [6], Прикладное программирование, Биоинформатика и Анализ Данных [7]
Приезжайте к нам на осеннюю школу [8] 27 октября — 4 ноября или зимнюю школу [9] в начале января.
А если вы уже не студент и не школьник — приезжайте преподавать [10].
Автор: Omrigan
Источник [11]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/295773
Ссылки в тексте:
[1] были изобретены: http://lamport.azurewebsites.net/pubs/time-clocks.pdf
[2] логических часов: https://www.cs.uic.edu/~ajayk/Chapter3.pdf
[3] векторные часы: http://zoo.cs.yale.edu/classes/cs426/2012/lab/bib/fidge88timestamps.pdf
[4] Kronos: The Design and Implementation of an Event Ordering Service: http://ayushdubey.com/pdf/kronos-eurosys14.pdf
[5] ACID: https://en.wikipedia.org/wiki/ACID_(computer_science)
[6] Алгоритмы и Приложения: https://goto.msk.ru/alg_program_summer2018/
[7] Анализ Данных: https://goto.msk.ru/ml_program_summer2018/
[8] осеннюю школу: https://goto.msk.ru/camp_autumn/
[9] зимнюю школу: https://docs.google.com/forms/d/e/1FAIpQLSe-7i8UNmZjC-nPQBhztDPTgWFhh7gkLDAZ-lWNGNvd8d59CA/viewform
[10] приезжайте преподавать: mailto:school@goto.msk.ru
[11] Источник: https://habr.com/post/426399/?utm_campaign=426399
Нажмите здесь для печати.