company_banner

etcd 3.4.3: исследование надёжности и безопасности хранилища

Автор оригинала: Kyle Kingsbury
  • Перевод
Прим. перев.: Содержимое этой статьи не совсем типично для нашего блога. Однако, как многим известно, etcd находится в самом сердце Kubernetes, из-за чего данное исследование, проведённое независимым консультантом в области надёжности, оказалось интересным и в среде инженеров, эксплуатирующих данную систему. Кроме того, оно интересно в разрезе того, как Open Source-проекты, уже зарекомендовавшие себя в production, совершенствуются даже на таком, весьма «низком», уровне.



Хранилище пар «ключ-значение» (KV) etcd представляет собой распределённую базу данных, основанную на алгоритме консенсуса Raft. В ходе анализа, проведенного в 2014 году, мы обнаружили, что etcd 0.4.1 по умолчанию была подвержена так называемым stale reads (операциям чтения, возвращающим старое, неактуальное значение из-за запаздывания синхронизации — прим. перев.). Мы решили вернуться к etcd (в этот раз — к версии 3.4.3), чтобы снова детально оценить ее потенциал в области надежности и безопасности.

Мы обнаружили, что операции с парами «ключ-значение» строго сериализуемы и что процессы-наблюдатели (watches) доставляют каждое изменение ключа по порядку. Однако блокировки (locks) в etcd принципиально небезопасны, а связанные с ними риски усугубляются багом, в результате которого не проверяется актуальность lease после ожидания блокировки. Комментарий разработчиков etcd к нашему отчету вы можете прочитать в блоге проекта.

Спонсором исследования выступил фонд Cloud Native Computing Foundation (CNCF), входящий в The Linux Foundation. Оно проводилось в полном соответствии с этической политикой Jepsen.

1. Предыстория


KV-хранилище etcd — это распределённая система, предназначенная для использования в качестве основы для координации. Как Zookeeper и Consul, etcd хранит небольшие объемы редко обновляемых состояний (по умолчанию до 8 Гб) в виде карты key-value и обеспечивает строго сериализуемые чтение, запись и микротранзакции по всему хранилищу данных, а также примитивы координации вроде блокировок (locks), отслеживания (watches) и выбора лидера. Многие распределённые системы, такие как Kubernetes и OpenStack, используют etcd для хранения метаданных кластеров, для координации согласованных представлений о данных, выбора лидера и т.п.

В 2014 мы уже проводили оценку etcd 0.4.1. Тогда мы обнаружили, что по умолчанию оно подвержено stale reads из-за оптимизации. В то время, как в работе о принципах Raft обсуждается необходимость разбиения операций чтения на потоки и их пропускания через систему консенсуса для обеспечения жизнеспособности, etcd выполняло чтение на любом лидере локально, не проверяя наличие более актуального состояния на более новом лидере. Команда разработчиков etcd внедрила опциональный флаг кворума, а в API etcd версии 3.0 по умолчанию появилась линеаризуемость для всех операций кроме операций отслеживания.

