Scalability Analysis of personal_world.SetChunkVerifier on MAP State
TL;DR
SetChunkVerifieris highly sensitive to the size of the map stored within a world.- It works at
N=10000, but atN=100000the very first call fails with an out-of-gas error onCPUCycles. - CPU cost grows much faster than storage usage.
- Even in the precondition batches,
gas_per_itemincreases by about 10x, which indicates a nonlinear cost pattern.
Conclusion: In the current structure, large-scale maps are not practical. CPU gas becomes the first bottleneck well before storage does.
The full dataset is available here:
Structure Summary
This experiment does not use a simple flat map. Instead, it uses the following layered structure:
- Outer layer:
AVL(worldID -> value) - Inner value:
map[string]string
SetChunkVerifier works as follows:
- Look up the verifier map from the outer AVL using
worldID. - Add or overwrite a value in
map[coordKey]verifierfor that world.
Experiment setup:
- A single verifier world is fixed throughout the test.
- The number of entries in the map for that same world is continuously increased.
In other words, N in this experiment refers to the size of a single map, not the number of worlds.
Experiment Environment
- Runtime: custom
gnodevbuild - Branch:
dev/jae/gas-model-improvements - Commit:
2d507b86935576c6eb5b06cae3b14861f0090c87
Result Summary (Milestones)
| Milestone N | Status | Result |
|---|---|---|
10000 | Success | gas_avg=51,448,966, storage_avg=14,300, elapsed_sec=4.965 |
100000 | Failed | iter=0, types.OutOfGasError{Descriptor:"CPUCycles"} |
1000000 | Skipped | Stopped after the failure at 100000 |
At 100k, even a single call fails immediately, which confirms that CPU cost is the limiting factor.
Key Observations
1. CPU Becomes the Bottleneck Before Storage
storage_per_itemremains nearly constant.gas_per_itemkeeps increasing.- The out-of-gas error is caused by
CPUCycles.
This indicates that the system reaches its limit because of CPU cost, not because of storage writes.
2. Cost Growth Is Nonlinear
Comparison across precondition batches:
gas_per_itemincreases by about 10.7xelapsed_secincreases by about 6xstorage_per_itemshows almost no change
As the map grows, the processing cost does not increase linearly.
3. Performance Degrades During the Precondition Phase
- Batch cost keeps increasing even before the single-call measurement begins.
- This suggests the issue starts with growth in the map itself, not only at the moment of read or write.
Precondition Generation Method
- Batch size:
200 - Final batch size:
190 - Call method:
SetChunkVerifiers(batch)
Precondition Batch Trend
| batch | n | gas_total | storage_total | gas_per_item | storage_per_item | elapsed_sec |
|---|---|---|---|---|---|---|
| #1 | 200 | 49808772 | 2851000 | 249043 | 14255 | 0.204 |
| #2 | 200 | 54151669 | 2740700 | 270758 | 13703 | 0.221 |
| #3 | 200 | 55031235 | 2740000 | 275156 | 13700 | 0.206 |
| #10 | 200 | 64170751 | 2780000 | 320853 | 13900 | 0.217 |
| #50 | 200 | 99880364 | 2780200 | 499401 | 13901 | 0.301 |
| #100 | 200 | 148455536 | 2820000 | 742277 | 14100 | 0.397 |
| #200 | 200 | 238737326 | 2820000 | 1193686 | 14100 | 0.613 |
| #300 | 200 | 329144474 | 2820000 | 1645722 | 14100 | 0.805 |
| #400 | 200 | 419732630 | 2820000 | 2098663 | 14100 | 1.030 |
| #500 | 190 | 507692403 | 2679200 | 2672065 | 14101 | 1.246 |
Even with the same batch size, the cost continues to rise over time.
The full batch history is available here:
Estimated Store Calls Per Single Invocation
Based on static analysis, not VM instrumentation
StoreGetObject:[hypothesis] 4..7StoreSetObject:[hypothesis] 4..7
Reasoning:
worldIDlookup in the AVL- loading and modifying the map object
- storing dirty objects and the owner chain
The dominant factor appears to be the growing internal map operations and serialization cost rather than the raw number of objects.
Parameter Sizes
Approximate payload estimates based on the function contract and key normalization logic
SetChunkVerifier(worldID, chunkKey, verifier) accepts three logical inputs, but the stored payload is slightly smaller than the request payload because chunkKey is normalized before insertion.
-
worldID- Logical type:
uint32 - About 4 bytes as a numeric value
- Used to look up the outer AVL entry for the world
- Not duplicated in every inner map entry
- Logical type:
-
chunkKey- Format:
"<worldID>:0_<index>" - Includes the world prefix in the request
- Normalized before storage, so only the coordinate portion (
"0_<index>") is kept in the inner map - Rough request size: about
5~10 bytes - Rough stored key size after normalization: about
3~8 bytes
- Format:
-
verifier- Measurement value:
"measure_v"(9 bytes) - Precondition value:
"v_<i>"(3~8 bytes)
- Measurement value:
Approximate Size Per Set
-
Request payload per
SetChunkVerifiercall:- approximately
12~23 bytesof raw logical content - calculated as
worldID(~4 bytes) +chunkKey(510 bytes) +verifier(39 bytes)
- approximately
-
Stored payload per inner map entry:
- approximately
6~17 bytesof raw string content - calculated as normalized
coordKey(38 bytes) +verifier(39 bytes)
- approximately
-
Per-world shared key:
- the outer AVL stores
worldIDonce per world as a separate lookup key
- the outer AVL stores
This means the per-entry stored payload is relatively small. The main issue is unlikely to be the raw byte size of each value itself, and is more likely tied to map growth, object serialization, and repeated internal processing as the map becomes larger.
Conclusion
personal_world.SetChunkVerifier shows the following characteristics:
- It is highly sensitive to map size growth.
- CPU cost grows much faster than storage cost.
- A practical limit is already reached around
100k. - Cost accumulation begins during the precondition phase.
Overall, as map size increases, CPU cost rises quickly enough to make large-scale map usage impractical in the current structure. CPU gas becomes the bottleneck before storage does.