Repository navigation
perf(tree-details): filter and cache builds, boots, and tests in SQL - #2206
Conversation
Restore Builds with one row per build and cache validated responses. Preserve existing filter and empty-result behavior. Part of kernelci#2202 Assisted-by: OpenAI Codex Signed-off-by: Felipe Bergamin <felipebergamin@profusion.mobi>
Filter before transfer, select one incident per boot and cache the final response. Preserve hardware fallback and full_environment_misc behavior. Part of kernelci#2202 Assisted-by: OpenAI Codex Signed-off-by: Felipe Bergamin <felipebergamin@profusion.mobi>
Use the filtered history query for non-boot tests, keeping separate cache entries and existing filter semantics. Part of kernelci#2202 Assisted-by: OpenAI Codex Signed-off-by: Felipe Bergamin <felipebergamin@profusion.mobi>
Restore CLI integration assertions for the re-enabled builds endpoint. Part of kernelci#2202 Assisted-by: Codex (GPT-6) Signed-off-by: Felipe Bergamin <felipebergamin@profusion.mobi>
| commit_hash: str, | ||
| tree_name: Optional[str], | ||
| ) -> dict[str, Any]: | ||
| where, issue_where, params = _filter_clauses(filters) |
There was a problem hiding this comment.
might be a nit, but we could just add the filters on params, this way filter_clauses keeps a single purpose
| """ | ||
| builds = [] | ||
| error_message = None | ||
| with connection.cursor() as cursor: |
There was a problem hiding this comment.
avoiding the fetch all might be a good approach here. But we could get a smaller footprint if we stream the rows using cursor.stream.
Might be a good idea to take a look on the time spent here, as we might need to find a good compromise on batch size and connection time.
There was a problem hiding this comment.
Small test here, using a 2000 batch size.
fetchall |
execute + for |
stream |
|
|---|---|---|---|
| Peak RSS | 354 MiB | 169 MiB | 82 MiB |
| Time to finish | 46.8 s | 43.3 s | 45.5 s |
| First byte | 46.8 s | 40.1 s | 2.8 s |
| Connection held | 46.4 s | 43.3 s | 45.5 s |
There was a problem hiding this comment.
Small test here, using a 2000 batch size.
fetchallexecute+forstream
Peak RSS 354 MiB 169 MiB 82 MiB
Time to finish 46.8 s 43.3 s 45.5 s
First byte 46.8 s 40.1 s 2.8 s
Connection held 46.4 s 43.3 s 45.5 s
How did you do this tests?
I tested this through response construction, Redis caching, and JSON rendering. With 100k synthetic rows, stream(size=2000) reduced peak RSS from 651 to 565 MiB, with similar total time, 6.16 vs 6.11 seconds.
Something that might become a problem is that cursor.stream() bypasses Django’s execution hooks and exception translation.
There was a problem hiding this comment.
I did a small script for comparison to keep only the query for next/master. But it seems you got roughly 90MiB extra, as I got.
There could be better numbers than 2000 to be used (it was just a guess from my part).
There was a problem hiding this comment.
I am not sure if understood the problem of the django execution hook. But feel free to ignore if it could be problematic for validation.
There was a problem hiding this comment.
I am not sure if understood the problem of the django execution hook. But feel free to ignore if it could be problematic for validation.
AFAIK Django normally wraps database execution to support query logging, instrumentation, and conversion of driver exceptions into Django exceptions. Calling cursor.stream() directly bypasses that execution path.
It’s manageable, not a reason to reject streaming. Just something to have in mind.
I did a small script for comparison to keep only the query for next/master. But it seems you got roughly 90MiB extra, as I got. There could be better numbers than 2000 to be used (it was just a guess from my part).
I ran with different batch sizes and got similar numbers. I think most of the remaining memory is somewhere else
| ) selected_issue ON TRUE | ||
| WHERE {where} | ||
| ) | ||
| SELECT metadata.checkout_count, metadata.has_builds, |
There was a problem hiding this comment.
if we are moving the filter to inside the query, we might be able to drop some of those columns.
There was a problem hiding this comment.
But we still should remember that moving filter inside, might make we need to do more long fetches when applying filters. If using cursor.stream help us enough with saving memory, might be worth keeping filters out.
There was a problem hiding this comment.
Might not be a big problem if we also consider #2207
alanpeixinho
left a comment
There was a problem hiding this comment.
Left some possible improvements as comments, but overall, it is a great improvement already
Summary
Closes #2202 by moving tree detail filtering into SQL for builds, boots, and tests. Each query selects only fields used by the response and returns at most one matching incident per item. The endpoint caches the final response instead of raw database rows.
The existing response shape and complete-result behavior remain unchanged. This does not add pagination or a result limit. It also keeps the worker recycling and container memory limits introduced by #2203.
Performance measurements
Using the same production commit and five requests per tab in separate processes:
RSS measured the legacy and optimized BaseView request paths with JSON rendering and explicit garbage collection. This is a short, single-process sample, not a concurrency or long-duration leak test. Response bodies were equivalent between implementations.
Validation
backend/schema.yml.To reproduce the SQL tests, start the local integration test services and database as described in IntegrationTests.md, then run from
backend/:Related: #2202, #2203