Repository navigation
perf(workbook): select stale refresh cells with bounded SQL - #97
Merged
Merged
Conversation
Refresh no longer loads every workbook row and enrichment overlay into Python to decide which cells are stale. _stale_row_columns keyset-paginates workbook_rows.id and, per page, cross-joins the rows with the enrichment column ids (each with its own TTL cutoff) and LEFT JOINs the overlays on (workbook_id, COALESCE(lead_id, id), column_id), returning only stale pairs: missing overlay, non-complete or NULL status, NULL updated_at, or updated_at older than the cutoff. Cutoffs bind as naive UTC to match the column on SQLite and PostgreSQL. Every page is scanned, so results are identical to the previous loop; WORKBOOK_REFRESH_SCAN_BATCH_SIZE (default 1000) only bounds per-query work. Part of #33. Signed-off-by: debpalash <debash67@outlook.in> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #33 (cheap fix: move refresh stale-row selection into bounded SQL).
What changed
_stale_row_columnsinapps/api/services/workbook/refresh.pyused to load all workbook rows and all overlays into Python. It now:workbook_rows.id(WHERE workbook_id = :wb [AND id > :after] ORDER BY id LIMIT :n), page size from the new settingWORKBOOK_REFRESH_SCAN_BATCH_SIZE(default 1000);UNION ALLset of enrichment column ids (each with its own cutoff and config ordinal) and LEFT JOINsworkbook_enrichmentson(workbook_id, COALESCE(row.lead_id, row.id), column_id);(row, column)pairs, so only stale candidates reach Python.Semantics preserved
A cell is stale exactly as before: overlay missing,
statusnotcomplete(NULL included),updated_atNULL, orupdated_at < now - staleness_ttl_days[target_field or id](default 30). The execution key is still the linked lead id, falling back to the row id. The output is still ordered by row id, with columns in config order. Every page is scanned, so the result doesn't depend on page size. The setting bounds the work done per query and does not cap how much a refresh covers.Timestamps:
updated_atis naive UTCDateTimeon both dialects, so cutoffs are bound as naive UTC. That avoids session-timezone coercion on PG and mismatched string formats on SQLite.Tenancy: same as before. There's an explicit
workbook_idfilter, and RLS applies on both tables. The PG test checks that another tenant gets{}for the same workbook id.The one intentional difference is that enrichment columns with no
id(malformed config) are now skipped. Before, they were reported as aNonecolumn on every row. Duplicate ids are de-duplicated, which the caller already did.The ADR
docs/adr/20261012-refresh-dependent-and-new-row-cells.mdhas an addendum covering this.Tests
tests/test_workbook_refresh_stale_sql.pycompares the new function against the old implementation, which is kept verbatim as an oracle intests/refresh_stale_support.py. It uses randomized workbooks covering fresh, stale, missing, error, pending, NULL status, NULLupdated_atand ±1h TTL-edge cells, linked and unlinked rows, per-column TTLs, TTL looked up viatarget_field, the default TTL, a TTL of 0, and a sibling workbook sharing execution keys. It runs page sizes 1/3/7/40/1000 and also checks the round-trip count under the setting.tests/test_workbook_refresh_cell_scope.py: an end-to-end refresh with page size 1 gives the same scope.tests/test_workbook_refresh_stale_sql_pg.py: the same comparison on PostgreSQL under FORCE RLS as the app role, plus the cross-tenant isolation check. It is added to the CI Postgres job.Run locally:
uv run pytest -q -m 'not live and not postgres'→ 2235 passed, 134 skipped, 8 deselectedTEST_DATABASE_URL=postgresql+psycopg://…/<disposable db> uv run pytest -q tests/test_create_merge_pg.py tests/test_entity_identity_pg.py tests/test_pg_tenancy_rls.py tests/test_lead_paging_pg.py tests/test_workbook_refresh_stale_sql_pg.py→ 23 passed (local PostgreSQL 18.4)🤖 Generated with Claude Code