fix(ccr): preserve exact SQLite TTL boundary (#2669)

## Description

SQLite CCR timestamps have whole-second resolution. Expiring a row when
`last_accessed + ttl == now` or `created_at + max_lifetime == now` can
shorten the configured lifetime by almost one second. This change keeps
entries valid at the exact boundary and expires them one second later.

## Type of Change

- [x] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)

## Changes Made

- Use strict expiration predicates for idle TTL and maximum lifetime.
- Keep lookup predicates valid at the exact boundary.
- Add deterministic fixed-time tests for both boundaries.

## Testing

- [ ] Unit tests pass (`pytest`)
- [ ] Linting passes (`ruff check .`)
- [ ] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [x] Manual testing performed

### Test Output

```text
running 3 tests
test ccr::backends::sqlite::tests::exact_max_lifetime_boundary_is_still_valid ... ok
test ccr::backends::sqlite::tests::exact_idle_ttl_boundary_is_still_valid ... ok
test transforms::code_compressor::tests::english_exact_token_match_unchanged ... ok

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 911 filtered out
```

## Real Behavior Proof

- Environment: Linux x86_64, repository Rust toolchain.
- Exact command / steps: `cargo test -p headroom-core exact_`
- Observed result: Both SQLite boundary tests returned the stored
payload at the exact configured boundary and removed it one second
later. The focused command passed 3/3 selected tests, including one
unrelated existing exact-token test.
- Not tested: Live provider or model traffic; the change is isolated to
the deterministic Rust SQLite backend.

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

## Checklist

- [x] My code follows the project style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented the boundary behavior
- [ ] I have made corresponding documentation changes (not applicable;
behavior and tests are local to the backend)
- [x] My changes generate no new warnings
- [x] I have added tests that prove the fix is effective
- [x] New and existing relevant tests pass locally with my changes
- [x] I did not edit `CHANGELOG.md`

## Screenshots (if applicable)

Not applicable.

## Additional Notes

