Every persist adapter inDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/backtest-kit/backtest-kit-redis-mongo-docker/llms.txt
Use this file to discover all available pages before exploring further.
backtest-kit-redis-mongo-docker upholds a strict write durability contract: once writeXData() returns without throwing, the very next readXData() call — whether it hits Redis or MongoDB — must observe the value that was just written. Fulfilling this contract in a concurrent environment (multiple strategy workers, paper trading alongside a backtest) requires the database layer to be atomically correct, not just eventually consistent.
The Naïve Approach Fails Under Concurrency
A common first attempt at MongoDB persistence looks like this:findOne before either has written, both see “no existing document” and both attempt create. The second insert crashes with E11000 Duplicate Key Error because the compound unique index rejects the conflicting document. Worse, without a retry loop the write is silently lost.
The Atomic Upsert Pattern
AllXDbService classes replace the two-step check-then-act with a single findOneAndUpdate call. For SignalDbService:
findOneAndUpdate with upsert: true is atomic at the document level: the find and the write are a single operation inside the storage engine. No two callers can both observe “missing” and both insert — only one succeeds; the other silently updates the same document.
Four Key Properties
Every upsert in the codebase relies on the same four guarantees: 1. Filter shape matches the compound unique index Thefilter passed to findOneAndUpdate always uses exactly the fields that form the schema’s unique compound index. For SignalModel:
$set for mutable fields
Using $set (rather than a full replacement) means concurrent writers that race to the same document will converge on the latest value rather than trampling each other’s writes. The payload field — the actual strategy data — is always written via $set.
3. new: true returns the mutated document for immediate cache update
The new: true option makes MongoDB return the post-update state of the document. The service uses this return value directly to update the Redis cache in the same synchronous critical section:
new: true is set, the Redis entry always reflects the document that MongoDB just committed — never a stale pre-update snapshot.
4. setDefaultsOnInsert: true applies Mongoose schema defaults
On the first insert (upsert path) Mongoose schema defaults would normally be skipped if only $set and $unset operators are provided. setDefaultsOnInsert: true re-enables them, so fields like positions: [] on RiskSchema are populated correctly for newly created documents.
Soft-Delete Pattern
Adapters that support logical deletion — Interval, Memory, and Measure — use asoftRemove method that sets removed: true without physically deleting the MongoDB document. This preserves audit history and allows the Redis cache entry to reflect the deletion:
MemoryDbService.softRemove and MeasureDbService.softRemove. Read methods check row.removed and return null for soft-deleted entries, which backtest-kit treats the same as a missing record.
Candle immutability — This guarantees that a second ingest run for the same data range cannot corrupt historical prices.
$setOnInsert instead of $setCandleDbService is the one exception to the $set rule. OHLCV candle data is immutable: once a candle for a given (symbol, interval, timestamp) tuple is written, it must never change. CandleDbService.create therefore uses $setOnInsert, which writes the OHLCV fields only when inserting a brand-new document and is a no-op on subsequent upserts: