Moving updated-at timestamps to unique millisecond times (#17489)

To allow the ability to have guaranteed-unique cursors, we make two
important updates:
* Add new updated_at_ms and created_at_ms columns that are in
millisecond precision
* Guarantee uniqueness -- if multiple items are inserted at the same
millisecond, bump the new one by one millisecond until it becomes unique

This lets us use single-number cursors for forwards and backwards paging
through resultsets and guarantee that the cursor is a fixed point to do
(timestamp > cursor) and get new items only.

This updated implementation is backwards-compatible since multiple
appservers can be running and won't handle the previous method well.
This commit is contained in:
David de Regt authored and GitHub committed 2026-04-14 11:55:34 -04:00
1 parent 61fe23159e
commit 4f2fc3e3fa
13 files changed
+470 -167

No files matched your search

+8 -7
View File
@@ -71,7 +71,6 @@ pub struct ThreadItem {
/// created_at comes from the filename timestamp with second precision.
pub created_at: Option<String>,
/// RFC3339 timestamp string for the most recent update (from file mtime).
/// updated_at is truncated to second precision to match created_at.
pub updated_at: Option<String>,
}
@@ -292,7 +291,10 @@ impl<'de> serde::Deserialize<'de> for Cursor {
impl From<codex_state::Anchor> for Cursor {
fn from(anchor: codex_state::Anchor) -> Self {
let ts = OffsetDateTime::from_unix_timestamp(anchor.ts.timestamp())
let ts = anchor
.ts
.timestamp_nanos_opt()
.and_then(|nanos| OffsetDateTime::from_unix_timestamp_nanos(nanos as i128).ok())
.unwrap_or(OffsetDateTime::UNIX_EPOCH);
Self::new(ts, anchor.id)
}
@@ -1156,17 +1158,16 @@ async fn file_modified_time(path: &Path) -> io::Result<Option<OffsetDateTime>> {
return Ok(None);
};
let dt = OffsetDateTime::from(modified);
// Truncate to seconds so ordering and cursor comparisons align with the
// cursor timestamp format (which exposes seconds), keeping pagination stable.
Ok(truncate_to_seconds(dt))
Ok(truncate_to_millis(dt))
}
fn format_rfc3339(dt: OffsetDateTime) -> Option<String> {
dt.format(&Rfc3339).ok()
}
fn truncate_to_seconds(dt: OffsetDateTime) -> Option<OffsetDateTime> {
dt.replace_nanosecond(0).ok()
fn truncate_to_millis(dt: OffsetDateTime) -> Option<OffsetDateTime> {
let millis_nanos = (dt.nanosecond() / 1_000_000) * 1_000_000;
dt.replace_nanosecond(millis_nanos).ok()
}
async fn find_thread_path_by_id_str_in_subdir(