Substreams sinks are now built in: Uniswap V4 indexed in under 8 minutes
We've folded substreams-sink-sql into the Substreams CLI, while greatly improving its speed and performance. You now run substreams sink postgres or substreams sink clickhouse directly, with no separate binary to install and no run vs from-proto decision to make. Pass a package and the mode is auto-detected.
The change is more than tidying. The new sink adds a disk spool, defers Postgres constraints until you hit chain head, and parses protobuf with a much faster unmarshaller. In a Uniswap V4 benchmark run by The Graph's BuildersDAO, a full Uniswap V4 backfill on Ethereum went from 23 hours on a subgraph to just 7 minutes 36 seconds on Substreams with a Pro Plan subscription. That's 182x faster!
The benchmark
BuildersDAO wanted a like-for-like comparison of subgraphs vs Substreams for Uniswap V4 indexing. Same events, same Postgres database, same block ranges: 21,688,328 to 23,535,547 on Ethereum (about 1.8M blocks) and 25,350,987 to 36,712,209 on Base (about 11.4M blocks). They reprocessed each range with a subgraph, then with Substreams on a cold cache at different worker counts, using Substreams v1.22.0 for the latest runs.
| Network | Subgraph | Substreams, 75 workers | Speedup |
|---|---|---|---|
| Ethereum | 23h 00m | 7m 36s | 182x |
| Base | 94h 19m | 2h 06m | 45x |
Benchmark environment: Ubuntu 24.04, PostgreSQL 16.14, 1.8 TB disk, Uniswap V4 events package, StreamingFast endpoints for Ethereum and Base mainnet.
More workers, more speed
Substreams parallelizes historical processing, and you control how far with a single header, X-Substreams-Parallel-Workers. Here is the full scaling curve from the v1.22.0 runs:
| Workers | Ethereum Network (time) | vs subgraph | Base Network (time) | vs subgraph |
|---|---|---|---|---|
| 1 | 8h 12m | 2.8x | 28h 41m | 3.3x |
| 5 | 1h 37m | 14x | 26h 51m | 3.5x |
| 15 | 36m | 38x | 9h 10m | 10x |
| 75 | 7m 36s | 182x | 2h 06m | 45x |
| 300 | 7m 30s* | 184x | 56m 38s | 100x |
BuildersDAO benchmark · Substreams v1.22.0 · cold cache.
*Ethereum's gains flatten around 75 workers, while Base has far more data to chew through, so it keeps gaining from 75 to 300. The longer the backfill, the more the extra workers pay off.
Log scale on the time axis; dashed lines are each chain's subgraph baseline.
At 300 workers, throughput hit 4,103 blocks/sec on Ethereum and 3,343 blocks/sec on Base, end to end into Postgres.
What changed in the sink
Earlier benchmark runs by BuildersDAO pointed us at the bottlenecks. Three changes account for most of the improvement since those runs:
- A disk spool. Data downloads to a local spool (8 GiB by default) and is committed to the database asynchronously, so a slow database no longer stalls the stream. This alone delivers up to 10x on Postgres sink throughput. Make sure the sink host has disk headroom for the spool.
- Constraints applied at chain head. Postgres constraints are no longer created at the start of a backfill. They're applied when you reach live blocks, or on demand with
substreams sink postgres constraints apply, and they can be built in parallel. It's the same trick as loading Postgres without indexes and building them afterwards. - A faster protobuf unmarshaller. We replaced
dynamicpbwithhyperpb, an 18x improvement on parsing and one memory arena per block instead of thousands of allocations.
BuildersDAO also had a practical tip to share: ingest raw tables to chain head first, then pause and build your materialized views. Heavy materialized views running during the backfill were the bottleneck, and turning them off gave an immediate boost.
Migrating
Existing databases keep working. Cursor tables and schemas are unchanged, so you can pause a sink, update the CLI and resume from your cursor without reprocessing. The main differences:
| Before | Now |
|---|---|
substreams-sink-sql run $DSN manifest.yaml 100:200 | substreams sink postgres manifest.yaml -s 100 -t 200 --dsn $DSN |
substreams-sink-sql from-proto ... | Auto-detected from the output module type |
substreams-sink-sql setup $DSN manifest.yaml | substreams sink postgres setup manifest.yaml --dsn $DSN |
DSN as positional argument, or PG_DSN / CLICKHOUSE_DSN | --dsn flag or SUBSTREAMS_SINK_DSN |
--metrics-listen-addr | --prometheus-addr |
The migration guide covers every flag and Docker change. The substreams-sink-sql repo is being deprecated, and new SQL dialects now live in the substreams repo under sink/sql.
The Substreams skill is updated too
The Substreams skill for AI coding agents now teaches the built-in substreams sink postgres and substreams sink clickhouse commands, so agents like Claude Code generate current syntax instead of the deprecated binary.
To install the skill, follow the skills overview, or get all nine from the GitHub repo to turn yourself into a Substreams expert.
Try it
Update to the latest Substreams CLI (v1.22.0 or later), point substreams sink postgres or substreams sink clickhouse at your package, and set your worker count.
A big thank you to the BuildersDAO team for running this benchmark and for pushing us to look harder at where sync time could be improved. Their feedback shaped several of these changes.
Have results of your own? Come tell us in Discord.