The problem
Anyone building on crypto market data has to integrate each exchange separately. Different protocols, different symbol formats, different rate limits, different failure modes — and the moment you add a decentralised exchange, none of the assumptions hold.
The usual answer is to write a pile of connectors, dump everything into a time-series database, and query that. It works, but it puts a database between you and data that is only interesting while it is fresh, and it turns a read into a network round trip plus a disk seek.
The brief was a single API — one symbol format, one response shape, one connection — fast enough that consumers could treat it as a local lookup rather than a remote call.
What we built
Finstrm holds all state in process. There is no database, no cache layer and no external service dependency. Feeds land in per-exchange goroutines, fan into a worker pool sized to the machine, and update a store built for lock-free reads. A broker fans the same events out over WebSocket.
The binary is stateless across restarts — market data rebuilds itself from live feeds on startup, which means deployment is copying one file and running it.
Decisions
No database
Market data is only valuable while it is current, and the working set fits comfortably in memory — under 512 MB for 500 symbols across three exchanges. Adding Postgres or a time-series store would have bought durability nobody wanted and cost a round trip on every read. Dropping it removed an entire class of operational work: no migrations, no connection pools, no backups, no failover.
Go, and only Go
Thousands of concurrent inbound events across a dozen feeds is precisely what goroutines
and channels are for. The whole system builds with go build and no other toolchain —
which matters more than it sounds, because the thing that runs in production is one
static binary with no runtime to install.
One symbol format
Every exchange names pairs differently. Normalising to BASE/QUOTE at the connector
boundary means consumers write the integration once. Adding a fourth exchange is a
connector satisfying an interface — no changes anywhere else in the system.
Result
The original target was 1,000 requests a second. The slowest endpoint does 23,500.
- Go 1.23
- WebSocket
- The Graph
- Docker
- Prometheus