ztreamerZcash Labs
// Measured November 13, 2026 · Ryzen 9 9950X3D

ztreamer vs lightwalletd

Sync speed, serving and resource use of ztreamer direct (embedded zakura), ztreamer remote (default snapshot, then a remote zakura) and lightwalletd. All three ran on one machine, one after the other, against the same zakura state.

Sync, direct vs lightwalletd42.5× fasterztreamer direct 2.4 min · lightwalletd 103.6 min
Sync, remote from snapshot5.7 minimport and tail to tip · 18.3× faster than lightwalletd
Wallet download2.9× fasterdirect 22.6 s · lightwalletd 65.2 s
10,000-block range, p5022.3× lowerdirect 4.79 ms · lightwalletd 107 ms
Throughput at 128 clients3.9× higherdirect 1.07 M blocks/s · lightwalletd 0.27 M
Disk footprint17.39 GiBztreamer index · lightwalletd cache 29.00 GiB

Summary

ztreamer reaches a serving index of the whole chain in 2.4 minutes when it runs zakura in process, and in 5.7 minutes in remote mode from the published snapshot. lightwalletd took 1 hour 44 minutes to build its cache from the same node. Once synced, ztreamer serves compact blocks 3 to 22 times faster than lightwalletd, depending on the request, with a lower tail latency at every concurrency level, and it uses about a tenth of the CPU for each block it serves. Its index is 40 % smaller on disk than the cache of lightwalletd. The two ztreamer modes serve at the same speed. One defect showed up under load: with 256 concurrent connections ztreamerd fails some requests with MDB_READERS_FULL.

Setup

Value
CPUAMD Ryzen 9 9950X3D · 16 cores / 32 threads · 128 MiB L3
Memory60.4 GiB RAM · 32 GiB swap
DiskWD_BLACK SN850X 2 TB NVMe · ext4 · index, cache and zakura state on the same disk
OSManjaro Linux · Linux 6.12.112 · x86_64
CPU governorpowersave
ztreamerztreamerd 0.1.0 · commit af04e99868 · maxperf profile
zakurazakurad 1.8.4+g618e972e5523 (standalone node), the same version embedded in ztreamerd
lightwalletdcommit 15456b9043 · Go build, default flags
Chainzakura mainnet state from the archive, tip 3,564,614, synced to the live tip before each run
Serving interval419,200 to 3,564,614, fixed for every server
Clientsnative Rust/Tonic serving suite of this repository · ghz v0.120.0 · loopback, plaintext HTTP/2

Each server ran alone on the host, in this order: ztreamer direct, ztreamer remote, lightwalletd. Every run started from an empty index. The zakura state came from the same archive and was brought to the live tip before each run, so no server timed the catch-up of its node. All three servers then served the same fixed interval, with the same seeded request sequences.

One run of each server. The node followed the live chain, so the tip moved a few blocks between runs; every historical request was in the fixed interval. The client shared the host with the server. lightwalletd built its cache from genesis, as does ztreamer. Remote mode depends on the download speed of the snapshot source; here the import moved 18.2 GB in 336 s, about 54 MB/s including verification. Throughput of ghz for streaming RPCs is limited by ghz itself (about 1,750 100-block ranges/s for both ztreamer modes); the native suite gives the server limits.

Sync speed

Time from the start of the server process until its index has the tip of the synced node and its gRPC listener accepts connections.

Sync speed
ServerTime to tipTipBlocks / sCPU sMean coresPeak RSSDisk readDisk written
ztreamer direct2.4 min3,569,24824,4105013.42.27 GiB377.11 GiB26.59 GiB
ztreamer remote5.7 min3,569,34410,5264,50013.34.20 GiB10.66 GiB35.29 GiB
lightwalletd103.6 min3,569,67257411,4841.81.23 GiB256.21 GiB30.45 GiB

In direct mode ztreamerd reads blocks straight from the zakura state with eight workers, at 1.5 to 6 GB/s from disk. The slow band between heights 1.7 M and 2.1 M is the sandblasting period, whose blocks are large. In remote mode the import fetches the sealed ranges of the snapshot and checks them: the node attests the block hash at the end of each range and the tree roots of each epoch. lightwalletd asks the node for one block at a time over JSON-RPC, so its rate follows block size: about 1,500 blocks/s at the start, 65 blocks/s in the sandblasting period. After the first sync, ztreamerd in direct mode filled the range metadata of the 3,569 sealed ranges for its Replicator. That took 6 minutes 41 s after it began to serve, and raised its RSS by mapping the index. In remote mode the snapshot brought that metadata, so the pass found nothing to do.

