Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions scripts/ops/reclaim_supabase_swap.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
-- Capgo-EU Phase A reclaim (run manually in a maintenance window).
Comment thread
riderx marked this conversation as resolved.
Comment thread
riderx marked this conversation as resolved.
Comment thread
riderx marked this conversation as resolved.
Comment thread
riderx marked this conversation as resolved.
Comment thread
riderx marked this conversation as resolved.
Comment thread
riderx marked this conversation as resolved.
Comment thread
riderx marked this conversation as resolved.
-- REQUIRED: psql (uses \gexec; VACUUM cannot run inside a transaction).
-- Prefer ~/.pgpass / PGPASSFILE instead of putting the password on the CLI.
-- Example:
-- psql "postgresql://postgres@HOST:5432/postgres?sslmode=require" -v ON_ERROR_STOP=1 -f scripts/ops/reclaim_supabase_swap.sql
Comment thread
coderabbitai[bot] marked this conversation as resolved.
-- Safe order: index -> truncate -> archives -> null manifests -> audit trim.
-- Re-run the FULL script until cleanup notices report deleted/updated = 0
-- (functions always emit a notice, including zero totals).

SET lock_timeout = '5s';

-- ---------------------------------------------------------------------------
-- 0) Baseline sizes
-- ---------------------------------------------------------------------------
SELECT pg_size_pretty(pg_database_size(current_database())::bigint) AS db_size;

SELECT
relname,
n_live_tup,
pg_size_pretty(pg_total_relation_size(format('%I.%I', schemaname, relname)::regclass)::bigint) AS total
FROM pg_stat_user_tables
WHERE (schemaname, relname) IN (
('net', '_http_response'),
('public', 'audit_logs'),
('public', 'app_versions'),
('public', 'manifest'),
('pgmq', 'a_on_version_update'),
('pgmq', 'a_on_manifest_create'),
('pgmq', 'a_webhook_dispatcher'),
('pgmq', 'a_on_channel_update')
)
ORDER BY pg_total_relation_size(format('%I.%I', schemaname, relname)::regclass) DESC;

-- ---------------------------------------------------------------------------
-- 0b) Candidate index for hourly nulling (non-blocking; must be outside a tx)
-- ---------------------------------------------------------------------------
CREATE INDEX CONCURRENTLY IF NOT EXISTS app_versions_manifest_present_idx

@cubic-dev-ai cubic-dev-ai Bot Jul 22, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: A lock timeout or interrupted concurrent build leaves an invalid index that every instructed rerun skips because of IF NOT EXISTS, so manifest cleanup can proceed with the intended pagination index unusable. Detect and drop an invalid same-name index before retrying the concurrent build.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/ops/reclaim_supabase_swap.sql, line 37:

<comment>A lock timeout or interrupted concurrent build leaves an invalid index that every instructed rerun skips because of `IF NOT EXISTS`, so manifest cleanup can proceed with the intended pagination index unusable. Detect and drop an invalid same-name index before retrying the concurrent build.</comment>

<file context>
@@ -0,0 +1,102 @@
+-- ---------------------------------------------------------------------------
+-- 0b) Candidate index for hourly nulling (non-blocking; must be outside a tx)
+-- ---------------------------------------------------------------------------
+CREATE INDEX CONCURRENTLY IF NOT EXISTS app_versions_manifest_present_idx
+  ON public.app_versions USING btree (id)
+  WHERE manifest IS NOT NULL;
</file context>
Fix with cubic

ON public.app_versions USING btree (id)
WHERE manifest IS NOT NULL;

-- ---------------------------------------------------------------------------
-- 1) Truncate pg_net response bloat
-- ---------------------------------------------------------------------------
TRUNCATE TABLE net._http_response;
Comment thread
riderx marked this conversation as resolved.

-- ---------------------------------------------------------------------------
-- 2) Purge pgmq archives/stuck messages.
-- Re-run the FULL script until archived_deleted=0 and stuck_deleted=0.
-- ---------------------------------------------------------------------------
SELECT public.cleanup_queue_messages();
Comment thread
riderx marked this conversation as resolved.

