Choosing a Message History Strategy for Waku v2 Applications
A decision guide for Waku v2 message history: compare Relay-only, Relay+Store, and Relay+Filter+App persistence across node cost, availability, privacy, and operational complexity. Includes a concrete Docker-based validation to measure real resource trade-offs.
06 Nov 2025, 16:09 UTC

The problem: history is not free in a gossip network
Waku v2 uses a libp2p gossip substrate (Relay) that forwards messages to peers subscribed to a content topic. By design, Relay delivers messages only to nodes that are online at publish time. If your application needs late‑joining clients to catch up, or must survive node restarts without losing messages, you have to add something on top of Relay. The choice is not trivial: each option shifts cost, trust, and operational burden in different directions.
Takeaway: For most teams the decision reduces to three patterns—Relay‑only for ephemeral chat, Relay + Store when you can run or pay for persistent store nodes, and Relay + Filter with application‑level persistence when you need full control over retention and encryption. The table below maps each pattern to the constraints that usually drive the decision.
Decision constraints
- Node resource budget: Disk, bandwidth, and CPU available for long‑term message retention.
- Acceptable message loss: Can a user miss messages sent while they were offline?
- Late‑joining clients: Must a client that connects hours or days later retrieve prior conversation?
- Privacy of stored content: Are messages end‑to‑end encrypted before they hit the network, or does the store node see plaintext?
- Operational complexity: Will you run your own store nodes, rely on third‑party providers, or keep persistence entirely in your backend?
Option comparison
| Pattern | History source | Node cost | Availability guarantee | Typical fit |
|---|---|---|---|---|
| Relay‑only | None (live only) | Low | Only while subscriber is online | Ephemeral group chat, presence, real‑time signals |
| Relay + Store | Network store nodes (Waku Store protocol) | Medium‑High (disk, indexing, query load) | Retention limited by store node policy & peer selection | Consumer apps that need catch‑up without custom backend |
| Relay + Filter + App persistence | Application database (via Filter subscription) | Low‑Medium (filter light client + your DB) | Application‑defined (you control retention & encryption) | Apps requiring audit logs, compliance, or custom encryption |
Trade‑offs in depth
Relay‑only
Zero history infrastructure. A node subscribes to a content topic via Relay and receives messages only while connected. If the node goes offline, messages published during that window are gone for that node. This is the simplest operational model—no disk growth, no query latency, no store‑node trust assumptions. It works when the product tolerates “you had to be there” semantics (e.g., live location sharing, gaming state sync).
Relay + Store
Waku Store adds a request/response protocol: a client sends a StoreQuery to a peer advertising the STORE capability, specifying a content topic and time range. The store node returns matching messages from its local index. Advantages: clients need no backend change; catch‑up works out of the box. Drawbacks:
- Retention is per‑node, not network‑wide. A store node prunes according to its own policy (time, disk quota, message count). There is no guarantee that another store node has the same messages.
- Trust surface expands. If messages are not end‑to‑end encrypted before Relay, the store operator can read them. Even with encryption, metadata (topic, timing, sender peer ID) is visible.
- Resource profile. A store node indexes every message on subscribed topics. Disk grows linearly with traffic; query latency depends on index structure and load.
- Light clients cannot serve Store. Only full nodes with the Store service enabled can answer queries. If your user base runs mostly light clients, you must operate or contract enough store nodes.
Relay + Filter + Application persistence
Your backend (or a dedicated service) runs a Waku light node with the Filter protocol. It subscribes to the same content topics as users, receives every message via Filter push, and writes them to your database—encrypted with your application keys if desired. Clients then fetch history from your API, not from the Waku network. This gives you:
- Full control over retention, encryption, and access control.
- No dependence on third‑party store node availability or pruning policies.
- Additional development effort: you build the ingestion pipeline, schema, and query API.
- Duplicate network logic: your filter node must stay online and re‑sync after restarts (use Filter’s
contentFilterwith a persistent cursor).
Concrete validation: spin up two local nodes and measure
The fastest way to internalize the trade‑offs is to run a controlled experiment. The following steps assume you have Docker and wakucli (or the js-waku / go-waku binaries) available. Run commands on a Linux/macOS host with at least 2 GB free RAM and 5 GB disk.
1. Start a Relay‑only node
docker run -d --name waku-relay \
-p 60000:60000 -p 60001:60001 \
wakuorg/nwaku:v0.32.0 \
--relay=true --store=false --filter=false \
--tcp-port=60000 --ws-port=60001 \
--log-level=INFO
This node advertises only Relay. It will not answer Store queries.
2. Start a Relay + Store node
docker run -d --name waku-store \
-p 70000:70000 -p 70001:70001 \
-v waku-store-data:/data \
wakuorg/nwaku:v0.32.0 \
--relay=true --store=true --filter=false \
--tcp-port=70000 --ws-port=70001 \
--store-message-retention=86400 \
--store-capacity=100000 \
--log-level=INFO
The --store-message-retention (seconds) and --store-capacity (message count) illustrate the per‑node policy. Adjust for your expected volume.
3. Connect the two nodes
# Get the multiaddr of the relay node
RELAY_ADDR=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' waku-relay)
docker exec waku-store \
wakucli admin peer add "/ip4/${RELAY_ADDR}/tcp/60000/p2p/$(docker exec waku-relay wakucli admin id | jq -r .peerId)"
Both nodes now gossip on the same topics.
4. Publish test messages
TOPIC="/waku/2/default-waku/proto/test/1"
for i in {1..20}; do
docker exec waku-relay wakucli relay publish \
--topic="${TOPIC}" \
--payload="msg-${i}-$(date +%s)" \
--ephemeral=false
sleep 0.2
done
Messages go to the Relay topic; both nodes receive them. The store node indexes them.
5. Simulate a late‑joining client
# Start a third node that was NOT online during publish
docker run -d --name waku-late \
-p 80000:80000 -p 80001:80001 \
wakuorg/nwaku:v0.32.0 \
--relay=true --store=false --filter=false \
--tcp-port=80000 --ws-port=80001
# Connect it to the store node
STORE_ADDR=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' waku-store)
docker exec waku-late \
wakucli admin peer add "/ip4/${STORE_ADDR}/tcp/70000/p2p/$(docker exec waku-store wakucli admin id | jq -r .peerId)"
6. Query history from the store node
docker exec waku-late wakucli store query \
--topic="${TOPIC}" \
--start-time=$(date -d '2 minutes ago' +%s) \
--end-time=$(date +%s) \
--page-size=50
You should see the 20 messages returned. Now run the same query against the relay‑only node (replace peer ID/port). It will return an empty result because that node does not run the Store service.
7. Observe resource usage
docker stats waku-store --no-stream
# Watch MEM, CPU, and disk growth (the mounted volume)
du -sh /var/lib/docker/volumes/waku-store-data/_data
Run the publish loop for a longer period (e.g., 10 k messages) and record disk growth per 1 k messages. This gives you a concrete capacity planning number for your environment.
Limitations and verification checklist
- Store retention is not a network guarantee. If the store node you query prunes aggressively or goes offline, history is unavailable. The experiment above shows retrieval works while the store node is up and unpruned.
- Light clients cannot serve Store. If your users run browser or mobile light nodes, you must operate store nodes yourself or pay a provider.
- Privacy depends on encryption layer. Waku Relay and Store handle plaintext payloads unless your application encrypts before publish. The store node sees topic, timestamp, and sender peer ID regardless.
- Version and stack parity. Go (nwaku) and JS (js-waku) implementations may differ in Store query parameters, pagination, or Filter cursor persistence. Verify the exact API for your chosen stack before committing.
- Filter + App persistence requires reliable ingestion. Your filter node must handle reconnection, cursor persistence, and backfill. Test a simulated crash/restart cycle to ensure no messages are dropped.
Practical decision flow
- Can users tolerate missing messages sent while offline? → Relay‑only.
- Do you need catch‑up without building a backend ingestion pipeline? → Relay + Store (budget for store node ops).
- Do you require custom retention, encryption keys you control, or compliance audit trails? → Relay + Filter + App persistence.
Run the validation steps above with your projected message volume and latency SLAs. The numbers you collect (disk per 1 k msgs, query p99, peer churn impact) are the only reliable inputs for capacity planning—generic benchmarks do not reflect your topic fan‑out, payload size, or network topology.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.