Введение
Решил проанализировать статью, описывающую некоторые интересные детали потоковой обработки ровно один раз: exactly-once. Дело в том, что некоторые авторы очень странно понимают термины. Разбор статьи как раз позволит прояснить многие детали более глубже, т.к. выявление нелогичностей и странностей позволяет более полноценно прочувствовать понятия и смысл.
Приступим.
Анализ
Начинается все очень даже неплохо:
Distributed event stream processing has become an increasingly hot topic in the area of Big Data. Notable Stream Processing Engines (SPEs) include Apache Storm, Apache Flink, Heron, Apache Kafka (Kafka Streams), and Apache Spark (Spark Streaming). One of the most notable and widely discussed features of SPEs is their processing semantics, with “exactly-once” being one of the most sought after and many SPEs claiming to provide “exactly-once” processing semantics.
Т.е., обработка данных крайне важна и т.п., а особенно обсуждаемая тема — это exactly-once. Обсудим же ее.
There exists a lot of misunderstanding and ambiguity, however, surrounding what exactly “exactly-once” is, what it entails, and what it really means when individual SPEs claim to provide it.
Действительно, очень важно понимать, что же это такое. Для этого было бы неплохо дать правильное определение перед пространными рассуждениями. Да и кто я такой, чтобы давать такие чертовски здравые советы?
I’ll discuss how “exactly-once” processing semantics differ across many popular SPEs and why “exactly-once” can be better described as effectively-once
Выдумывание новых терминов — это, безусловно, важная задача. Сам люблю это дело. Только для этого необходимо обоснование. Попробуем его найти.
Я не буду описывать очевидные вещи по типу направленных графов обработки и прочее. Читатели могут самостоятельно почитать оригинальную статью. Тем более, что для анализа эти детали малосущественны. Приведу лишь картинку:
Далее, идет описание семантик:
- At-most-once, т.е. не более одного раза. При кажущейся очевидности, такое поведение крайне сложно гарантировать при краевых сценариях по типу падений, нарушение сетевой связности и другое. Но для автора тут все просто:
- At-least-once, т.е. не менее одного раза. Схема более сложная. И граблей можно насобирать поболее:
- Exactly-once. Что же такое exactly-once?
Events are guaranteed to be processed “exactly once” by all operators in the stream application, even in the event of various failures.
Т.е. гарантия обработки exactly-once — это когда произошла обработка "ровно один раз".
Чувствуете мощность определения? Перефразирую: обработка один раз — это когда обработка происходит "один раз". Ну да, там еще говорится про то, что эта гарантия должна сохраняться и в случае отказов. Но для распределенных систем это вещь очевидная. И кавычки намекают на то, что что-то тут не так. Давать определения с кавычками, не объясняя, что это означает — признак глубокого и вдумчивого подхода.
Далее идет описание способов реализации такой семантики. И здесь я бы хотел остановиться более подробно.
Two popular mechanisms are typically used to achieve “exactly-once” processing semantics.
- Distributed snapshot/state checkpointing
- At-least-once event delivery plus message deduplication
Если первый механизм по поводу снепшотов и чекпоинтов вопросов не вызывает, ну кроме некоторых деталей типа эффективности, то со вторым есть небольшие проблемы, о которых автор умолчал.
Почему-то подразумевается, что обработчик может быть только детерминированным. В случае же недетерминированного обработчика каждый последующий перезапуск будет давать, вообще говоря, другие выходные значения и состояния, а значит дедупликация не будет работать, т.к. выходные значения будут другие. Таким образом, общий механизм будет гораздо сложнее, нежели описанный в статье. Или, говоря прямо, такой механизм некорректный.
Однако переходим к самому вкусному:
Is exactly-once really exactly-once?
Now let’s reexamine what the “exactly-once” processing semantics really guarantees to the end user. The label “exactly-once” is misleading in describing what is done exactly once.
Говорится о том, что пора бы уже пересмотреть это понятие, т.к. есть какие-то нестыковки.
Some might think that “exactly-once” describes the guarantee to event processing in which each event in the stream is processed only once. In reality, there is no SPE that can guarantee exactly-once processing. To guarantee that the user-defined logic in each operator only executes once per event is impossible in the face of arbitrary failures, because partial execution of user code is an ever-present possibility.
Дорогому автору стоит напомнить то, как работают современные процессоры. Каждый процессор при обработке выполняет большое количество параллельных стадий. Более того, есть ветвления, при которых процессор начинает выполнять не те действия если предсказатель переходов ошибся. В этом случае действия откатываются. Таким образом, обработчик один и тот же кусок кода, внезапно, может исполнить дважды, даже если никаких отказов не произошло!
Внимательный читатель тут же воскликнет: так ведь важен выхлоп, а не то, как это исполняется. Именно! Важно то, что произошло в результате, а не то, как на самом деле это происходило. Если результат такой, как если бы это происходило ровно один раз, так значит и произошло это ровно один раз. Не находите? А все остальное — это шелуха, не относящееся к делу. Системы сложны, и получаемые абстракции создают лишь иллюзию выполнения определенным образом. Нам кажется, что код выполняется последовательно, инструкция за инструкцией, что сначала идет чтение, потом запись, потом новая инструкция. Но это не так, все гораздо сложнее. И сущность правильных абстракций и заключается в том, чтобы поддерживать иллюзию простых и понятных гарантий, без ковыряния вглубь каждый раз, когда надо присвоить значения переменной.
И как раз вся проблема этой статьи и заключается в том, что exactly-once — это абстракция, позволяющая строить приложения, не задумываясь о дубликатах и потерянных значениях. Что все будет нормально, даже в случае падения. И изобретать для этого новые термины нет необходимости.
Пример же кода в статье наглядно демонстрирует отсутствие понимания того, как надо писать обработчики:
Map (Event event) {
Print "Event ID: " + event.getId()
Return event
}
Читателю предлагается самостоятельно переписать код, чтобы не повторять ошибок автора статьи.
So what does SPEs guarantee when they claim “exactly-once” processing semantics? If user logic cannot be guaranteed to be executed exactly once then what is executed exactly once? When SPEs claim “exactly-once” processing semantics, what they’re actually saying is that they can guarantee that updates to state managed by the SPE are committed only once to a durable backend store.
Пользователю не нужна гарантия физического исполнения кода. Зная, как работает процессор, легко заключить, что такое невозможно. Главное — логическое исполнение ровно один раз, как будто отказов и не было вовсе. Привлечение же понятий "коммита в хранилище данных" лишь усугубляет отсутствие понимания автором базовых вещей, т.к. существуют реализации подобной семантики без необходимости в наличии коммита.
Для более подробной информации можно кратко ознакомиться с моей статьей: Гетерогенная конкурентная обработка данных в реальном времени строго один раз.
In other words, the processing of an event can happen more than once but the effect of that processing is only reflected once in the durable backend state store.
То, что есть "durable backend state store" пользователю абсолютно фиолетово. Важен лишь эффект от обработки, т.е. консистентное состояние и выходные значения на всем промежутке выполнения обработки потоковых данных. Стоит отметить, что для некоторых задач нет необходимости иметь durable backend state store, а гарантировать exactly once было бы неплохо.
Here at Streamlio, we’ve decided that effectively-once is the best term for describing these processing semantics.
Типичный пример бестолкового ввода понятий: напишем некоторый пример и пространные рассуждения на целый абзац, а в конце допишем, что "мы так определяем это понятие". Точность и четкость определений вызывает по-настоящему яркий эмоциональный отклик.
Выводы
Непонимание сути абстракций приводит к искажению исходного смысла существующих понятий и последующем выдумыванием новых терминов на пустом месте.
[1] Exactly once is NOT exactly the same.
[2] Гетерогенная конкурентная обработка данных в реальном времени строго один раз.
Комментарии (17)
le1ic
12.08.2018 20:47В случае же недетерминированного обработчика каждый последующий перезапуск будет давать, вообще говоря, другие выходные значения и состояния, а значит дедупликация не будет работать, т.к. выходные значения будут другие.
дедупликация же на входе обработчика делается, а не на выходеgridem Автор
12.08.2018 20:57Дедупликация делается на входе последующего обработчика, который есть выход предыдущего.
andreystl
12.08.2018 22:13+1At-most-once, т.е. не более одного раза. При кажущейся очевидности, такое поведение крайне сложно гарантировать при краевых сценариях по типу падений, нарушение сетевой связности и другое
At-least-once, т.е. не менее одного раза. Схема более сложная. И граблей можно насобирать поболее
А почему так сложно гарантировать at most once? И что за грабли в at least once? Разве стратегии «никогда не ретраить»/«ретраить до упора» не обеспечивают поведения at most/at least once?
gridem Автор
12.08.2018 22:16Бывают нюансы, связанные со взаимодействием с внешними хранилищами. Но в нулевом приближении все просто.
andreystl
13.08.2018 23:07А какие нюансы могут быть с внешним хранилищем, которые что-либо усложняют?
At most once: если ни одна операция в системе не ретраится, как, при условии корректной реализации всех компонентов, могут появиться дубли? Я не говорю про систему без сбоев, только лишь про то, что в компонентах нет багов, которые приводят к дублям.
At least once: если каждый компонент ретраит запросы до победного конца, где могут появиться потери? При условии отсутствия бажных/чересчур оптимистично настроенных компонентов вроде стораджей, которые возвращают успех, никуда толком не сохранив.
Можно пример, который продемонстрировал бы, почему обеспечение этих поведений реализовать сложнее, чем описанные выше стратегии?
gridem Автор
14.08.2018 00:39Например, мы записываем во внешнюю базу данных, при этом у нас at-most-once. Если при этом не получилось записать, то эту запись мы сохраняем для того, чтобы позже повторить. Можно, конечно, тут же потерять эту запись, но нам бы не хотелось этого делать. После этого можно крешнуться прямо во время записи, а затем, т.к. эта запись осталась в нашем состоянии, то после восстановления снова попытаться ее записать.
bibmaster
13.08.2018 09:20Хотел бы я увидеть реализацию без коммита, т.е. без лога транзакций которая тот же print id сделает exactly once.
gridem Автор
13.08.2018 09:24- Реализация без коммита приведена в [2].
- Примеру с print id даже коммит не поможет.
bibmaster
13.08.2018 10:04Print не очень показательный. Пусть результат будет например сохраняемый в бд счётчик событий. Обеспечить идемпотентность может только фиксация факта учёта и увеличения счётчика в одной транзакции. Причём брокер действительно не может принципиально обеспечить exactly once. Он может предоставить возможность фиксации факта обработки. Но выборка события, обработка и фиксация обработки разнесены во времени. Так что даже в этом случае exactly once в случае ошибок превращается в at least once.
gridem Автор
13.08.2018 10:13Статью не читаю, комментарий пишу?
bibmaster
13.08.2018 10:31Смешались в кучу кони люди… Какой lock-free если в конце концов всё равно «теперь осталось лишь добавить взаимодействие с базой данных… tx.open()… tx.commit()».
И именно об этом и пишется в статье — «can guarantee that updates to state managed by the SPE are committed only once to a durable backend store».bibmaster
13.08.2018 11:49Используя «псеко»… :)
1 подход.
commit_snapshot(snapshot 1)
log_and_apply_transaction(snapshot 2, transaction)
log_and_apply_transaction(snapshot 2, transaction)
...
log_and_apply_transaction(snapshot 2, transaction)
commit_snapshot(snapshot 2)
...
restore:
rollback_all_transactions_since_last_commited_snapshot()
reply_all_transactions_since_last_commited_snapshot()
2 подход.
tx.open()
if tx.log_transaction(transaction.id): apply_transaction(transaction)
tx.commit()
restore: not required, all updates idempotent
Различия в том, что в первом случае лог можно писать только в рамках текущего окна событий. При этом в upstream периодически закидываются пинги, сигналы commit snapshot. О консистентности системы в целом можно судить по min(last commited snapshot) в sink.
А пример с print id автор приводит как раз в контексте того что print как user defined logic — не транзакционная операция, т.е. операция, exactly once выполнение которой в общем случае не может быть гарантировано. Например в случае возникновения ошибки внутри самой операции.gridem Автор
13.08.2018 21:33Я потерял нить диалога.
Хочется прояснить следующие вещи:
- Что это доказывает/опровергает?
- Каково конечное утверждение?
- "Обеспечить идемпотентность может только фиксация факта учёта и увеличения счётчика в одной транзакции." — откуда следует это утверждение?
- "Причём брокер действительно не может принципиально обеспечить exactly once." Что есть "брокер", и почему он "действительно не может принципиально обеспечить"? В статьях ничего такого не говорилось.
- "Так что даже в этом случае exactly once в случае ошибок превращается в at least once." Как это следует из предыдущего?
- "commit_snapshot(snapshot 1)". Что такое commit_snapshot и log_and_apply_transaction? Какое хранилище используется для хранения?
- Есть ли понимание, что tx.open()… tx.commit() использует другое хранилище, внешнее, по отношению к движку обработки данных?
Хочется понять проблематику и более подробное описание, что означет приведенный код.
igordata
Ты очень рассердился на автора той статьи, это понятно. Но кто он такой, что ты вообще его заметил? Чем его статья выделяется из кучи других ошибочных статей и мнений?
gridem Автор
Данная статья в топе по запросу "Exactly once". Статья в целом воспринимается как вполне толковая, и кто-то может подумать даже подумать, что в ней есть глубокий смысл. А рассмотрение деталей позволяет прояснить многие ключевые моменты.