Stateful processing в YTsaurus Flow
Stateful обработка — это обработка событий, включающая в себя read-modify-write работу со стейтами, хранящимися в YTsaurus. Простейший пример: подсчёт статистик по входящим событиям в разрезе по ключам — нужно загрузить старое значение, обновить и записать обратно.
Модель работы со стейтом
Доступ к стейту внутри компьютейшена строится из трёх сущностей:
- Стейт (state) — пользовательские данные, привязанные к ключу и хранящиеся в динамической таблице YTsaurus.
- Клиент стейта (state client) — типизированный по типу стейта объект (
Client<TState>), который process function заводит по одному на каждый именованный стейт и инициализирует вInit. Через клиент происходит доступ к стейту по ключу. Бывает read-write (работает и с внутренним, и с внешним стейтом) и read-only (джойнер к внешнему стейту). - Аксессор (state accessor) — то, что клиент отдаёт для конкретного ключа (по входному сообщению, таймеру или явному ключу): представление стейта того же типа
TState. Аксессор ведёт себя как умный указатель на стейт: read-write-аксессор позволяет читать, изменять и очищать стейт, read-only — только читать.
Важно
Аксессор действителен только в пределах текущей эпохи. Его нельзя сохранять в полях process function или переиспользовать между эпохами — на каждой эпохе стейт нужно получать через клиент заново.
Типы стейтов
Internal State
Простейший способ работы со стейтом: таблицы создавать не нужно — Flow управляет ими автоматически. Данные подгружаются в начале эпохи и записываются при коммите. Доступ read-write — тем же клиентом, что и для внешнего стейта. Тип стейта может быть произвольным; единственное требование — сериализуемость в YSON (для сохранения между эпохами).
External State
Стейт в пользовательской динамической таблице. Таблицы создаются и управляются пользователем. Доступ read-write — тем же клиентом, что и для внутреннего стейта, но бэкендом выступает state manager (TSimpleExternalStateManager). Объявляется в спеке компьютейшена в top-level секции external_state_managers, реализация резолвится по external_state_manager_class_name. Поддерживает кэширование.
External State Joiner
Read-only доступ к внешним стейтам через join по ключу — через joiner (TSimpleExternalStateJoiner). Объявляется в спеке компьютейшена в top-level секции external_state_joiners (на одном уровне с external_state_managers), реализация резолвится по external_state_joiner_class_name. Поддерживает кэширование с TTL: загруженные стейты живут в общем StateCache и переподгружаются из YT только после истечения TTL либо вытеснения из кэша.
Важно
Таблицы, к которым даёт доступ read-write state manager, должны модифицироваться исключительно через него (или при остановленном пайплайне), так как он может использовать кэши.
Одна таблица — один писатель
Писать в таблицу внешнего стейта должен ровно один компьютейшен: записи из разных партиций и транзакций нарушают консистентность стейта. State manager владеет своей таблицей на запись: TSimpleExternalStateManager объявляет её как принадлежащую ему. Read-only доступ к стейту другого компьютейшена нужно делать через external state joiner (TSimpleExternalStateJoiner) либо сообщениями компьютейшену-писателю — джойнеры таблицу не захватывают. Это проверяется валидацией спеки по владению на запись: на каждую таблицу стейта должен приходиться ровно один писатель-владелец, а пайплайн, где одну таблицу захватывают на запись два менеджера, отклоняется с ошибкой State table <путь> is claimed for writing by both .... Read-only потребители (джойнеры) таблицу на запись не захватывают, поэтому могут разделять её с писателем-владельцем.
Примечание
Для любого стейта пустое значение соответствует отсутствию строки в таблице. Если после модификации стейт пуст, соответствующая строка удаляется. По умолчанию пустота и очистка стейта определяются автоматически (сравнение со значением по умолчанию); тип стейта может переопределить это поведение.
Хранение стейтов
Стейты хранятся в динамических таблицах YTsaurus. Простейший пример схемы:
|
name |
type |
sort_order |
expression |
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
|
Согласованность group_by_schema
Из соображений корректности и производительности строго рекомендуется для Computation в качестве group_by_schema использовать схему из первых ключевых колонок динтаблицы со стейтами (строго префикс ключевых колонок). Тогда:
- с событиями по одному ключу будет работать только одна партиция (корректность);
- одна партиция будет работать с ограниченным числом таблетов (производительность).
Пример group_by_schema, согласованного со схемой таблицы стейтов из примера выше:
|
name |
type |
expression |
|
|
|
|
|
|
|
StateCache
Flow предоставляет общий двухуровневый (uncompressed + compressed) LRU кэш для стейтов. Конфигурирование: /dynamic_spec/job_tracker/state_cache.
Источник: yt/yt/flow/library/cpp/common/spec.h
|
Параметр |
Описание |
|
|
Тип: NYT::NYTree::TSize |
|
|
Тип: NYT::NYTree::TSize |
Реализация на разных языках
- C++: клиент
TMutableStateKeyClient<TState>(read-write) илиTJoinedStateKeyClient<TState>(read-only) отдаёт аксессорTStateAccessor<TState>/TConstStateAccessor<TState>; один и тот же key-client работает и с внутренним, и с внешним стейтом. Подробнее → - Java: YsonStateAccessor, ProtoStateAccessor, ExternalStateAccessor. Подробнее →
- Python: ctx.state(), ctx.external_state(), ctx.proto_state(). Подробнее →
- Go:
flow.OpenYSONState,flow.OpenProtoState,flow.OpenExternalState,flow.OpenJoinedExternalStateоткрывают аксессор к стейту по ключу входа. Подробнее →