-- Vacuum Capgo-EU evidenced bloated queues only.
VACUUM (VERBOSE) pgmq.a_on_version_update;
VACUUM (VERBOSE) pgmq.a_on_manifest_create;
VACUUM (VERBOSE) pgmq.a_webhook_dispatcher;
VACUUM (VERBOSE) pgmq.a_on_channel_update;
VACUUM (VERBOSE) pgmq.q_on_version_update;
VACUUM (VERBOSE) pgmq.q_on_manifest_create;
VACUUM (VERBOSE) pgmq.q_webhook_dispatcher;
VACUUM (VERBOSE) pgmq.q_on_channel_update;

-- ---------------------------------------------------------------------------
-- 3) Null fully migrated app_versions.manifest arrays (s3_path + file_hash).
-- Re-run the FULL script until updated=0.
-- ---------------------------------------------------------------------------
SELECT public.null_migrated_app_version_manifests();

VACUUM (ANALYZE, VERBOSE) public.app_versions;
-- Optional TOAST compaction after updated=0:
-- VACUUM (FULL, VERBOSE) public.app_versions;

-- ---------------------------------------------------------------------------
-- 4) Trim audit_logs older than 30 days.
-- Re-run the FULL script until deleted=0.
-- ---------------------------------------------------------------------------
SELECT public.cleanup_old_audit_logs();

VACUUM (ANALYZE, VERBOSE) public.audit_logs;
-- Optional TOAST compaction after deleted=0:
-- VACUUM (FULL, VERBOSE) public.audit_logs;

-- ---------------------------------------------------------------------------
-- 5) Final sizes
-- ---------------------------------------------------------------------------
SELECT pg_size_pretty(pg_database_size(current_database())::bigint) AS db_size_after;

SELECT
relname,
n_live_tup,
pg_size_pretty(pg_total_relation_size(format('%I.%I', schemaname, relname)::regclass)::bigint) AS total
FROM pg_stat_user_tables
WHERE (schemaname, relname) IN (
('net', '_http_response'),
('public', 'audit_logs'),
('public', 'app_versions'),
('public', 'manifest'),
('pgmq', 'a_on_version_update'),
('pgmq', 'a_on_manifest_create'),
('pgmq', 'a_webhook_dispatcher'),
('pgmq', 'a_on_channel_update')
)
ORDER BY pg_total_relation_size(format('%I.%I', schemaname, relname)::regclass) DESC;
121 changes: 121 additions & 0 deletions scripts/ops/verify_supabase_swap.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
-- Post-deploy / post-reclaim verification for Capgo-EU swap pressure.
Comment thread
riderx marked this conversation as resolved.
-- REQUIRED: psql (uses \gexec). Example:
-- psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f scripts/ops/verify_supabase_swap.sql

SELECT pg_size_pretty(pg_database_size(current_database())::bigint) AS db_size;

SELECT
name,
setting,
unit
FROM pg_settings
WHERE name IN ('shared_buffers', 'work_mem', 'max_connections');

SELECT
relname,
n_live_tup,
pg_size_pretty(pg_total_relation_size(format('%I.%I', schemaname, relname)::regclass)::bigint) AS total
FROM pg_stat_user_tables
WHERE (schemaname, relname) IN (
('net', '_http_response'),
('public', 'audit_logs'),
('public', 'app_versions'),
('public', 'manifest'),
('pgmq', 'a_on_version_update'),
('pgmq', 'a_on_manifest_create'),
('pgmq', 'a_webhook_dispatcher'),
('pgmq', 'a_on_channel_update')
)
ORDER BY pg_total_relation_size(format('%I.%I', schemaname, relname)::regclass) DESC;

