Almacena las tareas en una cola con buckets temporales / de prioridad indexada por hora de vencimiento, sondea solo las tareas que vencen ahora, y entrégalas a workers idempotentes con entrega at-least-once. Nunca escanees 10M de filas en cada tick — indexa por hora de disparo para que cada poll toque solo lo que vence.
Arquitectura
API ─▶ Task store (DB/Redis ZSET, score = due_ts) ── 10M tasks, sharded
│
Poller (leader per partition) ── ZPOPMIN due<=now ──▶ Ready queue (Kafka)
│ leader election (etcd/ZK) │
▼ ▼
other pollers idle/standby Worker pool (idempotent)
└─ ack ✔ / retry ✖ ─▶ DLQ