Rust formatting and `headroom-core` Clippy pass. A broader package run
passed 994 tests with three ignored; two unrelated ONNX parity tests
were excluded after reproducing their pre-existing futex stall.
This commit is contained in:
Agistaris 2026-08-04 20:18:22 -07:00 committed by GitHub
parent a97b82413b
commit d0a86d409f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -16,8 +16,8 @@
//! The TTL is an **idle window** (#2604): every successful `get` //! The TTL is an **idle window** (#2604): every successful `get`
//! restarts the row's clock via `last_accessed`, bounded by an absolute //! restarts the row's clock via `last_accessed`, bounded by an absolute
//! max lifetime measured from `created_at`. On every `get` we //! max lifetime measured from `created_at`. On every `get` we
//! lazy-purge stale rows (`WHERE last_accessed + ttl_seconds <= now OR //! lazy-purge stale rows (`WHERE last_accessed + ttl_seconds < now OR
//! created_at + max_lifetime <= now`) — no background reaper thread, //! created_at + max_lifetime < now`) — no background reaper thread,
//! no cron. DBs created by pre-sliding builds are migrated in place //! no cron. DBs created by pre-sliding builds are migrated in place
//! (the `last_accessed` column is added, backfilled from `created_at`). //! (the `last_accessed` column is added, backfilled from `created_at`).
//! //!
@ -153,10 +153,13 @@ impl SqliteCcrStore {
/// absolute max lifetime. Lazy — invoked from `get`. Returns the /// absolute max lifetime. Lazy — invoked from `get`. Returns the
/// number of rows purged. /// number of rows purged.
fn purge_expired(&self, conn: &Connection, now: u64) -> rusqlite::Result<usize> { fn purge_expired(&self, conn: &Connection, now: u64) -> rusqlite::Result<usize> {
// Timestamps have whole-second resolution. Use a strict boundary so
// truncation can extend a cache entry by less than one second but can
// never expire it before the configured idle or lifetime window.
let purged = conn.execute( let purged = conn.execute(
"DELETE FROM ccr_entries "DELETE FROM ccr_entries
WHERE last_accessed + ttl_seconds <= ?1 WHERE last_accessed + ttl_seconds < ?1
OR created_at + ?2 <= ?1", OR created_at + ?2 < ?1",
params![now as i64, self.max_lifetime_seconds as i64], params![now as i64, self.max_lifetime_seconds as i64],
)?; )?;
Ok(purged) Ok(purged)
@ -170,6 +173,58 @@ impl SqliteCcrStore {
.map(|d| d.as_secs()) .map(|d| d.as_secs())
.unwrap_or(0) .unwrap_or(0)
} }
fn get_at(&self, hash: &str, now: u64) -> Option<String> {
let conn = self.conn.lock().expect("ccr sqlite mutex poisoned");
// Lazy purge sweep, then the real lookup. Both happen under
// the same mutex so the row we read is guaranteed not to have
// been just-deleted by another caller.
if let Err(err) = self.purge_expired(&conn, now) {
tracing::warn!(
target = "ccr.sqlite",
error = %err,
"ccr_sqlite_purge_failed"
);
}
let row: Option<Vec<u8>> = conn
.query_row(
"SELECT original FROM ccr_entries
WHERE hash = ?1
AND last_accessed + ttl_seconds >= ?2
AND created_at + ?3 >= ?2",
params![hash, now as i64, self.max_lifetime_seconds as i64],
|r| r.get::<_, Vec<u8>>(0),
)
.optional()
.unwrap_or_else(|err| {
tracing::warn!(
target = "ccr.sqlite",
hash = %hash,
error = %err,
"ccr_sqlite_get_failed"
);
None
});
let row = row?;
// Sliding idle window (#2604): a successful hit restarts the
// row's idle clock. Still under the same mutex as the lookup.
if let Err(err) = conn.execute(
"UPDATE ccr_entries SET last_accessed = ?2 WHERE hash = ?1",
params![hash, now as i64],
) {
tracing::warn!(
target = "ccr.sqlite",
hash = %hash,
error = %err,
"ccr_sqlite_touch_failed"
);
}
String::from_utf8(row).ok()
}
} }
impl CcrStore for SqliteCcrStore { impl CcrStore for SqliteCcrStore {
@ -210,56 +265,7 @@ impl CcrStore for SqliteCcrStore {
} }
fn get(&self, hash: &str) -> Option<String> { fn get(&self, hash: &str) -> Option<String> {
let now = Self::now_unix_seconds(); self.get_at(hash, Self::now_unix_seconds())
let conn = self.conn.lock().expect("ccr sqlite mutex poisoned");
// Lazy purge sweep, then the real lookup. Both happen under
// the same mutex so the row we read is guaranteed not to have
// been just-deleted by another caller.
if let Err(err) = self.purge_expired(&conn, now) {
tracing::warn!(
target = "ccr.sqlite",
error = %err,
"ccr_sqlite_purge_failed"
);
}
let row: Option<Vec<u8>> = conn
.query_row(
"SELECT original FROM ccr_entries
WHERE hash = ?1
AND last_accessed + ttl_seconds > ?2
AND created_at + ?3 > ?2",
params![hash, now as i64, self.max_lifetime_seconds as i64],
|r| r.get::<_, Vec<u8>>(0),
)
.optional()
.unwrap_or_else(|err| {
tracing::warn!(
target = "ccr.sqlite",
hash = %hash,
error = %err,
"ccr_sqlite_get_failed"
);
None
});
let row = row?;
// Sliding idle window (#2604): a successful hit restarts the
// row's idle clock. Still under the same mutex as the lookup.
if let Err(err) = conn.execute(
"UPDATE ccr_entries SET last_accessed = ?2 WHERE hash = ?1",
params![hash, now as i64],
) {
tracing::warn!(
target = "ccr.sqlite",
hash = %hash,
error = %err,
"ccr_sqlite_touch_failed"
);
}
String::from_utf8(row).ok()
} }
fn len(&self) -> usize { fn len(&self) -> usize {
@ -271,3 +277,56 @@ impl CcrStore for SqliteCcrStore {
.unwrap_or(0) .unwrap_or(0)
} }
} }
#[cfg(test)]
mod tests {
use super::*;
fn store_with_row(
idle_ttl: u64,
max_lifetime: u64,
created_at: u64,
last_accessed: u64,
) -> (tempfile::TempDir, SqliteCcrStore, String) {
let dir = tempfile::tempdir().expect("tempdir");
let store =
SqliteCcrStore::open_with_ttls(dir.path().join("ccr.sqlite"), idle_ttl, max_lifetime)
.expect("open sqlite store");
let hash = "boundary-entry".to_string();
{
let conn = store.conn.lock().expect("ccr sqlite mutex poisoned");
conn.execute(
"INSERT INTO ccr_entries
(hash, original, created_at, ttl_seconds, last_accessed)
VALUES (?1, ?2, ?3, ?4, ?5)",
params![
&hash,
b"payload".as_slice(),
created_at as i64,
idle_ttl as i64,
last_accessed as i64,
],
)
.expect("insert boundary row");
}
(dir, store, hash)
}
#[test]
fn exact_idle_ttl_boundary_is_still_valid() {
let (_dir, store, hash) = store_with_row(5, 20, 100, 100);
assert_eq!(store.get_at(&hash, 105).as_deref(), Some("payload"));
assert_eq!(store.get_at(&hash, 111), None);
assert_eq!(store.len(), 0, "expired row must be purged");
}
#[test]
fn exact_max_lifetime_boundary_is_still_valid() {
let (_dir, store, hash) = store_with_row(5, 10, 100, 108);
assert_eq!(store.get_at(&hash, 110).as_deref(), Some("payload"));
assert_eq!(store.get_at(&hash, 111), None);
assert_eq!(store.len(), 0, "expired row must be purged");
}
}