very fast at protocol indexer with flexible filtering, xrpc queries, cursor-backed event stream, and more, built on fjall
rust
fjall
at-protocol
atproto
indexer
title: hydrant vs tap#
while tap is designed as a firehose consumer and simply just propagates events while handling sync, hydrant is flexible, it allows you to directly query the database for records, and it also provides an ordered view of events, allowing the use of a cursor to fetch events from a specific point. it can act as both an indexer or an ephemeral view of some window of events.
you can also read this blogpost for a longer comparison.
stream behavior#
the WS /stream (hydrant) and WS /channel (tap) endpoints have different designs:
| aspect | tap (/channel) |
hydrant (/stream) |
|---|---|---|
| distribution | sharded work queue: events are load-balanced across connected clients. if 5 clients connect, each receives ~20% of events. | broadcast: every connected client receives a full copy of the event stream. if 5 clients connect, all 5 receive 100% of events. |
| cursors | server-managed: clients ACK messages. the server tracks progress and redelivers unacked messages. | client-managed: client provides ?cursor=123. the server streams from that point. |
| persistence | events are stored in an outbox and sent to the consumer, and removed from the outbox when acked. nothing is replayable. | record events are replayable. identity/account are ephemeral. use GET /repos/:did to query identity / account info (handle, pds, signing key, etc.). |
| backfill | backfill events are mixed into the live queue and prioritized (per-repo, acting as synchronization barrier) by the server. | backfill simply inserts historical events (live: false) into the global event log. streaming is just reading this log sequentially. synchronization is the same as tap, live: true vs live: false. |
| event types | record, identity (includes status) |
record, identity (handle, cache-buster), account (status) |