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)
| File | Size | Uploaded | |
|---|---|---|---|
| s_replicationkit-0.1.0.tar.gz | 67.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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}
|