-
Notifications
You must be signed in to change notification settings - Fork 61
feat(analytics): World ID unique-verifications data layer (1/3) #2191
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
Open
soamdesai-tfh
wants to merge
19
commits into
main
Choose a base branch
from
feat/world-id-analytics-data
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
51b00c9
feat(analytics): World ID unique-verifications data layer
soamdesai-tfh 093f412
Merge branch 'main' into feat/world-id-analytics-data
soamdesai-tfh 54a69d5
fix(analytics): harden deployment gate
soamdesai-tfh a11585c
fix(analytics): scope fresh-stack runner to the data-slice suites
soamdesai-tfh 72d904c
fix(analytics): make deployment gate comprehensive
soamdesai-tfh f6c668b
fix(analytics): rebuild history on gate retries
soamdesai-tfh 6e3e44e
Merge branch 'main' into feat/world-id-analytics-data
soamdesai-tfh 9f17522
Merge branch 'main' into feat/world-id-analytics-data
soamdesai-tfh 10fe751
fix(analytics): reject invalid source index
soamdesai-tfh 437499c
fix(analytics): move the v4 created_at index out of the migration
soamdesai-tfh 3c2ff71
refactor(analytics): rename action_v3_stats_daily to action_legacy_st…
soamdesai-tfh 12526c6
Merge branch 'main' into feat/world-id-analytics-data
soamdesai-tfh d39ac03
feat(analytics): chunk the rollup and make the backfill resumable
soamdesai-tfh 317d52e
fix(analytics): make the documented parity reset a true rebuild
soamdesai-tfh 2d9a081
feat(analytics): replace watermark rollup with stateless windowed reb…
soamdesai-tfh 579368e
test(analytics): rewrite suites for windowed rollup, add e2e release …
soamdesai-tfh dfa1a81
fix(analytics): ship a single operations file
soamdesai-tfh 7ed4ca7
fix(analytics): take parent action locks before child rows in the rollup
soamdesai-tfh e9dc390
Merge branch 'main' into feat/world-id-analytics-data
soamdesai-tfh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
7 changes: 7 additions & 0 deletions
7
hasura/metadata/databases/default/functions/public_rollup_world_id_analytics.yaml
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| function: | ||
| name: rollup_world_id_analytics | ||
| schema: public | ||
| configuration: | ||
| exposed_as: mutation | ||
| permissions: | ||
| - role: service |
5 changes: 5 additions & 0 deletions
5
hasura/metadata/databases/default/functions/public_world_id_analytics_app_daily.yaml
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| function: | ||
| name: world_id_analytics_app_daily | ||
| schema: public | ||
| permissions: | ||
| - role: service |
12 changes: 12 additions & 0 deletions
12
hasura/metadata/databases/default/tables/public_action_legacy_stats_daily.yaml
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| table: | ||
| name: action_legacy_stats_daily | ||
| schema: public | ||
| object_relationships: | ||
| - name: action | ||
| using: | ||
| foreign_key_constraint_on: action_id | ||
| select_permissions: | ||
| - role: service | ||
| permission: | ||
| columns: [action_id, date_utc, unique_count] | ||
| filter: {} |
12 changes: 12 additions & 0 deletions
12
hasura/metadata/databases/default/tables/public_action_v4_stats_daily.yaml
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| table: | ||
| name: action_v4_stats_daily | ||
| schema: public | ||
| object_relationships: | ||
| - name: action_v4 | ||
| using: | ||
| foreign_key_constraint_on: action_v4_id | ||
| select_permissions: | ||
| - role: service | ||
| permission: | ||
| columns: [action_v4_id, date_utc, unique_count] | ||
| filter: {} |
8 changes: 8 additions & 0 deletions
8
hasura/metadata/databases/default/tables/public_world_id_app_stats_daily.yaml
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| table: | ||
| name: world_id_app_stats_daily | ||
| schema: public | ||
| select_permissions: | ||
| - role: service | ||
| permission: | ||
| columns: [date_utc, unique_count] | ||
| filter: {} |
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
5 changes: 5 additions & 0 deletions
5
hasura/migrations/default/1785459169000_add_world_id_single_metric_analytics/down.sql
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| DROP FUNCTION IF EXISTS public.rollup_world_id_analytics(date, date); | ||
| DROP FUNCTION IF EXISTS public.world_id_analytics_app_daily(varchar, varchar, date, date); | ||
| DROP TABLE IF EXISTS public.world_id_app_stats_daily; | ||
| DROP TABLE IF EXISTS public.action_v4_stats_daily; | ||
| DROP TABLE IF EXISTS public.action_legacy_stats_daily; |
180 changes: 180 additions & 0 deletions
180
hasura/migrations/default/1785459169000_add_world_id_single_metric_analytics/up.sql
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,180 @@ | ||
| CREATE TABLE public.action_legacy_stats_daily ( | ||
| action_id varchar(50) NOT NULL REFERENCES public.action(id) ON DELETE CASCADE, | ||
| date_utc date NOT NULL, | ||
| unique_count bigint NOT NULL CHECK (unique_count >= 0), | ||
| PRIMARY KEY (action_id, date_utc) | ||
| ); | ||
|
|
||
| CREATE TABLE public.action_v4_stats_daily ( | ||
| action_v4_id varchar(50) NOT NULL REFERENCES public.action_v4(id) ON DELETE CASCADE, | ||
| date_utc date NOT NULL, | ||
| unique_count bigint NOT NULL CHECK (unique_count >= 0), | ||
| PRIMARY KEY (action_v4_id, date_utc) | ||
| ); | ||
|
|
||
| CREATE INDEX action_legacy_stats_daily_date_idx | ||
| ON public.action_legacy_stats_daily (date_utc); | ||
| CREATE INDEX action_v4_stats_daily_date_idx | ||
| ON public.action_v4_stats_daily (date_utc); | ||
|
|
||
| CREATE TABLE public.world_id_app_stats_daily ( | ||
| date_utc date PRIMARY KEY, | ||
| unique_count bigint NOT NULL | ||
| ); | ||
|
|
||
| CREATE OR REPLACE FUNCTION public.world_id_analytics_app_daily( | ||
| app_id_input varchar, | ||
| environment_input varchar, | ||
| from_date_input date, | ||
| through_date_input date | ||
| ) | ||
| RETURNS SETOF public.world_id_app_stats_daily | ||
| LANGUAGE sql | ||
| STABLE | ||
| AS $$ | ||
| SELECT combined.date_utc, sum(combined.unique_count)::bigint | ||
| FROM ( | ||
| SELECT daily.date_utc, daily.unique_count | ||
| FROM public.action_legacy_stats_daily daily | ||
| JOIN public.action legacy_action ON legacy_action.id = daily.action_id | ||
| JOIN public.app application ON application.id = legacy_action.app_id | ||
| WHERE application.id = app_id_input | ||
| AND application.deleted_at IS NULL | ||
| AND ( | ||
| (environment_input = 'staging' AND application.is_staging) | ||
| OR (environment_input = 'production' AND NOT application.is_staging) | ||
| ) | ||
| AND daily.date_utc BETWEEN from_date_input AND through_date_input | ||
|
|
||
| UNION ALL | ||
|
|
||
| SELECT daily.date_utc, daily.unique_count | ||
| FROM public.action_v4_stats_daily daily | ||
| JOIN public.action_v4 action_v4 ON action_v4.id = daily.action_v4_id | ||
| JOIN public.rp_registration registration ON registration.rp_id = action_v4.rp_id | ||
| JOIN public.app application ON application.id = registration.app_id | ||
| WHERE application.id = app_id_input | ||
| AND application.deleted_at IS NULL | ||
| AND action_v4.environment::text = environment_input | ||
| AND daily.date_utc BETWEEN from_date_input AND through_date_input | ||
| ) combined | ||
| GROUP BY combined.date_utc | ||
| ORDER BY combined.date_utc | ||
| $$; | ||
|
|
||
| -- Stateless window rebuild: delete the window's rolled days and recount them | ||
| -- from the raw nullifier tables. Passing no dates rebuilds the standard | ||
| -- trailing window (the last ~25 hours); explicit dates rebuild exactly that | ||
| -- inclusive range, which is also how any suspect range is repaired. | ||
| CREATE OR REPLACE FUNCTION public.rollup_world_id_analytics( | ||
| from_date date, | ||
| to_date date | ||
| ) | ||
| RETURNS SETOF public.world_id_app_stats_daily | ||
| LANGUAGE plpgsql | ||
| VOLATILE | ||
| AS $$ | ||
| DECLARE | ||
| cutoff timestamptz := clock_timestamp() - interval '5 minutes'; | ||
| window_start date; | ||
| window_end date; | ||
| rebuild_until timestamptz; | ||
| BEGIN | ||
| IF (from_date IS NULL) <> (to_date IS NULL) THEN | ||
| RAISE EXCEPTION 'from_date and to_date must be supplied together'; | ||
| END IF; | ||
|
|
||
| window_start := COALESCE( | ||
| from_date, | ||
| ((cutoff - interval '25 hours') AT TIME ZONE 'UTC')::date | ||
| ); | ||
| window_end := COALESCE(to_date, (cutoff AT TIME ZONE 'UTC')::date); | ||
|
|
||
| IF window_start > window_end THEN | ||
| RAISE EXCEPTION 'from_date % is after to_date %', window_start, window_end; | ||
| END IF; | ||
|
|
||
| -- Never count the racing 5 minutes: rows may still be committing there, and | ||
| -- a later run's window always re-covers them. | ||
| rebuild_until := LEAST( | ||
| (window_end + 1)::timestamp AT TIME ZONE 'UTC', | ||
| cutoff | ||
| ); | ||
|
|
||
| IF window_start::timestamp AT TIME ZONE 'UTC' >= rebuild_until THEN | ||
| RETURN; | ||
| END IF; | ||
|
|
||
| -- Serialize concurrent runs (cron ticks, backfill chunks, parity catch-up). | ||
| -- Blocking rather than try-lock: Hasura hides Postgres SQLSTATEs from | ||
| -- non-admin roles, so the route could not tell a lock miss from a failure. | ||
| -- Waiters are bounded by the caller's request timeout. | ||
| PERFORM pg_advisory_xact_lock(533214, 43); | ||
|
|
||
| -- Take the recount's parent locks BEFORE touching child rows. Without | ||
| -- this, a concurrent action deletion (parent row first, then a cascade | ||
| -- into the very child rows this window just deleted) forms a lock cycle | ||
| -- whose victim is nearly always the user's delete (40P01, reproduced | ||
| -- 3/3). Parent-before-child on both sides removes the cycle: the delete | ||
| -- briefly waits for this transaction, then cascades cleanly. | ||
| PERFORM 1 | ||
| FROM public.action locked_action | ||
| WHERE locked_action.id IN ( | ||
| SELECT DISTINCT n.action_id | ||
| FROM public.nullifier n | ||
| WHERE n.created_at >= (window_start::timestamp AT TIME ZONE 'UTC') | ||
| AND n.created_at < rebuild_until | ||
| ) | ||
| ORDER BY locked_action.id | ||
| FOR KEY SHARE OF locked_action; | ||
|
|
||
| PERFORM 1 | ||
| FROM public.action_v4 locked_action | ||
| WHERE locked_action.id IN ( | ||
| SELECT DISTINCT n.action_v4_id | ||
| FROM public.nullifier_v4 n | ||
| WHERE n.created_at >= (window_start::timestamp AT TIME ZONE 'UTC') | ||
| AND n.created_at < rebuild_until | ||
| ) | ||
| ORDER BY locked_action.id | ||
| FOR KEY SHARE OF locked_action; | ||
|
|
||
| DELETE FROM public.action_legacy_stats_daily | ||
| WHERE date_utc BETWEEN window_start AND window_end; | ||
| DELETE FROM public.action_v4_stats_daily | ||
| WHERE date_utc BETWEEN window_start AND window_end; | ||
|
|
||
| INSERT INTO public.action_legacy_stats_daily (action_id, date_utc, unique_count) | ||
| SELECT n.action_id, (n.created_at AT TIME ZONE 'UTC')::date, count(*)::bigint | ||
| FROM public.nullifier n | ||
| JOIN public.action a ON a.id = n.action_id | ||
| WHERE n.created_at >= (window_start::timestamp AT TIME ZONE 'UTC') | ||
| AND n.created_at < rebuild_until | ||
| GROUP BY n.action_id, (n.created_at AT TIME ZONE 'UTC')::date; | ||
|
|
||
| INSERT INTO public.action_v4_stats_daily (action_v4_id, date_utc, unique_count) | ||
| SELECT n.action_v4_id, (n.created_at AT TIME ZONE 'UTC')::date, count(*)::bigint | ||
| FROM public.nullifier_v4 n | ||
| JOIN public.action_v4 a ON a.id = n.action_v4_id | ||
| WHERE n.created_at >= (window_start::timestamp AT TIME ZONE 'UTC') | ||
| AND n.created_at < rebuild_until | ||
| GROUP BY n.action_v4_id, (n.created_at AT TIME ZONE 'UTC')::date; | ||
|
|
||
| RETURN QUERY | ||
| SELECT combined.date_utc, sum(combined.unique_count)::bigint | ||
| FROM ( | ||
| SELECT daily.date_utc, daily.unique_count | ||
| FROM public.action_legacy_stats_daily daily | ||
| WHERE daily.date_utc BETWEEN window_start AND window_end | ||
| UNION ALL | ||
| SELECT daily.date_utc, daily.unique_count | ||
| FROM public.action_v4_stats_daily daily | ||
| WHERE daily.date_utc BETWEEN window_start AND window_end | ||
| ) combined | ||
| GROUP BY combined.date_utc | ||
| ORDER BY combined.date_utc; | ||
| END | ||
| $$; | ||
|
|
||
| COMMENT ON TABLE public.world_id_app_stats_daily IS | ||
| 'Empty tracked return shape for the service-only app analytics SQL functions.'; |
43 changes: 43 additions & 0 deletions
43
hasura/operations/world-id-analytics/create-nullifier-created-at-index.sql
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| \set ON_ERROR_STOP on | ||
|
|
||
| -- Run out of band before enabling the rollup. Hasura migrations are | ||
| -- transactional, so these builds deliberately do not live in a migration. | ||
| CREATE INDEX CONCURRENTLY IF NOT EXISTS nullifier_created_at_idx | ||
|
soamdesai-tfh marked this conversation as resolved.
soamdesai-tfh marked this conversation as resolved.
|
||
| ON public.nullifier (created_at); | ||
|
|
||
| DO $index_gate$ | ||
| BEGIN | ||
| IF NOT EXISTS ( | ||
| SELECT 1 | ||
| FROM pg_index | ||
| WHERE indexrelid = to_regclass('public.nullifier_created_at_idx') | ||
| AND indrelid = 'public.nullifier'::regclass | ||
| AND indisready | ||
| AND indisvalid | ||
| ) THEN | ||
| RAISE EXCEPTION USING | ||
| MESSAGE = 'nullifier_created_at_idx is missing or invalid', | ||
| HINT = 'Run DROP INDEX CONCURRENTLY IF EXISTS public.nullifier_created_at_idx; then rerun this script'; | ||
| END IF; | ||
| END | ||
| $index_gate$; | ||
|
|
||
| CREATE INDEX CONCURRENTLY IF NOT EXISTS nullifier_v4_created_at_idx | ||
| ON public.nullifier_v4 (created_at); | ||
|
|
||
| DO $index_gate$ | ||
| BEGIN | ||
| IF NOT EXISTS ( | ||
| SELECT 1 | ||
| FROM pg_index | ||
| WHERE indexrelid = to_regclass('public.nullifier_v4_created_at_idx') | ||
| AND indrelid = 'public.nullifier_v4'::regclass | ||
| AND indisready | ||
| AND indisvalid | ||
| ) THEN | ||
| RAISE EXCEPTION USING | ||
| MESSAGE = 'nullifier_v4_created_at_idx is missing or invalid', | ||
| HINT = 'Run DROP INDEX CONCURRENTLY IF EXISTS public.nullifier_v4_created_at_idx; then rerun this script'; | ||
| END IF; | ||
| END | ||
| $index_gate$; | ||
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
Oops, something went wrong.
Oops, something went wrong.
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.
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 would increase the timeout to 14m, so the cron job can make enough progress. if we only process rows 1 minute every 15 minutes this could hours/days.