A brief database hiccup throws away every packet in flight, and nothing retries them #48
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
When a batch insert fails for a database-level reason (connection reset during a failover, pool timeout, statement timeout, missing partition), the chunk is counted as invalid and the consumer returns an empty ack to GenStage. The producer immediately frees demand and hands over the next packets, so everything received during the error window is permanently lost while APRS-IS keeps sending. At roughly 100 packets per second, a 30 second failover is thousands of packets missing from a station's track and from history, with no dashboard counter that shows the loss.
Evidence
At
2f150f66:lib/aprsme/packet_consumer.ex:363is the only write path,Repo.insert_all(Aprsme.Packet, insert_attrs, @insert_opts).lib/aprsme/packet_consumer.ex:370sends non row errors down the else branch;:389logsBatch insert of N packets failed, not retrying row by row, and:391returns{0, invalid_count + length(rows)}.lib/aprsme/packet_consumer.ex:420isdef row_error?(_error), do: false, soDBConnection.ConnectionError,DBConnection.OwnershipErrorand pool timeouts all take the discard path.lib/aprsme/packet_consumer.ex:168returns{:noreply, [], new_state}after processing, which GenStage reads as consumed, so nothing is redelivered.lib/aprsme/packet_producer.ex:75shows the demand loop that pulls the next packets as soon as the consumer replies.The comment at
:388shows the per-row fallback was deliberately rejected, and that decision is right for a bad row. The gap is that a transient database failure also falls through to it with no batch-level retry.Expected
A batch that fails on a database level is retried with backoff, or parked while consumption pauses, so a failover delays ingestion instead of deleting it. Only rows that genuinely violate a constraint are dropped, and any drop emits a counter an operator can watch.
How to verify
In
test/aprsme/packet_consumer_test.exs, stubRepo.insert_allto raiseDBConnection.ConnectionErroronce, push one batch, and assert the insert was attempted twice and the rows are stored. Today the second attempt never happens and no row lands.Acceptance criteria
Effort: M
Lens: reliability