Standalone binaries that validate v1.7.0's chunked eviction + admission control + client retry against a real smartbotic-database instance.
Not unit tests — each one runs a real server on a configurable port, drives a workload, and reports a PASS/FAIL verdict. Not wired into CTest — these are operator/developer validation tools, not CI.
Four load-test binaries:
load_test — pressure + retry validation (the original)load_test_mixed — concurrent reads + writes under pressureload_test_integrity — byte-exact integrity of evicted + paged-in docsload_test_pinned — pinned=true collection protectionAll four talk to localhost:9005 and use Client::Config with transport-level retries. They differ only in workload shape and verdict criteria.
smartbotic-database package installed (for the server binary)libsmartbotic-db-client-dev installed (for headers + shared lib)cd tests/load_test
g++ -std=c++20 -O2 -I/usr/include load_test.cpp \
-lsmartbotic-db-client \
$(pkg-config --libs grpc++) \
-lspdlog -lfmt -pthread \
-o load_test
Uses port 9005 and data dir /tmp/smartbotic-loadtest/ to avoid conflicting with any existing instance on the default port 9004.
# 1. Start the test server with its own config (memory capped at 32 MB)
mkdir -p /tmp/smartbotic-loadtest/data /tmp/smartbotic-loadtest/files
smartbotic-database --config ./config.json > /tmp/smartbotic-loadtest/server.log 2>&1 &
SERVER_PID=$!
sleep 2
# 2. Run the load test — default: 20s, 4 writers, 2 KB docs
./load_test [duration_seconds] [writer_threads] [doc_bytes]
# Example — 30s with 8 writers:
./load_test 30 8 2048
# 3. Stop the server
kill $SERVER_PID
# 4. Inspect eviction events
grep -E "Eviction|MEMORY_" /tmp/smartbotic-loadtest/server.log
Sample output from a successful run (32 MB cap, 20s, 4 writers, 2 KB docs):
[t=18504ms] pressure= hard 84% mem=27584KB live= 5276 evicted= 41580 inserts= 46856 fails= 0
=== Summary ===
Duration: 20162ms
Successful inserts: 51444 (2551 ops/s)
Failed (after retries): 0
Pressure timeline:
normal: 1.5s
soft: 5.0s
hard: 9.0s
emergency: 2.0s
Transitions: 19
Verdict: PASS — client-side retry absorbed all transient errors
| Signal | What it means |
|---|---|
Eviction chunk: N docs evicted, X bytes freed |
T4 chunked eviction is working |
Eviction: nothing evictable this pass, backing off |
T4 hot-write floor is protecting in-flight upserts |
Emitted MEMORY_PRESSURE_HIGH |
T10 edge-triggered event fired |
Insert rejected: memory pressure emergency |
T7 admission control engaged |
Client::insert ...; retrying in Nms (attempt K/5) |
T11 client retry kicked in |
Successful inserts: N == attempts, Failed: 0 |
No writes lost under pressure |
Edit config.json:
hot_write_floor_ms to 500 ms so eviction can actually drop recent writeseviction_chunk_pause_ms to 200 msmemory_emergency_percent to 99max_memory_mb to 256 to see if the chunked approach still works at scaleload_test_mixed)Validates that reads stay responsive while writers + eviction are running. Three writer threads continuously insert 2 KB docs into a shared list of recent IDs; five reader threads pick random IDs from that list and call get(). Reads hit both live docs and evicted-stub IDs, exercising the WAL-fallback paging path.
g++ -std=c++20 -O2 -I/usr/include load_test_mixed.cpp \
-lsmartbotic-db-client $(pkg-config --libs grpc++) \
-lspdlog -lfmt -pthread -o load_test_mixed
# Same server setup as load_test:
mkdir -p /tmp/smartbotic-loadtest/data /tmp/smartbotic-loadtest/files
smartbotic-database --config ./config.json > /tmp/smartbotic-loadtest/server.log 2>&1 &
SERVER_PID=$!
sleep 2
./load_test_mixed [duration_sec=30] [writers=3] [readers=5] [doc_bytes=2048]
kill $SERVER_PID
not-found is counted separately from failures — it's a legitimate cold miss from a freshly-evicted stub where the WAL page hasn't been pulled yet, not a bug.
load_test_integrity)Writes 10,000 docs with deterministic makeContent(i) bodies (~1.5 KB each, content includes the id so misdirected reads also fail), waits 3 s for eviction to settle, then reads every doc back and compares byte-for-byte. Runs sequentially — no concurrency on verify, by design.
At 32 MB cap and ~15 MB of data plus overhead, eviction will fire; the verify phase exercises the WAL page-in path for every doc.
g++ -std=c++20 -O2 -I/usr/include load_test_integrity.cpp \
-lsmartbotic-db-client $(pkg-config --libs grpc++) \
-lspdlog -lfmt -pthread -o load_test_integrity
Uses the same config.json as the base test.
mkdir -p /tmp/smartbotic-loadtest/data /tmp/smartbotic-loadtest/files
smartbotic-database --config ./config.json > /tmp/smartbotic-loadtest/server.log 2>&1 &
SERVER_PID=$!
sleep 2
./load_test_integrity [num_docs=10000] [stabilize_sec=3]
kill $SERVER_PID
content field byte-exact matches makeContent(i)If the summary says no eviction stubs present, the budget is too loose for your dataset size — either bump num_docs or lower max_memory_mb in the config so eviction actually fires during the run.
load_test_pinned)Confirms that pinned=true collections are never evicted, even under sustained pressure on other collections. Needs a different server config because the settings collection is created pinned by a migration — createCollection via the client API doesn't expose pinned yet.
g++ -std=c++20 -O2 -I/usr/include load_test_pinned.cpp \
-lsmartbotic-db-client $(pkg-config --libs grpc++) \
-lspdlog -lfmt -pthread -o load_test_pinned
# 1. Set up data dir + migrations dir + copy the migration file.
mkdir -p /tmp/smartbotic-loadtest-pinned/{data,files,migrations}
cp migrations/001_create_pinned_settings.json /tmp/smartbotic-loadtest-pinned/migrations/
# 2. Start the server with the pinned-config. Migration auto-applies on startup.
smartbotic-database --config ./config_pinned.json > /tmp/smartbotic-loadtest-pinned/server.log 2>&1 &
SERVER_PID=$!
sleep 2
# 3. Run the test.
./load_test_pinned [duration_sec=30] [bulk_writers=4] [num_settings=200]
# 4. Stop.
kill $SERVER_PID
settings.documentCount == 200 (all pinned docs still present)settings.evictedStubCount == 0 (pinned docs never evicted)bulk.evictedStubCount > 0 (proves pressure actually fired — defensive sanity check)If bulk.evictedStubCount is 0, either the run was too short, or the bulk workload produced less than ~32 MB — raise duration_sec or bulk_writers.