From 237ee70bec4e4920c7d6f8a45dd21e53ce98b869 Mon Sep 17 00:00:00 2001 From: George Katsitadze Date: Mon, 24 Nov 2025 10:45:11 -0800 Subject: [PATCH 1/4] perf: optimize migration 371 to run faster on large deployments closes https://github.com/coder/coder/issues/20899 This is in response to a migration in v2.27 that takes very long on deployments with large `api_key` tables. --- ...371_api_key_scopes_array_allow_list.up.sql | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/coderd/database/migrations/000371_api_key_scopes_array_allow_list.up.sql b/coderd/database/migrations/000371_api_key_scopes_array_allow_list.up.sql index b38bf89880bed..bc68a32e1cf74 100644 --- a/coderd/database/migrations/000371_api_key_scopes_array_allow_list.up.sql +++ b/coderd/database/migrations/000371_api_key_scopes_array_allow_list.up.sql @@ -141,17 +141,24 @@ ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace_proxy:read'; ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace_proxy:update'; -- End enum extensions +-- Purge old API keys to speed up the migration for large deployments. +-- Note: that problem should be solved in coderd once PR 20863 is released: +-- https://github.com/coder/coder/blob/main/coderd/database/dbpurge/dbpurge.go#L85 + +DELETE FROM api_keys WHERE expires_at < NOW() - INTERVAL '7 days'; + -- Add new columns without defaults; backfill; then enforce NOT NULL -ALTER TABLE api_keys ADD COLUMN scopes api_key_scope[]; -ALTER TABLE api_keys ADD COLUMN allow_list text[]; +ALTER TABLE api_keys + ADD COLUMN scopes api_key_scope[], + ADD COLUMN allow_list text[]; -- Backfill existing rows for compatibility -UPDATE api_keys SET scopes = ARRAY[scope::api_key_scope]; -UPDATE api_keys SET allow_list = ARRAY['*:*']; - --- Enforce NOT NULL -ALTER TABLE api_keys ALTER COLUMN scopes SET NOT NULL; -ALTER TABLE api_keys ALTER COLUMN allow_list SET NOT NULL; +UPDATE api_keys SET + scopes = ARRAY[scope::api_key_scope], + allow_list = ARRAY['*:*']; --- Drop legacy single-scope column -ALTER TABLE api_keys DROP COLUMN scope; +-- Enforce NOT NULL and drop legacy single-scope column +ALTER TABLE api_keys + ALTER COLUMN scopes SET NOT NULL, + ALTER COLUMN allow_list SET NOT NULL, + DROP COLUMN scope; From 92911f52fdf6dbad7afe54002f8611208f369c8f Mon Sep 17 00:00:00 2001 From: George Katsitadze Date: Mon, 24 Nov 2025 12:17:10 -0800 Subject: [PATCH 2/4] chore: whitespace fix --- .../migrations/000371_api_key_scopes_array_allow_list.up.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/coderd/database/migrations/000371_api_key_scopes_array_allow_list.up.sql b/coderd/database/migrations/000371_api_key_scopes_array_allow_list.up.sql index bc68a32e1cf74..726bbca95ee4b 100644 --- a/coderd/database/migrations/000371_api_key_scopes_array_allow_list.up.sql +++ b/coderd/database/migrations/000371_api_key_scopes_array_allow_list.up.sql @@ -144,10 +144,11 @@ ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace_proxy:update'; -- Purge old API keys to speed up the migration for large deployments. -- Note: that problem should be solved in coderd once PR 20863 is released: -- https://github.com/coder/coder/blob/main/coderd/database/dbpurge/dbpurge.go#L85 - DELETE FROM api_keys WHERE expires_at < NOW() - INTERVAL '7 days'; +-- -- Add new columns without defaults; backfill; then enforce NOT NULL +-- ALTER TABLE api_keys ADD COLUMN scopes api_key_scope[], ADD COLUMN allow_list text[]; From f255b62434c1e0ca4fd97353862a781aa799cbaf Mon Sep 17 00:00:00 2001 From: George Katsitadze Date: Mon, 24 Nov 2025 15:22:25 -0800 Subject: [PATCH 3/4] chore: Minimize the diff wrt original migration where possible --- ...0371_api_key_scopes_array_allow_list.up.sql | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/coderd/database/migrations/000371_api_key_scopes_array_allow_list.up.sql b/coderd/database/migrations/000371_api_key_scopes_array_allow_list.up.sql index 726bbca95ee4b..12fb99f89f83f 100644 --- a/coderd/database/migrations/000371_api_key_scopes_array_allow_list.up.sql +++ b/coderd/database/migrations/000371_api_key_scopes_array_allow_list.up.sql @@ -146,20 +146,18 @@ ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace_proxy:update'; -- https://github.com/coder/coder/blob/main/coderd/database/dbpurge/dbpurge.go#L85 DELETE FROM api_keys WHERE expires_at < NOW() - INTERVAL '7 days'; --- -- Add new columns without defaults; backfill; then enforce NOT NULL --- -ALTER TABLE api_keys - ADD COLUMN scopes api_key_scope[], - ADD COLUMN allow_list text[]; +ALTER TABLE api_keys ADD COLUMN scopes api_key_scope[]; +ALTER TABLE api_keys ADD COLUMN allow_list text[]; -- Backfill existing rows for compatibility UPDATE api_keys SET scopes = ARRAY[scope::api_key_scope], allow_list = ARRAY['*:*']; --- Enforce NOT NULL and drop legacy single-scope column -ALTER TABLE api_keys - ALTER COLUMN scopes SET NOT NULL, - ALTER COLUMN allow_list SET NOT NULL, - DROP COLUMN scope; +-- Enforce NOT NULL +ALTER TABLE api_keys ALTER COLUMN scopes SET NOT NULL; +ALTER TABLE api_keys ALTER COLUMN allow_list SET NOT NULL; + +-- Drop legacy single-scope column +ALTER TABLE api_keys DROP COLUMN scope; From 47bba5bdb94a206f584495e3b81d4a5ad4b9a507 Mon Sep 17 00:00:00 2001 From: George Katsitadze Date: Tue, 25 Nov 2025 11:11:32 -0800 Subject: [PATCH 4/4] chore: add test fixture --- ...i_key_and_oauth2_provider_app_token.up.sql | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 coderd/database/migrations/testdata/fixtures/000371_add_api_key_and_oauth2_provider_app_token.up.sql diff --git a/coderd/database/migrations/testdata/fixtures/000371_add_api_key_and_oauth2_provider_app_token.up.sql b/coderd/database/migrations/testdata/fixtures/000371_add_api_key_and_oauth2_provider_app_token.up.sql new file mode 100644 index 0000000000000..cd597539971f1 --- /dev/null +++ b/coderd/database/migrations/testdata/fixtures/000371_add_api_key_and_oauth2_provider_app_token.up.sql @@ -0,0 +1,57 @@ +-- Ensure api_keys and oauth2_provider_app_tokens have live data after +-- migration 000371 deletes expired rows. +INSERT INTO api_keys ( + id, + hashed_secret, + user_id, + last_used, + expires_at, + created_at, + updated_at, + login_type, + lifetime_seconds, + ip_address, + token_name, + scopes, + allow_list +) +VALUES ( + 'fixture-api-key', + '\xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + '30095c71-380b-457a-8995-97b8ee6e5307', + NOW() - INTERVAL '1 hour', + NOW() + INTERVAL '30 days', + NOW() - INTERVAL '1 day', + NOW() - INTERVAL '1 day', + 'password', + 86400, + '0.0.0.0', + 'fixture-api-key', + ARRAY['workspace:read']::api_key_scope[], + ARRAY['*:*'] +) +ON CONFLICT (id) DO NOTHING; + +INSERT INTO oauth2_provider_app_tokens ( + id, + created_at, + expires_at, + hash_prefix, + refresh_hash, + app_secret_id, + api_key_id, + audience, + user_id +) +VALUES ( + '9f92f3c9-811f-4f6f-9a1c-3f2eed1f9f15', + NOW() - INTERVAL '30 minutes', + NOW() + INTERVAL '30 days', + CAST('fixture-hash-prefix' AS bytea), + CAST('fixture-refresh-hash' AS bytea), + 'b0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', + 'fixture-api-key', + 'https://coder.example.com', + '30095c71-380b-457a-8995-97b8ee6e5307' +) +ON CONFLICT (id) DO NOTHING;