S3 lists objects lexicographically — it has no concept of insertion time and cannot answer “what were the last N records added?” without scanning the entire prefix. For Log, Notification, and Storage entries (the three entities whoseDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/theonetrade/backtest-kit-minio-s3-docker/llms.txt
Use this file to discover all available pages before exploring further.
readXData methods return a newest-first window), a Redis index sidesteps this limitation. The design is deliberately lightweight: no sorted sets, no Lua scripts — just plain per-minute Redis SETs and a single floor key.
Only Log, Notification, and Storage use the Redis time index. The remaining 13 adapters read directly from MinIO using deterministic keys and never touch Redis for listings.
Per-Minute SET Model
Each of the three connection services —LogConnectionService, NotificationConnectionService, and StorageConnectionService — maintains its own namespace of Redis keys. For Log entries, the connection key is log-items-connection; for Notifications, notification-items-connection; for Storage, storage-items-connection.
When a new object is written to MinIO, register(objectName) is called immediately afterward:
LogConnectionService.ts
SADDaddsobjectNameto the SET for the current minute bucket. If the same name is registered twice within the same minute (e.g. during a fast backtest replay),SADDdeduplicates it automatically.SETNXwrites the floor marker — the timestamp of the very first minute that ever had a registration — but only if it has not been set before. This bounds the backwards walk inlistNewestso it never scans into empty history.
TIMESTAMP_PAD characters (16 digits). For example, a minute boundary near timestamp 1700000000000 produces a 16-digit key:
MAX_SAFE_INTEGER − ms) for newest-first lexicographic ordering.
listNewest(limit, prefix): Backwards Walk
listNewest reconstructs the most recent limit object names by walking backwards from the current minute to the floor marker, probing minutes in batches of 1 000 to minimise round trips:
LogConnectionService.ts
collect helper applies the optional prefix filter, deduplicates via a seen Set, and returns true (early exit) once limit is satisfied.
Why SCARD First?
A minute that had no registrations is simply absent from Redis —SCARD returns 0. By pipelining 1 000 SCARD commands in a single round trip, the walk can skip an entire year of empty minutes (525 600 minutes) in ~526 round trips, each skipping 1 000 empty minutes for free. Contrast this with a naive SCAN over the full keyspace, which would transfer thousands of key names just to find the populated ones.
Hot-Minute Handling
During a fast backtest replay, many log entries can land in the same wall-clock minute. A minute SET with more than 1 000 members is considered “hot” and paged withSSCAN + early exit, so the caller never needs to pull an entire hot-minute’s worth of names when limit is already satisfied.
Floor Marker
The floor key<entity>-connection:floor stores the millisecond timestamp (as a string) of the first-ever registration. The backwards walk terminates at this boundary — it never descends below it — which means the walk cost is proportional to the age of the most recent limit entries, not to the total history in the bucket.
SETNX ensures the floor is written only once and never overwritten, even if register is called millions of times afterward.
Cold-Index Fallback
If Redis is flushed (restarted with--no-appendonly, replaced with a new container, or explicitly FLUSHALL-ed), listNewest returns an empty array because the floor key is absent. LogDataService.listAll detects this and falls back to a direct MinIO bucket listing:
LogDataService.ts
MAX_SAFE_INTEGER − ms, zero-padded), listObjectsV2 naturally returns them in newest-first lexicographic order. The cold path reads the most recent window and rebuilds the Redis index for the next call — one cold listing is all it takes to recover full performance.
Redis Key Summary
| Key pattern | Type | Content | TTL |
|---|---|---|---|
log-items-connection:<minute> | SET | Object names registered in that minute | none |
log-items-connection:floor | String | Earliest minute timestamp ever registered | none |
notification-items-connection:<minute> | SET | Notification object names | none |
notification-items-connection:floor | String | Earliest notification minute | none |
storage-items-connection:<minute> | SET | Storage object names | none |
storage-items-connection:floor | String | Earliest storage minute | none |
ttlExpireSeconds = -1 in the BaseMap constructor), so they persist until Redis is flushed or the process explicitly clears them.