FairGate, from the inside.
A TCP ingestion gateway in Go that keeps one noisy producer from starving the rest, and makes every accepted event durable before acknowledging it.
Each chapter below is a small, working model of one part of the gateway. They follow the behaviour documented in the repository, with numbers scaled down so you can watch them happen.
- Go
- Segmented WAL → ClickHouse
- Prometheus · Grafana · Docker Compose
- Open source · MIT · beta
Every exit from the pipeline answers the producer.
A producer sends an event the gateway has never seen.
- Decodependingwire.DecodeEvent
- Admissionpendingadmission.Allow
- IdentitypendingdedupManager.PersistIfNew
- WAL + syncpendingwalLog.Append
- Enqueuependingqueues.EnqueueContext
- ACKpendingsendAck
{
"producer_id": "sensor-01", "producer_epoch": "1", "event_time": "2026-10-02T08:00:00Z", "seq": 1, "idx": 0, "event_type": "temperature", "payload": "{\"value\":24.7}"}- Press play to send the event.
Waiting for the gateway…
A flood is refused at the door.
10.0 / 10
- 0
- 0
accepted rejected
Start a stream faster than the refill rate, or fire a burst bigger than the capacity, to see events refused.
Durable, without one fsync per event.
- 1
- 1 sync
A single writerLoop goroutine drains all 6 waiting requests, writes them, calls Sync() once, then releases every caller. However many handlers are appending, each waits about one sync. A request that arrives alone waits up to 2 ms for company.
Telling an interrupted write from corruption.
segment 0003
segment 0004
segment 0005final · active
Inject a fault, then restart. Recovery scans segments in ID order, record by record.
Shares follow weights, not volume.
- AShare 0%
- BShare 0%
- CShare 0%
Press play or step to run the scheduler.
Durable isn’t stored, until the shipper says so.
seg 3
- e1▼
- e2
- e3
seg 4
- e4
- e5
- e6
seg 5
- e7
- e8
▼ checkpoint current batch pending (durable) stored
- Shipper started at checkpoint e1.
- No rows yet.
- 0 / 8
- 0
Try crashing between insert and commit, or losing the checkpoint after a few batches. The replay re-reads the same records, but every event still lands exactly once.