已关闭
[RUNTIME-01] Replace minute polling with event-driven outbox wake-ups #39
urandon创建于  8月10日关闭于  8月11日
urandon
urandon成员
8月10日 创建

Parent and architecture

Problem

The cloud-dev hot path is polling-driven even when the system is idle:

  • the reconciler timer runs every minute and scans all 16 dispatch-ready buckets plus all 16 quota-expiry buckets;
  • the Telegram sender timer runs every minute and scans all 16 delivery-ready buckets;
  • this produces 48 YDB queries per idle minute, 69,120 per day, before any user workload;
  • the provisioned delivery YMQ queue is not connected to the sender;
  • the dispatch YMQ queue is downstream of the reconciler, so an empty queue does not stop the polling work.

A one-hour cloud-dev sample contained 60 reconciler and 60 sender invocations. The reconciler billed 109.2 seconds and the sender 45.4 seconds. Minute polling is therefore both an architectural and cost defect for a sparse serverless workload.

Outcome

Make dispatch admission and Telegram delivery event-driven on their hot paths while retaining durable YDB outboxes and a low-frequency recovery sweep for lost wake-up events and time-based recovery.

Design

flowchart LR
    TX["YDB transaction + durable outbox"] --> H["opaque wake-up envelope"]
    H --> Q["YMQ wake/delivery queue"]
    Q -->|"YMQ trigger"| C["targeted relay or sender"]
    C --> O["dispatch queue, worker, or Telegram"]
    TX -->|"rare bounded recovery sweep"| R["recovery pass"]
    R --> Q

Wake-up messages are hints, not canonical state. They contain only tenant-scoped opaque identifiers. Consumers point-read the durable outbox and remain idempotent under duplicate delivery. Publishing occurs only after the YDB transaction succeeds. A failed post-commit publish is recoverable from the durable ready index.

Scope

  • Add a dedicated scheduler wake queue and DLQ; do not mix pre-admission wake-ups with admitted worker dispatch messages.
  • Use the existing delivery queue as the YMQ-triggered Telegram sender input.
  • Add versioned queue kinds for dispatch-ready wake-ups while preserving dispatch.run and deliver.telegram.
  • Publish deterministic wake-up envelopes after successful creation of:
    • canonical/legacy ingress dispatch outboxes;
    • command and worker terminal Telegram delivery outboxes.
  • Add targeted, tenant-scoped point operations:
    • admit and publish one dispatch outbox by ID;
    • claim and send one Telegram delivery by ID.
  • Treat duplicate wake-ups as successful no-ops after confirming durable terminal/published state.
  • Make transient failures retain the YMQ message for retry and rely on queue redrive/DLQ limits.
  • Retain bounded bucket scans only as recovery:
    • dispatch/outbox and quota-expiry recovery;
    • delivery retry/recovery whose next_attempt_at has elapsed.
  • Change cloud-dev defaults from every-minute timers to no more than one recovery pass every six hours.
  • Update local stand wiring, public architecture/runbook documentation, and cost expectations.
  • Add a follow-up research note for YDB changefeed-based wake-ups; it does not block this implementation.

Correctness constraints

  • Never publish a wake-up before the corresponding YDB transaction commits.
  • Never place prompts, attachment bytes, credentials, Telegram tokens, or provider auth material in YMQ.
  • A lost wake-up must delay work only until recovery; it must not lose the outbox.
  • Duplicate YMQ delivery must not duplicate admission, quota reservations, worker dispatch, Telegram logical delivery, or audit effects.
  • Tenant ID and outbox ID from the envelope must both match the point-read durable record.
  • Scheduled retries must not busy-loop before next_attempt_at.

Verification

  • Unit tests for duplicate, missing, terminal, retryable, and malformed wake-up envelopes.
  • YDB integration tests prove targeted point reads and exactly-once state transitions under duplicate delivery.
  • Local integration proves:
    • ingress emits a scheduler wake-up;
    • admission emits one worker dispatch;
    • worker completion emits one delivery wake-up;
    • sender consumes the delivery queue without a timer scan.
  • Terraform validation proves YMQ triggers, DLQs, IAM and recovery schedules.
  • An idle cloud-dev observation window shows no recurrent reconciler/sender container invocations between recovery windows.
  • Recovery tests prove an intentionally dropped wake-up is eventually republished.

Acceptance criteria

  • Normal ingress-to-worker-to-delivery latency is driven by queue events, not cron.
  • The delivery queue is an active runtime dependency rather than unused infrastructure.
  • Empty queues cause no per-minute YDB bucket scans.
  • Recovery remains bounded and idempotent.
  • The deployed defaults fit the sparse-development serverless cost model.

Non-goals

  • Replacing YMQ with YDB topics in the same change.
  • Removing durable YDB outboxes.
  • Implementing Telegram canonical-ingress migration #36.
  • Claiming direct YDB changefeed to Serverless Container trigger compatibility without a verified spike.

Estimate

  • 8 SP / 4 engineering days
  • Risk: high (outbox correctness, retry semantics, IAM and cloud trigger wiring)
likedislike
urandon
urandon成员
8月11日 评论:

Research note: YDB changefeeds as a future wake source

Decision for RUNTIME-01: keep the explicit post-commit YMQ wake envelopes. Do not make YDB CDC a dependency of this implementation.

YDB CDC is technically attractive for outbox wake-up generation:

  • a changefeed is written only after the table transaction commits;
  • records for the same primary key preserve order;
  • KEYS_ONLY mode can expose only the durable outbox key;
  • the changefeed is backed by a YDB topic and can use topic auto-partitioning.

