Skip to main content

s-replicationkit — доставка событий между узлами

Кит родился из волны 7 платформы китов (Atlas #2718) и закрывает четыре дыры синхронизации, найденные в коде Atlas и подтверждённые комментариями самого этого кода.

Дыра Как было Что в ките
1. Курсор двигался по батчу целиком пропущенное событие проезжалось курсором и больше не переспрашивалось — «потеря событий была НЕВИДИМА» stream.apply_stream двигает курсор по непрерывной цепочке применённых, до первого пропущенного
2. Отказ доставки не различался невалидное навсегда событие гоняло пять попыток наравне с обрывом сети; обрыв сети на пятой попытке выбрасывал здоровое событие failures + queue.decide: постоянный отказ уходит сразу, временный держится до порога
3. Опрос базы вместо уведомителя долгий опрос ходил в базу дважды в секунду — плата бралась за тишину notify.Notifier + notify.wait_for_events
4. Курсор — метка времени пятьсот событий с одной меткой доставляли одно envelope.SequenceAllocator — монотонный номер

И пятое, ради чего всё затевалось: поток, не теряющий события на разрыве. Кадр несёт номер (id:), клиент возвращается с Last-Event-ID, сервер отдаёт хвост из stream.ReplayBuffer — а если хвост уже вытеснен, честно говорит об этом (truncated=True), а не молчит.

Состав

Модуль О чём
replicationkit.envelope конверт доставки и монотонная последовательность вместо метки времени
replicationkit.cursor курсор канала: в памяти, на диске, поверх чужого стора
replicationkit.stream применение с продвижением курсора и буфер повтора хвоста
replicationkit.queue судьба записи очереди после неудачной отправки
replicationkit.filequeue очередь конвертов на диске (переехала из telemetrykit.outbox)
replicationkit.failures временный отказ против постоянного
replicationkit.notify уведомитель вместо опроса базы
replicationkit.hold удержание долгоживущего канала: возврат после обрыва, стоп после отзыва

Зависимостей нет — только stdlib. Доставка стоит под всеми, и кит, который тянет за собой транспорт или ORM, навязывает их каждому потребителю.

Как это выглядит

Применение с честным курсором:

from replicationkit import FileCursorStore, apply_stream

report = apply_stream(envelopes, apply_one, cursor_store=FileCursorStore(path), channel="atlas")
if not report.complete:
    log.warning("отложено %s конвертов: %s", report.skipped, report.pending)

Отказ доставки, разобранный по существу:

from replicationkit import decide

record.attempts += 1
verdict = decide(exc, record.attempts, permanent=(AccessRevoked,))
record.status = verdict.status        # 'failed' сразу — или 'pending' до порога
record.last_error = verdict.reason

Поток, который переживает разрыв:

from replicationkit import Envelope, ReplayBuffer, SequenceAllocator

seq, buffer = SequenceAllocator(), ReplayBuffer(maxlen=512)
buffer.add(Envelope(seq=seq.next(), envelope_id=job_id, kind="job.status", payload=state))

tail, truncated = buffer.since(parse_cursor(request.headers.get("Last-Event-ID")))

Переезды и переходники

telemetrykit.outbox остался как переходник: его публичный API (append / read_batch / remove / path) не изменился, а механика теперь живёт в replicationkit.filequeue.FileEnvelopeQueue. Причина переезда: очередь исходящих конвертов — это не про телеметрию, через неё едет что угодно, и второй такой механизм заводить нельзя. Чтение переменных окружения с префиксом продукта осталось у переходника: кит доставки имени продукта не знает.

Образец удержания соединения (hold_channel) поднят из netkit.hold — там он написан и обкатан на бою; сам netkit не тронут.

Прогон

uv sync --group dev
uv run pytest -q
uv run ruff check .
uv run --with import-linter lint-imports

Metadata

Release files for s-replicationkit 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for s-replicationkit 0.1.0
File Size Uploaded
s_replicationkit-0.1.0.tar.gz 67.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for s-replicationkit 0.1.0
File Interpreter ABI Platform
s_replicationkit-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 101.2 kB

Release files / s_replicationkit-0.1.0.tar.gz

Download URL s_replicationkit-0.1.0.tar.gz
Size 67.1 kB
Tags Source
SHA-256 checksum
How to use checksums
c770546b0610a7fc41064f4b2076c164590dd1ba115120f7548d9bc6dac654d6
BLAKE2b-256 checksum
How to use checksums
af84671f9fe93d2b9e824982ed57c40ed5640951474150fc14ffbd0ed226ef1d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / s_replicationkit-0.1.0-py3-none-any.whl

Download URL s_replicationkit-0.1.0-py3-none-any.whl
Size 34.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ec13c3880902c905c57cb6dd1e3387e6823c3204b3325e513b2db33540cd6d61
BLAKE2b-256 checksum
How to use checksums
e55577d316f0e134a260fe80ab8a2720b4069002f6cd9d970d4b39de95900fd3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page