use window approach - #1502
Conversation
|
(FYI the change summary was generated by Claude since I was in a hurry, sorry if it reads poorly...) |
There was a problem hiding this comment.
Pull request overview
This PR is a WIP performance-focused refactor for “latest version” resolution of science files, replacing correlated-subquery logic with a shared window-function query builder and reusing it across query and release APIs. It also introduces performance tests and consolidates SQLite session test scaffolding so regressions can be caught early.
Changes:
- Introduces a shared
build_latest_version_query()using a window function to resolve “latest” science files. - Updates query and release lambdas to use the shared builder; release now performs bulk
UPDATEoperations instead of per-row ORM updates. - Adds unit and performance tests plus shared in-memory SQLite session fixtures.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
sds_data_manager/lambda_code/SDSCode/api_lambdas/latest_version_query.py |
New shared window-function “latest version” query builder for science files. |
sds_data_manager/lambda_code/SDSCode/api_lambdas/query_api.py |
Refactors query parameter handling and uses the shared latest-version builder. |
sds_data_manager/lambda_code/SDSCode/api_lambdas/release_api.py |
Removes duplicated latest-resolution logic; releases via bulk UPDATE and builds ancillary “latest” as an IN subquery. |
sds_data_manager/lambda_code/SDSCode/database/models.py |
Adds FILE_ID_COLUMNS constant and reuses it for the version index. |
tests/conftest.py |
Adds shared in_memory_session() contextmanager for DB-backed tests. |
tests/lambda_endpoints/conftest.py |
Switches endpoint tests to the shared in-memory session helper. |
tests/lambda_endpoints/test_version_resolution.py |
Adds unit tests validating latest-version semantics (per-series, NULL-safe repointing grouping, filter ordering, released visibility). |
tests/lambda_endpoints/test_release_api.py |
Updates ancillary latest-version test to use the new query builder; removes repointing-specific release test. |
tests/performance/conftest.py |
Adds time-constrained SQLite session fixture to fail fast on slow statements. |
tests/performance/test_query_performance.py |
New performance regression test for query API “latest” resolution at scale. |
tests/performance/test_release_performance.py |
New performance regression test for release API latest-resolution + bulk release update at scale. |
tests/performance/test_spice_query_performance.py |
New performance regression test for SPICE query API latest-version resolution at scale. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # Valid query parameters per table: its columns (minus "id"), plus "end_date" | ||
| # for every table but "ancillary", plus the ingestion date range params. | ||
| _VALID_PARAMETERS = { | ||
| table: [ | ||
| *(column.key for column in model.__table__.columns if column.key != "id"), | ||
| *(["end_date"] if table != "ancillary" else []), | ||
| "ingestion_start_date", | ||
| "ingestion_end_date", | ||
| ] | ||
| for table, model in _TABLE_MODELS.items() | ||
| } |
There was a problem hiding this comment.
Valid issue, I added a test to avoid regressions on this and pushed a change to fix it. I also added a comment clarifying the differences across tables to avoid future confusion. I also removed the id exclusion since none of the included tables have an id column (@tmplummer please confirm)
| # We want to order the query returns by the filename | ||
| # This will implicitly sort by: instrument, data level, descriptor, start_date, ... | ||
| # Default for the table is by the ascending id so by insertion order | ||
| # This fails for the SPICE table because it uses 'file_name' | ||
| query = query.order_by(model.file_path) | ||
| query = query.order_by(cols.file_path) |
There was a problem hiding this comment.
The problematic comment is from May 2025. It doesn't appear to be true of the latest codebase. I think I need input here on what the expected behavior should be.
|
@tmplummer @tech3371 Question: What is the expected behavior for |
As initial comment, would you like to remove changes in About the ancillary |
|
I don't mind removing the changes to release_api. |
|
@hafarooki, this is still on my radar, but some other pressing matters are keeping me from doing a thorough review of this. |
|
No rush on my end! I'm happy to work on other things in the meanwhile as well ;) |
|
I wanted to take some of your code into release work I was working on. I have to get that work in asap for next week. Tagging you for awareness that I used some of your code in this PR already, #1533. Thank you for these improvements. |
tmplummer
left a comment
There was a problem hiding this comment.
This all looks good to me. One comment about how long the new performance tests take to run.
| case "start_date": | ||
| return cols.start_date >= datetime.datetime.strptime(value, "%Y%m%d") | ||
| case "end_date": | ||
| # TODO: Need to discuss as a team how to handle date queries. For now, |
There was a problem hiding this comment.
Is this specific to ancillary files that can have a start and end date? I would think that the desired results would be to provide any ancillary files that would be used for the full range that is queried.
There was a problem hiding this comment.
Not sure tbh. That TODO comment was in the code from before my changes
There was a problem hiding this comment.
I think we can define our own desired behavior here since it was not defined previously
There was a problem hiding this comment.
I'm curious about how long this takes to run? I have noticed that our sds-data-manager tests are taking close to 10 minutes now and want to be mindful of not adding tests that take too long.
|
After my optimizations the performance test takes a few hundred ms to run. Before with no index it took longer than i had the patience to find out |
tmplummer
left a comment
There was a problem hiding this comment.
This is a nice improvement with some really useful added testing.
|
Sorry for the slow review. There are now some merge conflicts and pre-commit checks that need to be addressed. |
|
Thanks! I suggest we go ahead and merge it, and open a separate issue for handling of end date for ancillary files, since that behavior was never defined and it's out of scope for this PR. |
|
I'll resolve the merge conflicts... |
Upstream IMAP-Science-Operations-Center#1533 already landed the window-function version resolution as api_lambdas/utils.build_latest_version_query, so this branch's duplicate latest_version_query.py and FILE_ID_COLUMNS definition are dropped in favor of upstream's. release_api.py takes upstream's manifest-line release wholesale; the release performance test now exercises latest_science_release directly since the old lambda params are gone. Assisted-by: Claude
Assisted-by: Claude
|
Should be good to go now |
132f243
into
IMAP-Science-Operations-Center:dev
Preface: This is a WIP change that is intended to fix #1493 . At this time, I would just like @tech3371 to test if this fixes the performance issue for the queries mentioned in #1493 in a production-like environment.
Change Summary
Overview
Replaces the correlated-subquery approach for resolving "latest version" of science
files with a single window-function (RANK) query, shared between the query API and
the release API. The old approach re-scanned the science table per candidate row and
got very slow at realistic data volumes; the release API additionally had its own
~180-line copy of the same logic. The release path now also applies releases as a
single bulk UPDATE instead of loading ORM objects and flipping them one at a time.
File changes
latest_version_query.py(new):build_latest_version_query(), which ranks rowsby version within each file series (partitioned on the columns identifying one
logical file) and keeps rank 1. Handles both "newest file" and "all minors of the
latest major".
query_api.py: uses the shared builder. Some incidental cleanup while in here:table/valid-parameter lookups are now module-level dicts, the version mode is a
StrEnum, and per-parameter filter construction moved into a helper.release_api.py: deletesquery_latest_science_files(including the repointingspecial-casing — the window query is NULL-safe on repointing so it no longer needs
two code paths). Ancillary resolution now returns a query used as an
INsubquery,so releasing stays a single UPDATE statement.
models.py: newFILE_ID_COLUMNSconstant, also reused for the version index.tests/: the in-memory SQLite session fixture moved up to the top-level conftestso it can be shared with a new
tests/performance/directory.Testing
test_version_resolution.pycovering per-series resolution,NULL-safe repointing grouping, filter/ranking interaction, and released-file
visibility for unauthenticated users.
data, with a 2s per-statement budget that fails fast if a correlated-subquery-style
regression sneaks back in.
test_release_repoint_files_date_rangesince the function it exercised isgone; its scenarios are covered by the new version-resolution tests.