Sources:

The current Yandex Serverless Containers trigger catalog, however, has triggers for Yandex Message Queue, Data Streams, Object Storage, Cloud Logging, and several other services, but does not document a trigger for a YDB topic/changefeed:

Therefore CDC would still require one of these bridges:

  1. an always-running YDB topic consumer that invokes the container or republishes to YMQ;
  2. a custom scheduled relay, reintroducing polling;
  3. a verified YDB-topic → Data Streams/YMQ integration that is not currently documented as a native trigger path.

Options 1 and 2 conflict with the sparse serverless cost goal, while option 3 cannot be treated as available without a working provider/API spike. CDC also adds write/storage overhead and an additional retention/consumer-offset failure mode.

Revisit criteria

Re-open this design only when at least one of the following is true:

  • Yandex Serverless Containers exposes a native YDB topic/changefeed trigger;
  • YDB Serverless provides a documented push subscription;
  • a measured relay can scale to zero and costs less than explicit YMQ publication.

A future spike should compare end-to-end latency, idle cost, duplicate/lost-event behavior, IAM, topic partition growth, retention expiry, and operational recovery against the current YDB outbox + deterministic YMQ hint design.

likedislike
urandonurandon成员
8月11日 关联了pull request:RUNTIME-01: replace minute polling with event-driven outbox wakes
urandon
urandon成员
8月11日 评论:

Implementation status — event-driven outbox wake-ups

Implementation is ready in MR !24, head commit 718ff23.

Implemented

  • Added payload-free, deterministic wake.dispatch and wake.telegram envelopes. They contain only the tenant and durable outbox identifier.
  • YDB outboxes remain the source of truth. Consumers resolve wakes through tenant-scoped primary-key point reads.
  • Missing, duplicate, and already-terminal wakes are acknowledged as successful no-ops.
  • Dispatch domain blocks use bounded exponential retries and are then parked; transport/system errors remain subject to YMQ retry/redrive and DLQ behavior.
  • Duplicate ingress and duplicate worker deliveries republish the deterministic wake, repairing a post-commit publication outage.
  • A failed wake publication after a successful YDB commit is logged for recovery and does not produce a false Telegram webhook failure.
  • Local consumers long-poll YMQ. Cloud-dev uses YMQ triggers on /wake.
  • Bucket scans are retained only for local startup recovery and six-hour cloud recovery timers.
  • Added the scheduler wake queue/DLQ, trigger wiring, least-privilege publisher credentials, Compose wiring, runbooks, and recovery-oriented E2E coverage.

Idle-cost model

For two consumers scanning 24 buckets:

  • scheduled YDB bucket reads: 69,120/day -> 192/day;
  • timer-triggered container invocations: 2,880/day -> 8/day;
  • reduction in both scheduled idle-work classes: 99.72%.

Message-driven work remains proportional to real traffic.

Verification

Green locally:

  • make ci
  • make e2e-local
  • clean-room, twice-migrated make ydb-integration
  • docker compose config --quiet
  • Terraform format plus provider-aware bootstrap/cloud-dev validation

GitHub mirror CI is fully green for 718ff23: run #72, including Go, YDB, local multi-service E2E/restart, Terraform, and runtime image jobs.

The first mirrored run exposed a hidden YDB integration-test assumption: a global recovery bucket scan was asserted to contain only the test tenant. Deterministic delivery IDs legitimately placed another tenant in the same bucket. Commit 718ff23 changed the assertion to verify exactly one matching tenant/delivery tuple without rejecting valid co-located rows; the full clean-room suite and mirrored CI then passed.

Remaining acceptance gate

Keep #39 open through merge and cloud-dev deployment. Close it only after monitoring confirms:

  1. the former approximately 0.033 requests/second minute-timer baseline is gone;
  2. idle reconciler and Telegram sender activity is limited to four recovery invocations each per day;
  3. no periodic YMQ requests remain while idle;
  4. one synthetic dispatch and one Telegram delivery are processed through the YMQ-triggered path.
likedislike
urandon
urandon成员
8月11日 评论:

Completion and cloud handoff

MR !24 is merged into main as commit 2f0337f. The exact pre-merge head 718ff23 passed all mirrored GitHub Actions jobs: Go verification, clean YDB integration, full local multi-service E2E with restart, Terraform validation, and runtime image builds.

The repository implementation requested by #39 is complete:

  • normal scheduler and Telegram delivery work is YMQ-triggered;
  • consumers use tenant-scoped outbox point reads;
  • duplicate/missing/terminal wakes are idempotent;
  • post-commit wake loss is recoverable;
  • cloud recovery defaults are every six hours rather than every minute;
  • the expected scheduled idle-work reduction is 99.72%.

The deployment and live cost/effectiveness evidence is now tracked as RUNTIME-02 #40. That issue owns the saved-plan deployment, real queue-triggered E2E, duplicate and missed-wake checks, a measured idle window, Lockbox/KMS/YDB/YMQ/container evidence, and rollback proof. It also feeds the broader production-readiness gate #14.

Closing #39 as the implementation task; no cloud success is claimed until #40 passes.

likedislike
urandonurandon成员
8月11日 关闭了 issue
urandonurandon成员
8月11日 关联了pull request:[RUNTIME-02] Preserve the live Telegram edge in cloud deployment
urandonurandon成员
27 天前 添加了label:mvpruntime
urandonurandon成员
27 天前 关联了里程碑:MVP — Core platform (#6)