Stockez les tâches dans une queue à buckets temporels / à priorité indexée par heure d'échéance, ne pollez que les tâches dues maintenant, et confiez-les à des workers idempotents avec une livraison at-least-once. Ne scannez jamais 10M de lignes à chaque tick — indexez par heure de déclenchement pour que chaque poll ne touche que ce qui est dû.
Architecture
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
