-
Notifications
You must be signed in to change notification settings - Fork 30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: use cached results from GCS in TA GQL #952
Conversation
Codecov ReportAttention: Patch coverage is ✅ All tests successful. No failed tests found. 📢 Thoughts on this report? Let us know! |
Codecov ReportAttention: Patch coverage is
✅ All tests successful. No failed tests found. Additional details and impacted files@@ Coverage Diff @@
## main #952 +/- ##
==========================================
- Coverage 96.25% 96.04% -0.22%
==========================================
Files 827 827
Lines 19090 19068 -22
==========================================
- Hits 18376 18313 -63
- Misses 714 755 +41
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
❌ 5 Tests Failed:
View the top 3 failed tests by shortest run time
To view individual test run time comparison to the main branch, go to the Test Analytics Dashboard |
Test Failures Detected: Due to failing tests, we cannot provide coverage reports at this time. ❌ Failed Test Results:Completed 2606 tests with View the full list of failed testspytest
|
this refactor simplifies the tests from test results aggregates and flake aggregates and adds some typing to the generate_test_results function. It also changes the arguments passed to the generate_test_results function so handling them is simpler and changes some things regarding error handling in that function.
we want the GQL endpoints to consume the cached query results from GCS and then do some filtering and ordering on top of that, and also do some extra caching of those results in redis. this commit also contains a bunch of reorganization and refactoring of the GQL code
when we miss the redis cache but find the test rollup results in GCS we want to queue up a task to cache it in redis so subsequent calls to the GQL endpoint for the same repo get to hit redis instead of GCS
a4a6e79
to
e3dd9a7
Compare
flake_rate: float | ||
flake_rate_percent_change: float | None | ||
flake_count_percent_change: float | None = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dang you didn't like having it grouped huh
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
all args with defaults must come after arguments with no defaults 😭
flake_rate_percent_change: float | None = None | ||
|
||
|
||
def calculate_flake_aggregates(table: pl.DataFrame) -> pl.DataFrame: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it possible to know what columns exist on the table at this point? Right now it's a little "magic"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the names of the columns are in the worker repo where it's being written, come to think of it maybe all the serialization and reading stuff can be in shared
but i don't know if that would make things worst, we only read in api
and we only write in worker
), | ||
(pl.col("total_skip_count").sum()).alias("skips"), | ||
(pl.col("total_fail_count").sum()).alias("fails"), | ||
((pl.col("avg_duration") >= pl.col("avg_duration").quantile(0.95)).sum()).alias( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we still need to do the limit 100 here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indeed, that's being handled above but not here
|
||
merged_results: pl.DataFrame = pl.concat([past_aggregates, curr_aggregates]) | ||
|
||
merged_results = merged_results.with_columns( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was a bit confusing for me at first to see this since I didn't realize with_columns is essentially an append or upsert depending on the column existing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i will leave a comment
first: int | None, | ||
last: int | None, | ||
) -> None: | ||
if interval not in {1, 7, 30}: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: do we have these magic numbers typed out as consts somewhere?
).decode("ascii") | ||
|
||
|
||
def validate( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Random thought here, does the order of these validations matter? Do we want it to matter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nope, and i don't think it should matter (maybe in past iterations it would've(
|
||
if flags: | ||
table = table.filter( | ||
pl.col("flags").list.eval(pl.element().is_in(flags)).list.any() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Claude was telling me we might be able to add these guards to this check, not sure if you think we need em tho
pl.col("flags").is_not_null() & # handle null rows
pl.col("flags").list.len() > 0 & # handle empty lists
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think the is not null check makes sense
total_count = table.height | ||
|
||
def ordering_expression(cursor_value: CursorValue, is_forward: bool) -> pl.Expr: | ||
if is_forward: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what does forward imply here? It's also a little confusing that we use the value of is_forward and then change the value of is_forward in the same fn
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is_forward is just about determining the ordering of the table, but ordering_expression
only reads is_forward, it's the outer function that sets the value and invokes ordering_expression
so i ended up just moving it out
|
||
rows = [TestResultsRow(**row) for row in page_elements.rows(named=True)] | ||
|
||
page: list[dict[str, str | TestResultsRow]] = [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be pages right? More than one
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no, this is a single page, since it's after all the ordering and filtering
return await sync_to_async(generate_test_results_aggregates)( | ||
repoid=repository.repoid, interval=convert_interval_to_timedelta(interval) | ||
repoid=repository.repoid, interval=interval.value if interval else 30 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these 30s can be MeasurementInterval.INTERVAL_30_DAY right?
interval_start: int, | ||
interval_end: int | None = None, | ||
) -> str: | ||
key = f"test_results:{repoid}:{branch}:{interval_start}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is an int... does this have a reasonable enough level of precision so we actually have some cache hits sometimes?
Like if the timestamp is 1231232132 vs. 1231232135 it seems like that would trigger a new rollup to be cached/created
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there's no timestamp being use in the key, it's just the range of days that the cached values apply to, for example 1 day, 7 days, or 30 days
✅ All tests successful. No failed tests were found. 📣 Thoughts on this report? Let Codecov know! | Powered by Codecov |
this commit refactors the code and tests related to ordering direction if we map ordering direction asc = 1 and desc = 0 and first = 1 and last = 0 then the ordering direction is an xor of those two variables ordering direction determines whether we're filtering by greater than or lesser than this commit also fixes a bug in the specification of slow tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approved, reminder to update the bool(after) thing and add a comment on the top_k() thing
we want the GQL endpoints to consume the cached query results from GCS
and then do some filtering and ordering on top of that, and also do some
extra caching of those results in redis.
this commit also contains a bunch of reorganization and refactoring of
the GQL code