Sync rate by height
ServerMilestoneAfterHeight
ztreamer directhistorical pass complete2.4 min3,568,258
ztreamer directgRPC listener open2.4 min–
ztreamer remoteimport planned2.0 s–
ztreamer remoteimport complete5.6 min–
ztreamer remotegRPC listener open5.6 min–

Resource use

A sampler read /proc once a second for each process: CPU time, resident memory and disk bytes. For the remote and lightwalletd runs the totals include the standalone zakurad, which they need; in the direct run the node is inside ztreamerd.

Resources while syncingIndex growthDisk footprintResources while serving
ServerWorkloadDurationCPU sMean coresPeak RSS
ztreamer directidle60.0 s0.30.0119.27 GiB
ztreamer directlatency110.1 s348.83.1719.31 GiB
ztreamer directranges115.0 s28.20.2519.31 GiB
ztreamer directconcurrency 10k2.6 min652.34.1419.36 GiB
ztreamer directconcurrency 1002.0 min609.35.0119.36 GiB
ztreamer directwallet download22.6 s4.40.1919.36 GiB
ztreamer directghz11.2 min3,805.85.6519.47 GiB
ztreamer remoteidle60.0 s0.20.002.82 GiB
ztreamer remotelatency2.7 min334.62.092.95 GiB
ztreamer remoteranges117.9 s29.20.2519.53 GiB
ztreamer remoteconcurrency 10k2.6 min601.73.8619.71 GiB
ztreamer remoteconcurrency 1002.0 min577.84.7519.74 GiB
ztreamer remotewallet download22.6 s4.60.2019.73 GiB
ztreamer remoteghz11.2 min3,945.55.8719.74 GiB
lightwalletdidle60.0 s0.50.011.15 GiB
lightwalletdlatency2.8 min371.32.231.31 GiB
lightwalletdranges6.2 min482.31.291.35 GiB
lightwalletdconcurrency 10k3.9 min2,070.38.821.81 GiB
lightwalletdconcurrency 1002.0 min1,448.011.871.79 GiB
lightwalletdwallet download65.2 s87.31.341.37 GiB
lightwalletdghz11.1 min6,750.010.102.40 GiB

Serving efficiency: compact blocks served per CPU second of the server and its node, in the workloads that serve blocks.

Workloadztreamer direct blocksztreamer direct CPU sztreamer direct blocks / CPU sztreamer remote blocksztreamer remote CPU sztreamer remote blocks / CPU slightwalletd blockslightwalletd CPU slightwalletd blocks / CPU s
ranges21,110,00028.2748,84721,110,00029.2722,45021,110,000482.343,774
concurrency 10k110,010,000652.3168,636112,700,000601.7187,28735,460,0002,070.317,128
concurrency 10090,507,100609.3148,54394,403,600577.8163,38723,806,3001,448.016,440
wallet download3,145,4154.4721,4253,145,4154.6688,2753,145,41587.336,034

While syncing, ztreamer direct read 405 GB from disk in 146 s. In the first 44 s the embedded zakura opened its state on 6.5 cores on average and read 136 GB; the historical pass then read 269 GB of zakura state in 102 s (2.6 GB/s) on about 2 cores, so it was bound by the disk, not the CPU. Remote mode used about 24 cores while it verified the import. lightwalletd used under one core for itself and about 1.5 cores for its node for 1 hour 44 minutes. While serving, ztreamer used 4 to 6 cores under the heaviest workloads and lightwalletd 9 to 12, for a third to a quarter of the blocks. At idle all three use almost no CPU. The peak RSS of ztreamer while serving counts the mapped index; see the split below.

Restart and memory
ServerProcessWhenAnonymousFile-backedRSS
ztreamer directserveridle0.88 GiB0.57 GiB1.45 GiB
ztreamer directserverload1.21 GiB17.24 GiB18.46 GiB
ztreamer remoteserveridle0.02 GiB0.49 GiB0.51 GiB
ztreamer remoteserverload0.39 GiB17.18 GiB17.57 GiB
ztreamer remotezakurad nodeidle0.78 GiB0.03 GiB0.81 GiB
ztreamer remotezakurad nodeload0.77 GiB0.04 GiB0.81 GiB
lightwalletdserveridle0.10 GiB0.02 GiB0.12 GiB
lightwalletdserverload0.52 GiB0.02 GiB0.54 GiB
lightwalletdzakurad nodeidle0.75 GiB0.03 GiB0.78 GiB
lightwalletdzakurad nodeload0.76 GiB0.03 GiB0.80 GiB
ServerRestart to servingWallet download after restartBlocks / s at 64 clients after restart
ztreamer direct47.3 s22.6 s1,087,600
ztreamer remote3.1 s22.5 s1,098,970
lightwalletd1.1 s64.9 s273,830

