PostgreSQL B-tree lookups on an indexed compound key are fast — O(log n) — but backtest-kit performs thousands of read-by-context-key per second during backtests. At that volume, even sub-millisecond index scans accumulate into meaningful overhead. Redis turns every context-key lookup into an O(1)Documentation Index
Fetch the complete documentation index at: https://mintlify.com/theonetrade/backtest-kit-redis-postgres-pgpool-docker/llms.txt
Use this file to discover all available pages before exploring further.
GET, bringing steady-state read latency down to a single network round-trip per call regardless of table size.
What the Cache Stores
The cache stores only the row’s UUID (id), never the document itself. A typical entry looks like:
BaseMap: The ioredis Wrapper
BaseMap is a factory-generated class that accepts a connectionKey (namespace prefix) and a ttlExpireSeconds parameter. Every cache service in the project extends BaseMap with a fixed key and a TTL of -1 (no expiry), because identity caches must survive the full lifetime of a backtest run.
Namespaced keys
Every key is prefixed with
${connectionKey}: so multiple cache services can coexist in the same Redis instance without key collisions. For example, signal_cache:* and candle_cache:* are entirely separate key spaces.TTL = -1 for identity caches
Passing
-1 as ttlExpireSeconds skips the EXPIRE call entirely. Identity caches (all 16 domain cache services) use -1 — entries persist until the Redis server restarts or clear() is called explicitly.SCAN-based iteration
toArray, keys, values, size, and clear all use cursor-based SCAN with a batch size of 100 instead of KEYS, avoiding O(n) blocking on the Redis event loop for large key sets.Default 5-minute TTL
The
DEFAULT_TTL_EXPIRE_SECONDS constant is 5 minutes (300 s). Any BaseMap subclass that does not pass a TTL argument gets auto-expiring keys — useful for transient caches that should not grow unbounded.SignalCacheService: A Concrete Example
Each domain cache service extendsBaseMap with a typed key-builder and domain-specific get/set methods. Here is SignalCacheService in full:
signal_cache:binance:Jan2026Strategy:TRXUSDT → "a3f7c2e1-…".
The Read Path in Detail
SignalDbService.findByContext implements the complete cache-aware read path:
- Cache Hit (Steady State)
- Cache Miss (Cold Start)
- After Upsert (Write Path)
Two operations, both O(1):
redis.GET signal_cache:binance:Jan2026Strategy:TRXUSDT→ returns UUID stringSELECT * FROM "signal-items" WHERE id = $1→ primary-key index scan
uuid column with cardinality 1, which PostgreSQL executes in O(1) time regardless of table size.Cold Start and Redis Restart
When Redis restarts, every cache key is lost. On the nextfindByContext call for any context key, getSignalId returns null, the code falls through to the Postgres filter, fetches the row, and calls setSignalId to repopulate the cache. The process repeats for each unique context key on first access.
A Redis restart causes zero data loss. All records remain in PostgreSQL. The only consequence is a temporary increase in Postgres load as the cache warms back up on first access per context key. Warm-up is complete within the first full pass of the backtest’s context key space.
getRedis():