nautechsystems/nautilus_trader · error

Instrument close not found in Redis: {key}

Error message

Instrument close not found in Redis: {key}

What it means

load_instrument_closes reads a bulk set of keys from Redis and zips keys with values. If Redis returns None (nil) for a key that the instrument-close index listed — the key existed in the index but the value is missing — this error is thrown naming the missing key.

Source

Thrown at crates/infrastructure/src/redis/queries.rs:429

    /// Returns an error if scanning, reading, parsing, or deserializing instrument closes fails.
    pub async fn load_instrument_closes(
        con: &ConnectionManager,
        trader_key: &str,
        encoding: SerializationEncoding,
    ) -> anyhow::Result<AHashMap<InstrumentId, InstrumentClose>> {
        let prefix = format!("{trader_key}{REDIS_DELIMITER}{INSTRUMENT_CLOSES}{REDIS_DELIMITER}");
        let pattern = format!("{prefix}*");
        log::debug!("Loading {pattern}");

        let mut con = con.clone();
        let keys = Self::scan_keys(&mut con, pattern).await?;
        let values = Self::read_bulk(&con, &keys).await?;
        let mut closes = AHashMap::with_capacity(keys.len());

        for (key, value) in keys.into_iter().zip(values) {
            let instrument_id = parse_instrument_key(&key, &prefix)?;
            let value = value
                .ok_or_else(|| anyhow::anyhow!("Instrument close not found in Redis: {key}"))?;
            let close: InstrumentClose = Self::deserialize_payload(encoding, &value)?;
            anyhow::ensure!(
                close.instrument_id == instrument_id,
                "Instrument close key ID {instrument_id} did not match payload ID {}",
                close.instrument_id,
            );
            closes.insert(instrument_id, close);
        }

        log::debug!("Loaded {} instrument close(s)", closes.len());
        Ok(closes)
    }

    /// Loads all synthetic instruments for `trader_key` using the specified `encoding`.
    ///
    /// # Errors
    ///
    /// Returns an error if scanning keys or reading synthetic instrument data fails.

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check the named key in Redis (redis-cli GET) — if expired/deleted, re-write the instrument close.
  2. Rebuild or clean the instrument-close index so it no longer lists missing keys.
  3. Avoid TTLs on values while index entries have none, or set matching TTLs.
  4. Re-snapshot the cache from a fresh session to regenerate consistent index + values.

Example fix

// before: value keys with TTL, index without TTL -> nil slots on read
redis.setex(value_key, 3600, payload); redis.sadd(index_key, value_key);
// after: persist values (no TTL) while indexed, or purge index entries on expiry
redis.set(value_key, payload);
Defensive patterns

Strategy: validation

Validate before calling

// Rust: verify all indexed keys exist before loading closes
async fn all_keys_exist(con: &mut ConnectionManager, keys: &[String]) -> bool {
    let values: Vec<Option<Vec<u8>>> = redis::cmd("MGET").arg(keys)
        .query_async(con).await.unwrap_or_default();
    values.iter().all(|v| v.is_some())
}

Try / catch

match load_instrument_closes(&mut con, trader_key, encoding).await {
    Ok(closes) => closes,
    Err(e) if e.to_string().contains("not found in Redis") => {
        // purge stale index entry or re-write the missing value, then retry once
        rebuild_index(&mut con, trader_key).await?;
        load_instrument_closes(&mut con, trader_key, encoding).await?
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling load_instrument_closes / load_all when the Redis index of instrument-close keys contains a key whose value has expired (TTL), was deleted, or the bulk read (MGET) returned a nil slot.

Common situations: Values written with an expiry_ms TTL that later expired while the index entry persisted; manual deletion of value keys; Redis persistence restore that lost values but kept the index; replica with partial data.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/1bd4cbb990cce4e8. Report an issue: GitHub.