The restart runs restarted each server on the index of its main run, with the node already at the tip, then measured idle and loaded memory and repeated the wallet download. The wallet download after the restart took the same time as in the main run for every server.

Serving overview

Serving overview

Both ztreamer modes serve from the same kind of index, and their serving results agree within a few percent. lightwalletd serves blocks from its cache file and asks the node for everything else: chain info, tree states, transactions and address data.

RPC latencies

500 sequential requests per case from one client, after one warmup. Completion time includes draining the stream and decoding. Address cases use a busy and a quiet transparent address over the whole interval; GetTransaction uses two fixed transactions.

RPC latencies
Caseztreamer direct p50ztreamer direct p99ztreamer remote p50ztreamer remote p99lightwalletd p50lightwalletd p99LWD ÷ direct, p50
GetLightdInfo21 µs37 µs22 µs52 µs270 µs460 µs12.6×
GetLatestBlock21 µs30 µs21 µs24 µs176 µs353 µs8.2×
GetBlock32 µs429 µs52 µs405 µs85 µs789 µs2.7×
GetTreeState173 ms764 ms247 ms1.53 s261 ms1.50 s1.5×
GetLatestTreeState39 µs101 µs26 µs41 µs280 µs543 µs7.3×
GetSubtreeRoots/sapling79 µs170 µs100 µs284 µs302 µs663 µs3.8×
GetSubtreeRoots/orchard384 µs732 µs447 µs751 µs954 µs1.89 ms2.5×
GetMempoolTx58 µs96 µs61 µs247 µs25 µs51 µs0.4×
GetTaddressTxids/busy59 µs157 µs99 µs237 µs129 µs212 µs2.2×
GetTaddressTransactions/busy50 µs75 µs98 µs198 µs136 µs222 µs2.7×
GetTaddressBalance/busy92 µs124 µs127 µs213 µs186 µs285 µs2.0×
GetAddressUtxos/busy42 µs83 µs73 µs149 µs120 µs218 µs2.8×
GetTaddressTxids/quiet96 µs191 µs327 µs598 µs420 µs751 µs4.4×
GetTaddressTransactions/quiet95 µs191 µs267 µs369 µs461 µs775 µs4.9×
GetTaddressBalance/quiet96 µs185 µs124 µs195 µs181 µs302 µs1.9×
GetAddressUtxos/quiet43 µs107 µs71 µs124 µs123 µs207 µs2.9×
GetTransaction/039 µs81 µs108 µs168 µs140 µs223 µs3.6×
GetTransaction/136 µs66 µs102 µs153 µs167 µs267 µs4.7×

Range sizes

GetBlockRange over 1,000 seeded random ranges per size (100 at 100,000 blocks), one client, sequential.

Range sizes
Rangeztreamer direct p50ztreamer direct p99ztreamer direct blocks/sztreamer remote p50ztreamer remote p99ztreamer remote blocks/slightwalletd p50lightwalletd p99lightwalletd blocks/s
10 blocks37 µs1.50 ms87,06353 µs2.03 ms61,814232 µs2.39 ms27,220
100 blocks108 µs14.34 ms122,197165 µs19.04 ms99,5871.19 ms22.81 ms43,087
1,000 blocks660 µs136 ms118,376788 µs177 ms105,46010.53 ms228 ms43,953
10,000 blocks4.79 ms1.20 s161,9335.06 ms1.23 s157,381107 ms1.94 s52,812
100,000 blocks51.93 ms4.56 s228,57056.26 ms4.59 s228,977997 ms8.72 s63,674

Concurrent serving

Closed loop: each client keeps one GetBlockRange outstanding for 20 s per level, on its own connection. Two request sizes: 10,000 blocks (a wallet catching up) and 100 blocks (a wallet that is nearly synced).

Concurrency

10,000 blocks per request

Clientsztreamer direct blocks/sztreamer direct p50ztreamer direct p99ztreamer direct errorsztreamer remote blocks/sztreamer remote p50ztreamer remote p99ztreamer remote errorslightwalletd blocks/slightwalletd p50lightwalletd p99lightwalletd errors
1143,5975.10 ms1.26 s0144,5004.88 ms1.26 s054,56997.49 ms1.78 s0
4551,4605.43 ms1.34 s0547,2325.42 ms1.35 s0160,833135 ms2.50 s0
161,057,42812.56 ms2.63 s01,109,20711.44 ms2.48 s0301,068305 ms4.81 s0
641,062,686402 ms3.06 s01,105,806398 ms2.83 s0265,8531.28 s18.31 s0
1281,065,0551.06 s3.64 s01,101,892979 ms3.44 s0270,3352.50 s23.91 s0
2561,050,6762.21 s4.97 s01,126,9932.14 s4.71 s0279,8685.09 s29.63 s0

100 blocks per request

Clientsztreamer direct blocks/sztreamer direct p50ztreamer direct p99ztreamer direct errorsztreamer remote blocks/sztreamer remote p50ztreamer remote p99ztreamer remote errorslightwalletd blocks/slightwalletd p50lightwalletd p99lightwalletd errors
1120,269110 µs14.98 ms0121,169108 µs14.92 ms044,6851.12 ms22.19 ms0
4421,846136 µs16.66 ms0426,791132 µs16.40 ms0147,1611.41 ms26.86 ms0
16955,560378 µs25.63 ms0985,647353 µs25.12 ms0260,2133.45 ms57.42 ms0
641,012,7884.61 ms41.62 ms01,073,1534.27 ms40.47 ms0244,51214.10 ms215 ms0
1281,006,29111.10 ms48.54 ms01,061,43510.50 ms47.58 ms0244,89626.12 ms397 ms0
2561,001,41724.19 ms61.50 ms01,045,46123.09 ms60.71 ms0243,43726.41 ms814 ms0

Wallet download

One client downloads every compact block from Sapling activation to the end of the interval, 10,000 blocks per request. This is the network part of a new wallet's first sync; it excludes trial decryption, scanning and storage.

Wallet download
ServerTimeBlocksProtobuf bytesBlocks / s
ztreamer direct22.6 s3,145,41516.66 GiB139,121
ztreamer remote22.6 s3,145,41516.66 GiB139,188
lightwalletd65.2 s3,145,41516.66 GiB48,276

Throughput per RPC

ghz drives one RPC at a time with 1, 16, 64 and 256 concurrent workers, one connection each, for 15 s per level. Requests with heights cycle through 5,000 seeded random heights. The first request of each worker is not counted. Throughput counts successful requests only.

