feat: Keep host on aggregated API usage buckets - #8471
Conversation
host was written to APIUsageRaw and then dropped by the hourly rollup, so it only survived the raw-data retention window. Buckets now carry it: the rollup groups by host and the overlap check treats it as a dimension. Read paths are unchanged — aggregations still sum across hosts — so dashboards behave exactly as before while the data stays available.
|
The latest updates on your projects. Learn more about Vercel for GitHub. 3 Skipped Deployments
|
Docker builds report
|
📝 WalkthroughWalkthroughThe API usage bucket model and migration add a Estimated code review effort: 2 (Simple) | ~10 minutes Merge Risk: 🟠 High · up to Host-aware bucketing can double-count historical API usage when legacy windows are reprocessed, producing incorrect usage totals. The legacy transition must be handled before merge, and bucket-to-bucket host preservation should be covered. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #8471 +/- ##
=======================================
Coverage 98.81% 98.81%
=======================================
Files 1621 1622 +1
Lines 66299 66314 +15
=======================================
+ Hits 65514 65529 +15
Misses 785 785 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
✅ private-cloud · depot-ubuntu-latest-16 — run #20198 (attempt 1)Playwright Test Results (private-cloud - depot-ubuntu-latest-16)Details
🗂️ Previous results✅ private-cloud · depot-ubuntu-latest-arm-16 — run #20198 (attempt 1)Playwright Test Results (private-cloud - depot-ubuntu-latest-arm-16)Details
✅ oss · depot-ubuntu-latest-arm-16 — run #20198 (attempt 1)Playwright Test Results (oss - depot-ubuntu-latest-arm-16)Details
✅ oss · depot-ubuntu-latest-16 — run #20198 (attempt 1)Playwright Test Results (oss - depot-ubuntu-latest-16)Details
|
There was a problem hiding this comment.
Actionable comments posted: 2
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Team
Run ID: ccf868b1-727f-4ea5-a929-b42caf2afa63
📒 Files selected for processing (4)
api/app_analytics/migrations/0009_apiusagebucket_host.pyapi/app_analytics/models.pyapi/app_analytics/tasks.pyapi/tests/unit/app_analytics/test_tasks.py
Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.
| defaults={"total_count": row["count"]}, | ||
| environment_id=row["environment_id"], | ||
| resource=row["resource"], | ||
| host=row["host"], |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Prevent duplicate counts when legacy buckets are reprocessed.
When a time window containing a pre-migration bucket is reprocessed from raw data, the migrated row has host="", while the raw aggregate has a real host such as "host1". Line 194 therefore uses a different lookup key and creates a second bucket. Line 101 does not treat the legacy empty-host row as overlapping. Because read paths still sum across hosts, the same usage can be counted twice.
api/app_analytics/tasks.py#L194-L194: handle or avoid legacy empty-host rows before using the host-specific lookup.api/app_analytics/models.py#L101-L101: keep overlap detection consistent with the legacy-row transition.api/app_analytics/migrations/0009_apiusagebucket_host.py#L16-L16: add a backfill/rebuild strategy or prevent reprocessing of windows containing legacy rows;default=""alone is insufficient.
📍 Affects 3 files
api/app_analytics/tasks.py#L194-L194(this comment)api/app_analytics/models.py#L101-L101api/app_analytics/migrations/0009_apiusagebucket_host.py#L16-L16
| def test_populate_api_usage_bucket__multiple_hosts__preserves_host( | ||
| freezer: FrozenDateTimeFactory, | ||
| ) -> None: | ||
| # Given events from two hosts in the same bucket window | ||
| environment_id = 1 | ||
| when = timezone.now() - timedelta(minutes=90) | ||
| for _ in range(3): | ||
| _create_api_usage_event(environment_id, when, host="edge-proxy") | ||
| _create_api_usage_event(environment_id, when) | ||
|
|
||
| # When | ||
| freezer.move_to(timezone.now() - timedelta(hours=1)) | ||
| populate_api_usage_bucket(bucket_size=15, run_every=60) | ||
|
|
||
| # Then the buckets are split by host, each keeping its host | ||
| buckets = APIUsageBucket.objects.filter(environment_id=environment_id) | ||
| assert {(bucket.host, bucket.total_count) for bucket in buckets} == { | ||
| ("edge-proxy", 3), | ||
| ("host1", 1), | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Add coverage for bucket-to-bucket host grouping.
This test omits source_bucket_size, so it covers only the raw APIUsageRaw branch. The change also groups APIUsageBucket source rows by host in api/app_analytics/tasks.py Lines 231-238. Add two source buckets with different hosts and assert that the target buckets preserve both hosts and totals.
Visual Regression19 screenshots compared. See report for details. |
Changes
Contributes to Flagsmith/flagsmith-private#256
hostis written toAPIUsageRawon every tracked request, but the hourly rollup dropped it — aggregated buckets carried no host, so the dimension only survived the raw-data retention window (30 days). With the Edge Proxy reporting its served requests withhost="edge-proxy", that dimension is how proxy-served traffic stays distinguishable from direct traffic long-term.APIUsageBucketgains ahostcolumn (default""for existing rows)._get_api_usage_source_data) groups byhoston both the raw→bucket and bucket→bucket paths, andpopulate_api_usage_bucketwrites it.hostas a dimension alongsideresource.FeatureEvaluationBucketis untouched (no host on its raw rows).How did you test this code?
New test: raw events from two hosts in one bucket window roll up into separate buckets, each keeping its host. Full
test_tasks.py(20) and the bucket read-path tests (test_analytics_db_service.py,test_commands.py, 29) pass against Postgres; ruff + mypy clean.