-- Sample first 1000 non-null manifests; a zero does not prove global completion.
SELECT count(*) AS eligible_dual_storage_sample
FROM (
SELECT sample.id
FROM (
SELECT av.id, av.manifest
FROM public.app_versions AS av
WHERE av.manifest IS NOT NULL
ORDER BY av.id
LIMIT 1000
Comment thread
riderx marked this conversation as resolved.
) AS sample
WHERE NOT EXISTS (
SELECT 1
FROM unnest(sample.manifest) AS entry(file_name, s3_path, file_hash)
Comment thread
riderx marked this conversation as resolved.
WHERE NOT EXISTS (
SELECT 1
FROM public.manifest AS m
WHERE m.app_version_id = sample.id
AND m.s3_path = entry.s3_path
AND m.file_hash = entry.file_hash
)
)
) AS eligible;

SELECT
name,
enabled,
hour_interval,
run_at_hour,
run_at_minute,
target,
description,
updated_at
FROM public.cron_tasks
WHERE name IN (
'cleanup_queue_messages',
'cleanup_net_http_response',
'cleanup_old_audit_logs',
'null_migrated_app_version_manifests'
)
ORDER BY name;

SELECT indexname
Comment thread
riderx marked this conversation as resolved.

@cubic-dev-ai cubic-dev-ai Bot Jul 22, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: An interrupted concurrent build can appear here by name while remaining INVALID, leaving hourly manifest cleanup without the intended index and preventing IF NOT EXISTS from rebuilding it. Exposing pg_index.indisvalid/indisready and the definition would let operators identify and repair that state.

(Based on your team's feedback about building the manifest index concurrently.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/ops/verify_supabase_swap.sql, line 73:

<comment>An interrupted concurrent build can appear here by name while remaining INVALID, leaving hourly manifest cleanup without the intended index and preventing `IF NOT EXISTS` from rebuilding it. Exposing `pg_index.indisvalid`/`indisready` and the definition would let operators identify and repair that state.

(Based on your team's feedback about building the manifest index concurrently.) </comment>

<file context>
@@ -0,0 +1,121 @@
+)
+ORDER BY name;
+
+SELECT indexname
+FROM pg_indexes
+WHERE schemaname = 'public'
</file context>
Fix with cubic

FROM pg_indexes
WHERE schemaname = 'public'
AND indexname = 'app_versions_manifest_present_idx';

SELECT EXISTS (
SELECT 1
FROM public.audit_logs
WHERE created_at < now() - interval '30 days'
LIMIT 1
) AS has_audit_logs_older_than_30d;

SELECT format(
$fmt$SELECT %L AS queue_name,
EXISTS (
SELECT 1
FROM pgmq.%I
WHERE archived_at < now() - interval '2 days'
LIMIT 1
) AS has_rows_older_than_2d;$fmt$,
queue_name,
'a_' || pg_catalog.lower(queue_name)
)
FROM pgmq.list_queues()
\gexec

SELECT format(
$fmt$SELECT %L AS queue_name,
EXISTS (
SELECT 1
FROM pgmq.%I
WHERE read_ct > 5
LIMIT 1
) AS has_stuck_read_ct_gt_5;$fmt$,
queue_name,
'q_' || pg_catalog.lower(queue_name)
)
FROM pgmq.list_queues()
\gexec

SELECT
'index hit rate' AS name,
ROUND((sum(idx_blks_hit)::numeric / nullif(sum(idx_blks_hit + idx_blks_read), 0) * 100), 2) AS ratio
FROM pg_statio_user_indexes
UNION ALL
SELECT
'table hit rate',
ROUND((sum(heap_blks_hit)::numeric / nullif(sum(heap_blks_hit) + sum(heap_blks_read), 0) * 100), 2)
FROM pg_statio_user_tables;
1 change: 1 addition & 0 deletions src/services/versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export function createBuiltinChannelVersion(channel: {
cli_version: null,
comment: null,
created_at: channel.created_at,
created_by_apikey_rbac_id: null,
deleted: false,
deleted_at: null,
external_url: null,
Expand Down
Loading
Loading