fix(manager-core): F-P-005 keyset cursor on execution_logs list_for_script
list_for_script used ORDER BY created_at DESC LIMIT $2 OFFSET $3.
Postgres has to scan + discard OFFSET rows on every page, so deep
pagination on a script with many log rows gets linearly slower. Every
sister-table list endpoint in the repo already uses cursor pagination
— this is the outlier.
Add ExecutionLogCursor { created_at, id } with `<rfc3339>_<uuid>`
encode/decode. New trait signature:
list_for_script(script_id, limit, cursor: Option<ExecutionLogCursor>)
Predicate: WHERE script_id=$1 AND (created_at, id) < ($cursor_ts, $cursor_id).
ORDER BY also gains `id DESC` for deterministic ordering at equal-time
boundaries.
API surface:
- /api/v1/admin/scripts/{id}/logs?cursor=<token>&limit=50 is the new
shape (dashboards adopt later — separate finding F-U-012).
- Legacy `offset` query param accepted-and-ignored to keep older
dashboards from 400'ing during rollout.
AUDIT.md anchor: F-P-005.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -375,8 +375,14 @@ async fn delete_script<R: ScriptRepository, L: ExecutionLogRepository>(
|
||||
pub struct LogsQuery {
|
||||
#[serde(default = "default_limit")]
|
||||
pub limit: i64,
|
||||
/// F-P-005: keyset cursor (`<rfc3339>_<uuid>`). Replaces the OFFSET
|
||||
/// path that scanned + discarded N rows per page. Absent for the
|
||||
/// first page. The legacy `offset` query param is accepted but
|
||||
/// silently ignored to keep older dashboards from 400'ing.
|
||||
#[serde(default)]
|
||||
pub offset: i64,
|
||||
pub cursor: Option<String>,
|
||||
#[serde(default, rename = "offset")]
|
||||
pub _legacy_offset: Option<i64>,
|
||||
}
|
||||
|
||||
const fn default_limit() -> i64 {
|
||||
@@ -399,8 +405,11 @@ async fn list_logs<R: ScriptRepository, L: ExecutionLogRepository>(
|
||||
// Cap to keep the dashboard responsive; the data plane writes are
|
||||
// unbounded over time so a paged read is the only sane default.
|
||||
let limit = q.limit.clamp(1, 200);
|
||||
let offset = q.offset.max(0);
|
||||
let logs = state.logs.list_for_script(id, limit, offset).await?;
|
||||
let cursor = q
|
||||
.cursor
|
||||
.as_deref()
|
||||
.and_then(crate::repo::ExecutionLogCursor::decode);
|
||||
let logs = state.logs.list_for_script(id, limit, cursor).await?;
|
||||
Ok(Json(logs))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user