API etcd 3.0 концентрируется на плоской карте KV, где ключи и значения представляют собой непрозрачные (opaque) байтовые массивы. С помощью диапазонных запросов можно имитировать иерархические ключи. Пользователи могут читать, писать и удалять ключи, а также отслеживать поток обновлений для одного ключа или диапазона ключей. Инструментарий etcd дополняют lease'ы (переменные объекты с ограниченным временем жизни, которые поддерживаются в активном состоянии heartbeat-запросами клиента), lock'и (выделенные именованные объекты, привязанные к lease'ам) и выбор лидеров.

В версии 3.0 etcd предлагает ограниченный транзакционный API для атомарных операций со множеством ключей. В этой модели транзакция представляет собой некое условное выражение с предикатом, истинной ветвью и ложной ветвью. В качестве предиката может выступать конъюнкция нескольких поключевых сравнений: равенство или различные неравенства, по версиям одного ключа, глобальной ревизии etcd или текущему значению ключа. Истинная и ложная ветви могут включать множественные операции чтения и записи; все они применяются атомарно в зависимости от результата оценки предиката.

1.1 Гарантии согласованности в документации


По состоянию на октябрь 2019-го в документации к API etcd заявляется, что «все вызовы API демонстрируют последовательную согласованность — самую сильную форму гарантии согласованности, доступную в распределённых системах». Это не так: последовательная согласованность строго слабее линеаризуемости, а линеаризуемость определённо достижима в распределённых системах. Далее в документации заявляется, что «в ходе операции чтения etcd не гарантирует передачу [самого недавнего (измеренного внешними часами по итогам завершения запроса)] доступного на любом представителе кластера значения». Это также слишком консервативное утверждение: если etcd обеспечивает линеаризуемость, операции чтения всегда связаны с самым последним зафиксированным состоянием в порядке линеаризации.

В документации также утверждается, что etcd гарантирует сериализуемую изоляцию: все операции (даже те, которые затрагивают несколько ключей) выполняются в некотором общем порядке. Авторы описывают сериализуемую изоляцию как «самый сильный уровень изоляции, доступный в распределённых системах». Это (в зависимости от того, что вы понимаете под «уровнем изоляции») также неверно; строгая сериализуемость сильнее простой сериализуемости, при этом первая также достижима в распределённых системах.

В документации говорится, что все операции (кроме отслеживания) в etcd линеаризуемы по умолчанию. При этом линеаризуемость определяется как согласованность со слабо синхронизированными глобальными часами. Следует отметить, что такое определение не только несовместимо с определением линеаризуемости Херлихи и Винга (Herlihy & Wing), но также подразумевает нарушение причинности: узлы с опережающим часами будут пытаться считывать результаты операций, которые ещё даже не начинались. Мы предполагаем, что etcd всё же не является машиной времени, и, поскольку в его основе лежит алгоритм Raft, должно применяться общепринятое определение линеаризуемости.

Поскольку KV-операции в etcd сериализуемы и линеаризуемы, мы считаем, что на самом деле etcd по умолчанию обеспечивает строгую сериализацию. Это имеет смысл, поскольку все ключи etcd находятся в единой машине состояний, а полную упорядоченность всех операции на этой машине состояний обеспечивает Raft. По сути, весь набор данных etcd представляет собой единый линеаризуемый объект.

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

2. Разработка теста


Для создания набора тестов мы воспользовались соответствующей библиотекой Jepsen. Анализу подверглась версия etcd 3.4.3 (самая свежая по состоянию на октябрь'19), работающая на кластерах Debian Stretch, состоящих из 5 узлов. Мы внедрили ряд неисправностей в эти кластеры, включая сетевые разделения, изолирующие отдельные узлы, разделение кластера на большинство и меньшинство, а также нетранзитивные разбиения с перекрывающимся большинством. «Роняли» и приостанавливали случайные подмножества узлов, а также умышленно выводили из строя лидеров. Вводили временные искажения до нескольких сотен секунд, как на многосекундных интервалах, так и на миллисекундных (быстрое «мерцание»). Поскольку etcd поддерживает динамическое изменение числа компонентов, мы случайным образом добавляли и удаляли узлы во время тестирования.

Тестовые нагрузки включали регистры, наборы и транзакционные тесты для проверки операций над KV, а также специализированные нагрузки для lock'ов и watch'ей.

2.1 Регистры


Для оценки надёжности etcd при KV-операциях был разработан тест с регистрами, в ходе которого производились случайные операции чтения, записи, сравнения/установки (compare-and-set) над единичными ключами. Оценку результатов проводили с помощью инструмента для проверки линеаризуемости Knossos с использованием модели регистра сравнения/установки и информации о версиях.

2.2 Наборы


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

2.3 Append-тест


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

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

2.4 Lock'и


Как координационный сервис, etcd обещает встроенную поддержку распределённой блокировки. Мы исследовали эти блокировки двумя способами. Сначала генерировали рандомизированные запросы lock и unlock, получая lease для каждого lock'а и оставляя его открытым с помощью встроенной в Java-клиент etcd функции keepalive до высвобождения. Мы провели проверку результатов в Knossos, чтобы понять, формируют ли они линеаризуемую реализацию lock-сервиса.

Для более практического теста (и получения количественной оценки частоты сбоя блокировок) мы использовали lock'и etcd для организации взаимного исключения при внесении обновлений в набор в in-memory и искали потерянные обновления в этом наборе. Этот тест позволил нам прямым образом подтвердить, могут ли системы, использующие etcd как мьютекс, безопасно обновлять внутреннее состояние.

Третий вариант lock-теста задействовал guard'ы на lease key для модификации набора, хранящегося в etcd.

2.5 Отслеживание


Чтобы проверить, что watch'и предоставляют информацию о каждом обновлении ключа, в рамках теста создавался один ключ и ему вслепую присваивались уникальные целочисленные значения. Тем временем клиенты совместно отслеживали этот ключ по нескольку секунд за раз. Каждый раз после инициации watch'а клиент начинал с той ревизии, на которой остановился в прошлый раз.

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

3. Результаты


3.1 Отслеживание с 0-ой ревизии


При отслеживании (watching) ключа клиенты могут указать начальную ревизию, которая является «опциональной ревизией, с которой (включительно) начинается слежение». Если пользователь желает увидеть каждую операцию с неким ключом, он может указать первую ревизию etcd. Что это за ревизия? Модель данных и глоссарий не дают ответа на этот вопрос; ревизии описываются как монотонно возрастающие 64-битные счетчики, но непонятно, начинает ли etcd с 0 или 1. Разумно предположить, что отсчет идет с нуля (так, на всякий случай).

Увы, это неправильно. Запрос 0-ой ревизии приводит к тому, что etcd начинает транслировать обновления, начиная с текущей ревизии на сервере плюс один, а не с самой первой. Запрос 1-ой ревизии выдаёт все изменения. Такое поведение нигде не задокументировано.

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

3.2 Мифические lock'и


Документация к API для lock'ов гласит, что заблокированный ключ «может использоваться совместно с транзакциями для гарантирования того, что обновления в etcd происходят только тогда, когда сохраняется принадлежность блокировки». Странно, но в ней не приводятся какие-либо гарантии для самих блокировок и не объясняется их предназначение.

Впрочем, в других материалах maintainer'ы etcd все же делятся информацией об использовании блокировок. Например, в анонсе о выходе etcd 3.2 описывается применение etcdctl для блокировки совместного изменения файла на диске. Кроме того, в issue на GitHub с вопросом о конкретном предназначении блокировок один из разработчиков etcd ответил следующее:

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

  1. включить блокировку в etcd;
  2. сделать что-то (опять же, необязательно связанное с базой etcd);
  3. отключить упомянутую выше блокировку.

Именно такой пример приводится в документации к etcdctl: блокировка использовалась для защиты команды put, но не привязывала ключ блокировки к обновлению.

Увы, это небезопасно, поскольку позволяет нескольким клиентам одновременно удерживать одну и ту же блокировку. Проблема усугубляется приостановкой процессов, падениями или разделениями сети, однако она также может возникать во вполне здоровых кластерах без каких-либо внешних сбоев. Например, в этом тестовом прогоне процесс номер 3 успешно устанавливает блокировку, а процесс 1 параллельно получает ту же самую блокировку ещё до того, как у процесса 3 появляется возможность её снять:



Сильнее всего нарушение мьютекса было заметно на lease'ах с коротким TTL: TTL в 1, 2 и 3 секунды были не в состоянии обеспечить взаимное исключение спустя всего несколько минут тестирования (даже в здоровых кластерах). Приостановки процессов и сетевые разбиения ещё быстрее приводили к проблемам.

В одном из вариантов нашего lock-теста мьютексы etcd использовались для защиты совместных обновлений набора целых чисел (как и предлагает документация etcd). Каждое обновление считывало текущее значение выборки in-memory, и, примерно через одну секунду, записывало эту коллекцию обратно с добавлением уникального элемента. При lease'ах с двухсекундным TTL, пяти параллельных процессах и приостановке процесса каждые пять секунд, мы смогли вызвать устойчивую потерю примерно 18 % подтвержденных обновлений.

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

Включение дополнительной проверки lease, а также выбор более длительных TTL и внимательная настройка выборных (election) таймаутов позволят снизить частоту возникновения этой проблемы. Однако полностью устранить нарушения мьютексов не получится, поскольку распределённые блокировки фундаментально небезопасны в асинхронных системах. Д-р Martin Kleppmann убедительно описывает это в своей статье о распределённых блокировках. По его словам, службы блокировки обязаны приносить в жертву правильность, чтобы поддерживать жизнеспособность в асинхронных системах: если процесс падает, контролируя блокировку, службе блокировки нужен некий способ, чтобы принудительно снять блокировку. Однако если процесс на самом деле не упал, а просто медленно работает или временно недоступен, снятие блокировки может привести к тому, что она будет удерживаться в нескольких местах одновременно.

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

Чтобы предотвратить эту проблему, необходимо полагаться на то, что сама система хранения будет поддерживать корректность транзакций или, если служба блокировки предоставляет такой механизм, использовать «заградительный» (fencing) токен, который будет включаться во все операции, производимые держателем блокировки. Он позволит гарантировать, что никакие операции предыдущего держателя блокировки внезапно не возникнут между операциями текущего владельца блокировки. Например, в службе блокировки Chubby от Google подобные токены зовутся sequencers. В etcd можно использовать ревизию ключа блокировки в качестве глобально упорядоченного заградительного токена.

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

Итоги работ в issues проекта:

4. Обсуждение


В наших тестах etcd 3.4.3 оправдал ожидания в отношении KV-операций: мы наблюдали строго сериализуемую согласованность операций чтения, записи и даже мульти-ключевых транзакций, несмотря на приостановки процессов, падения, манипуляции с часами и сетью, а также изменение числа участников кластера. Строго сериализуемое поведение реализовалось по умолчанию в операциях KV; выполнение чтений с установленным флагом serializable приводило к появлению stale reads (о чём и написано в документации).

Отслеживания (watches) работали корректно — по крайней мере, на единичных ключах. До тех пор, пока сжатия истории не уничтожали старые данные, watch'и успешно выдавали каждое обновление ключа.

Однако оказалось, что lock'и в etcd (как и все распределённые блокировки) не обеспечивают взаимное исключение. Различные процессы могут одновременно удерживать блокировку — даже в здоровых кластерах с идеально синхронизированными часами. В документации с API блокировок об этом ничего не было сказано, а представленные примеры lock'ов были небезопасными. Впрочем, часть проблем с блокировками должна была уйти после выпуска этого патча.

В результате нашего сотрудничества команда etcd внесла ряд поправок в документацию (они уже появились в GitHub и будут опубликованы в следующих версиях сайта проекта). Страничка API гарантий на GitHub теперь гласит, что по умолчанию etcd строго сериализуема, а утверждение о том, что последовательный и сериализуемый являются самыми сильными уровнями согласованности, доступными в распределенных системах, удалено. В отношении ревизий теперь указано, что начинать следует с единицы (1), хотя в документации к API по-прежнему ничего не говорится о том, что попытка начать с 0-ой ревизии приведет к «выдаче событий, произошедших после текущей ревизии плюс 1» вместо ожидаемой «выдачи всех событий». Документация о проблемах с безопасностью блокировок находится в стадии разработки.

Некоторые изменения документации, например, описание особого поведения etcd при попытке считывания, начиная с нулевой ревизии, всё ещё требуют внимания.

Как обычно, мы подчёркиваем, что Jepsen предпочитает экспериментальный подход к проверке безопасности: мы можем подтвердить наличие багов, но не их отсутствие. Значительные усилия прилагаются для поиска проблем, однако не можем доказать общую корректность etcd.

4.1 Рекомендации


Если вы используете lock'и в etcd, задумайтесь о том, нужны ли они вам для обеспечения безопасности или для простого повышения производительности путём вероятностного ограничения параллелизма. Блокировки etcd вполне можно использовать для повышения производительности, однако их применение для целей безопасности может быть рискованным.

В частности, если вы используете блокировку etcd для защиты общего ресурса вроде файла, базы данных или сервиса, этот ресурс должен гарантировать безопасности и без блокировок. Один из способов достичь этого — использовать монотонный заградительный токен. Им может выступать, например, ревизия etcd, связанная с текущим ключом удерживаемой блокировки. Общий ресурс должен гарантировать, что как только клиент использовал токен y для выполнения некоторой операции, любая операция с токеном x < y будет отвергаться. Такой подход не обеспечивает атомарность, однако он гарантирует, что операции в рамках блокировки выполняются по порядку, а не вперемешку.

Мы подозреваем, что обычные пользователи вряд ли столкнутся с этой проблемой. Но если вы всё же полагаетесь на чтение всех изменений из etcd, начиная с первой ревизии, помните, что в качестве параметра нужно передавать 1, а не 0. Наши опыты показывают, что нулевая ревизия в данном случае означает «текущая ревизия», а не «самая ранняя».

Наконец, lock'и etcd (как и все распределённые блокировки) вводят пользователей в заблуждение: у них может возникнуть желание использовать их как обычные блокировки, однако они будут весьма удивлены, когда поймут, что эти lock'и не обеспечивают взаимное исключение. В документации к API, публикации в блогах, issues на GitHub ничего не говорится об этом риске. Мы рекомендуем включить в документацию etcd информацию о том, что lock'и не обеспечивают взаимное исключение и привести примеры использования заградительных токенов для обновления состояний общих ресурсов вместо примеров, которые могут привести к потере обновлений.

4.2 Дальнейшие планы


Проект etcd уже несколько лет считается стабильным: алгоритм Raft в его основе хорошо себя зарекомендовал, API для KV-операций прост и понятен. Хотя некоторые дополнительные функции недавно получили новый API, его семантика относительно проста. Мы считаем, что уже достаточно изучили базовые команды вроде get и put, транзакции, блокировки и отслеживания. Однако существуют и другие тесты, которые стоило бы выполнить.

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

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

Работа была проведена при финансовой поддержке Cloud Native Computing Foundation, входящего в состав The Linux Foundation, и соответствует принципам этической политики Jepsen. Мы хотим поблагодарить команду etcd за ее помощь, и следующих её представителей в особенности: Chris Aniszczyk, Gyuho Lee, Xiang Li, Hitoshi Mitake, Jingyi Hu и Brandon Philips.

P.S. от переводчика


Читайте также в нашем блоге:

Флант
Специалисты по DevOps и Kubernetes

Комментарии 18

    –3

    я не очень понимаю зачем решать задачу на этом уровне.


    допустим у нас есть АСИНХРОННОЕ ключ-значение хранилище, данные до которого доезжают с лагами.


    задача: отдавать конфигурации приложений консистентно.


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


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


    Например у нас имеется конфиг


    [database]
    nshards = 2
    shard0 = 'host=1.2.3.4 port=5432 user=user password=password'
    shard1 = 'host=1.2.3.5 port=5432 user=user password=password'

    Очевидно, что тут число шардов и настройки отдельного шарда — взаимозависимые величины и хранить их надо в одном ключе KV хранилища.


    Всё что дальше — просто должно предоставлять удобный интерфейс над этим

      0

      А потом начинается — нужно разделение этого ключа по правам доступа для разных клиентов. Ограничение на размер ключа ( а если там 1КБ таких значений?). Консистентность в том же консуле можно реализовать через "косвенную адресацию". Что-то типа симлинка. Мы создаём под /key/rev_v1 пачку ключей. Потом мы хотим их обновить. Пишем в /key/rev_v2 новую пачку ключей, но приложение наше о них сейчас не знает. Как записали — меняем /key/revision, чтобы оно указывало, что приложение должно смотреть в поддерево rev_v2. Задача решена. Это, конечно, не спасает от ИзМЕНЕНИЯ ключей, которое несогласованное. Но можно попросту договориться на уровне организации, что мы ключи не меняем, а создаём новое поддерево (rev_v3, rev_v4 и и.т.д.). Проблема решена.

        –1
        А потом начинается — нужно разделение этого ключа по правам доступа для разных клиентов.

        права доступа — это метаданные про наш конфиг.
        то есть метаданные о метаданных.


        этим может заниматься админка.


        задача: конфиг переходит из состояния 1 в состояние 2 за N шагов. требуется на реплике конфига не видеть промежуточные 1..N значений. Так же требуется чтобы шаги делались разными пользователями с разными правами.


        Разные пользователи == нельзя открыть транзакцию и не закрывать пока они все доделают работу. Это в любом случае какое-то надстроечное решение.


        решение 1: выполнение всех шагов ещё до стадии "отправка в реплику". фиксация всех изменений и отправка в реплику нового значения конфига целиком (или блока).


        решение 2: отправка на реплику маркеров с версиями (то что предлагаете Вы)


        решение Х: ещё какой-то вариант...


        и вот мне кажется что решение 2 сложнее решения 1.


        Нет?

          0
          этим может заниматься админка.

          Админка чего, простите? У меня само приложение лезет за своей конфой в etcd/consul/zk. Но очевидно, что не каждый инстанс приложения и даже не каждый его компонент одинаков в своих правах.


          решение 1: выполнение всех шагов ещё до стадии "отправка в реплику". фиксация всех изменений и отправка в реплику нового значения конфига целиком (или блока).

          Это очень интересный вопрос. Потому что если etcd гарантирует то, что сначала происходит запись во все живые члены кластера, то это не совсем асинхронная KV схема с Ваших слов. А вполне синхронная — запись была? Была. Надежная? Да, вполне, в несколько узлов. И только после того, как большинство участников кластера подтвердили запись — мы увидим "новое" значение ключа. Или у Вас смещение терминологии и реплика — это клиент etcd? Ну, тогда извините — говорить не о чем

            0
            Это очень интересный вопрос. Потому что если etcd гарантирует то, что сначала происходит запись во все живые члены кластера

            пришла запись
            записали в определённое количество хостов
            в остальные хосты информация едет как обычный асинхрон


            И только после того, как большинство участников кластера подтвердили запись

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


            например у вас кластер о 300 нод.
            подтверждение запросят у 5 из 300. это будут считать гарантией того что данные не потеряются. в 295 поедет обычный асинхрон.


            по крайней мере я это так понимаю

              +1

              Етсд кластер на 300 нод никто в здравом уме делать не будет. Обычно делают 3,5. Я уж не говорю, что в условиях кроссдц там начинаются интересные (на самом деле нет) эффекты

                0
                Етсд кластер на 300 год никто в здравом уме делать не будет. Обычно делают 3,5. Я уж не говорю, что в условиях кроссдц там начинаются интересные (на самом деле нет) эффекты

                если у вас какой-либо мап-редьюс кластер — почему его не конфигурить централизовано?


                именно на больших размерах кластеров и начинают применять централизованные решения распространения конфигов

                  0

                  А это тут причем? Одно дело — кластер с конфигурацией. Другое дело — кластер с рабочими узлами. Посмотрите на кубернетес — количество мастер нод вполне ограничено разумными значениями. Никто не собирает кубернетес с количеством мастеров 500 нод. Зато 5 мастеров вполне тянут кластер на 1000-5000 рабочих узлов

                    0

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


                    именно между мастерами и играют в выборы, консенсус итп


                    а вот на нодах же тоже реплики стоят. и к нодам от мастера тоже едет с каким-то лагом.
                    я об этом и говорю: на 300 нод выборы и подтверждение записи в пяти.


                    Вы говорите 5 мастеров на 5000 нод. тоже хороший вариант. не противоречит тому что я говорю.

        +2

        Даже не знаю, что в это комментарии наиболее безграмотно:


        1. Предложения, начинающиеся с маленькой буквы.
        2. Отделение отдельных предложений абзацами.
        3. Использование выдуманных терминов: "АСИНХРОННОЕ ключ-значение хранилище", ""доехать" по репликации", "конфигфайле", "ключ-значение-хранилище".
        4. И наконец, отсутствие понимание исходной задачи, какие гарантии хотим предоставлять с точки зрения консистентности, доступности и отказоустойчивости.

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

          –3

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


          И наконец, отсутствие понимание исходной задачи

          как раз я и пытался сдвинуть к исходной задаче!


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


          я говорю что именно если исходить из понятия "конфиг" — как нечто целое. то теоретически можно держать его весь целиком (или блоками) в какой-то паре ключ-значение.


          то есть ключ — имя проекта (неделимого блока), значение — JSON, Ямл с конфигом (блока) целиком.


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


          По поводу оригинальной статьи. Ее автор — небезызвестный Aphyr

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


          претензии к маленькой букве вообще не принимаю. с телефона пишу. ставьте минусы в карму раз не нравится.

            +2
            исходная задача — обеспечить консистентность частей старого и нового конфига приложения между собой. При том что едет на хост он частями.

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


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


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

              –3
              Если не использовать консенсус в качестве базы, то можно легко показать (и Aphyr это не раз демонстрировал), что данные разъезжаются, реплики будут содержать разные данные

              и если использовать консенсус то данные тоже разъезжаются. В статье же написано что stale reads даже описан в документации. Консенсус — это решение про то что данные гарантированно начнут разъезжаться по кластеру вне зависимости от того какие части кластера сейчас доступны, а какие временно нет. Консенсус — это не про то что кластер гарантированно консистентен.


              и я так понимаю тут дальше две проблемы


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

              и вот я говорил о второй проблеме.


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

                0

                У вас разная трактовка понятия "разъезжаются".
                Это и "данные распределяются по кластеру", и "данные неконсистентны". Вы уж определитесь, что в конкретном контексте

                  0

                  пока данные распределяются (== разъезжаются) по кластеру:


                  • узлы кластера неконсистентны друг другу (у кого-то новая конфигурация, у кого-то еще старая). это первая проблема


                  • и вторая проблема — что данные приезжают частями.



                  что не так?

                    0

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

                      0
                      Второе — вообще не понял, в чем проблема. Вы про атомарность обновления нескольких связанных key?

                      да
                      и именно про это городятся маркеры с версиями, транзакции итп

                        0
                        Первое не является проблемой. Это решается, например, тем, что мы всегда будем читать с "лидера" (я не про етсд, а вообще) — у него всегда будет актуальная копия данных.

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

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

        Самое читаемое