Throughput per RPCLatency per RPC
RPCWorkersztreamer direct req/sztreamer direct p50ztreamer direct p99ztreamer direct errorsztreamer remote req/sztreamer remote p50ztreamer remote p99ztreamer remote errorslightwalletd req/slightwalletd p50lightwalletd p99lightwalletd errors
GetBlock19,39437 µs1.31 ms09,37737 µs1.31 ms05,30871 µs1.80 ms0
GetBlock1666,16583 µs2.23 ms066,73682 µs2.22 ms041,205149 µs3.18 ms0
GetBlock6488,755351 µs3.95 ms089,051351 µs3.91 ms053,075705 µs5.87 ms0
GetBlock25673,6551.65 ms7.79 ms343,43373,3311.69 ms7.83 ms359,65652,6922.95 ms16.07 ms0
GetBlockNullifiers110,01936 µs593 µs53,9049,00136 µs591 µs78,39310,19842 µs760 µs0
GetBlockNullifiers1666,53979 µs934 µs352,75559,05778 µs922 µs504,50063,853104 µs1.33 ms0
GetBlockNullifiers6494,241269 µs1.75 ms476,55084,514246 µs1.67 ms716,75187,542361 µs2.75 ms0
GetBlockNullifiers25691,7951.33 ms4.92 ms545,37381,8181.28 ms4.79 ms718,77788,1821.52 ms9.24 ms0
GetBlockRange1124674 µs91.63 ms124,416178701 µs99.68 ms01661.28 ms100 ms0
GetBlockRange161,6621.13 ms181 ms01,6761.12 ms178 ms01,2003.06 ms222 ms0
GetBlockRange641,74626.32 ms244 ms01,76925.62 ms244 ms01,22218.65 ms657 ms0
GetBlockRange2561,703143 ms357 ms01,712143 ms355 ms01,20529.33 ms2.62 s0
GetBlockRangeNullifiers1464.16 ms280 ms0464.05 ms283 ms03111.15 ms288 ms0
GetBlockRangeNullifiers164757.30 ms487 ms04807.18 ms483 ms022735.14 ms562 ms0
GetBlockRangeNullifiers6451491.14 ms699 ms051591.48 ms695 ms0226152 ms2.23 s0
GetBlockRangeNullifiers256490484 ms1.05 s0497480 ms1.05 s0216703 ms6.45 s0
GetLatestBlock122,88718 µs51 µs023,23018 µs48 µs04,974162 µs319 µs0
GetLatestBlock16116,73753 µs297 µs0117,53553 µs284 µs034,895363 µs931 µs0
GetLatestBlock64168,329180 µs853 µs0173,631172 µs751 µs043,1271.27 ms3.16 ms0
GetLatestBlock256176,055614 µs3.01 ms0179,053580 µs2.88 ms042,3423.25 ms7.05 ms2,813
GetLatestTreeState115,10836 µs122 µs019,21324 µs94 µs02,887289 µs732 µs0
GetLatestTreeState1681,421106 µs471 µs0105,41166 µs347 µs022,445610 µs1.31 ms0
GetLatestTreeState6491,642528 µs1.61 ms0154,033208 µs899 µs026,6602.22 ms4.22 ms0
GetLatestTreeState25694,6652.29 ms6.01 ms0152,887697 µs3.55 ms025,7245.28 ms10.02 ms161
GetLightdInfo122,12420 µs60 µs022,32420 µs53 µs02,849304 µs571 µs0
GetLightdInfo16114,14256 µs318 µs0113,62856 µs306 µs023,467589 µs1.18 ms0
GetLightdInfo64166,420187 µs854 µs0168,005183 µs812 µs027,8062.15 ms4.39 ms0
GetLightdInfo256170,627629 µs3.15 ms0173,215614 µs3.00 ms026,8514.98 ms9.16 ms0
GetSubtreeRoots12,486356 µs705 µs42,2153,144358 µs716 µs01,378872 µs1.53 ms0
GetSubtreeRoots1620,541477 µs1.70 ms373,07022,850548 µs2.67 ms99,6821.54 ms3.47 ms0
GetSubtreeRoots6425,3021.24 ms3.66 ms497,23535,8941.52 ms4.14 ms2910,2335.11 ms19.63 ms0
GetSubtreeRoots25619,3826.10 ms15.11 ms669,92127,8405.22 ms11.89 ms294,58610,0879.24 ms123 ms2
GetTaddressBalance17,98193 µs178 µs06,559119 µs226 µs04,222201 µs362 µs0
GetTaddressBalance1653,528221 µs550 µs041,707296 µs739 µs029,527431 µs1.16 ms0
GetTaddressBalance6472,107671 µs2.11 ms055,643974 µs2.24 ms041,7321.31 ms3.16 ms0
GetTaddressBalance25672,1892.90 ms8.09 ms058,2711.74 ms202 ms042,0733.01 ms7.43 ms0
GetTransaction114,77140 µs92 µs06,572117 µs245 µs04,379189 µs375 µs0
GetTransaction1692,39288 µs375 µs045,590270 µs590 µs033,536377 µs964 µs0
GetTransaction64131,406275 µs1.10 ms069,435759 µs1.79 ms048,8351.09 ms2.76 ms0
GetTransaction256134,6691.33 ms4.46 ms070,9921.51 ms4.67 ms049,3702.50 ms6.19 ms15,911
GetTreeState15134 ms753 ms492302 ms1.25 s123301 ms1.32 s0
GetTreeState1633362 ms1.68 s21228408 ms2.27 s025467 ms2.27 s0
GetTreeState64351.19 s8.89 s22362198 ms7.06 s030843 ms10.21 s0
GetTreeState256316.28 s10.79 s2078110.90 ms9.06 s1,705222.73 s10.16 s491,456

Raw data

All scenario summaries · Request samples (without the 2 M samples of the 100-block concurrency suite) · ghz results · Resource samples · Index sizes · Phases · All numbers. The harness is in benchmarks/2026-11-lwd-comparison. The previous page, ztreamer v0.1.0 against v0.0.1, is archived here.