feat(db): migrate Slack connection IDs from env name to env id#5564
feat(db): migrate Slack connection IDs from env name to env id#5564
Conversation
Adds a knex migration that rewrites Slack notification connection IDs in
the admin account from the legacy `account-{uuid}-{envName}` format to
the new `account-{uuid}-{envId}` format, scoped to environments with
`slack_notifications = true`.
Includes a preview SQL helper to capture before/after state for manual
rollback (the knex down is a no-op since new and migrated connections
are indistinguishable after the dual-lookup deploy).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| @@ -0,0 +1,19 @@ | |||
| exports.config = { transaction: true }; | |||
There was a problem hiding this comment.
Happy to move this one into a new PR for first having deployed in prod all stuff that will be required for doing a rollback in case the rename is problematic.
This would allow us to first run the backup and later run the migration.
Note that the new created slack alerts will already follow the new convention after this one is merged, so there is no chance that we could be missing any record in case having to make a rollback of this migration
/cc @TBonnin @marcindobry
There was a problem hiding this comment.
I think you are worrying too much. The update query is atomic and not destroying any info that cannot be reconstructed. I will keep this PR simple with only the migration/sql (and the tests if you want to).
Of course make sure to validate everything works before/after migration locally and in staging.
There was a problem hiding this comment.
Yeps Im gonna do that, indeed Ive already run the query and downloaded the results for getting the connections that will be renamed, so we can use this for reconstructing later
| AND c.environment_id IN ( | ||
| SELECT e2.id | ||
| FROM _nango_environments AS e2 | ||
| JOIN _nango_accounts AS a2 ON e2.account_id = a2.id | ||
| WHERE a2.uuid = '${adminUUID}' | ||
| ) |
There was a problem hiding this comment.
you can simplify and avoid the subquery with the join I think
| AND c.environment_id IN ( | |
| SELECT e2.id | |
| FROM _nango_environments AS e2 | |
| JOIN _nango_accounts AS a2 ON e2.account_id = a2.id | |
| WHERE a2.uuid = '${adminUUID}' | |
| ) | |
| AND c.environment_id = e.id | |
| AND a.uuid = '${adminUUID}' |
There was a problem hiding this comment.
Unless I mistaken this will prune this JOIN FROM _nango_environments AS e JOIN _nango_accounts AS a ON a.id = e.account_id Not achieving what we wanted, see the reproduction that I did in local:
ango=# SELECT c.id, c.connection_id FROM _nango_connections AS c JOIN _nango_environments AS e ON true JOIN _nango_accounts AS a ON a.id = e.account_id WHERE c.connection_id = 'account-' ||
nango-# a.uuid || '-' || e.name AND c.provider_config_key = 'slack' AND c.environment_id IN (SELECT e2.id FROM _nango_environments AS e2 JOIN _nango_accounts AS a2 ON e2.account_id = a2.id WHERE
nango(# a2.uuid = '5f589ece-7f5a-41cd-a7f0-d5a3b9114b00') AND e.slack_notifications = true AND c.deleted = false;
id | connection_id
----+---------------------------------------------------
6 | account-ea0fb22f-515f-4aae-9767-7b8651d5c506-dev2
(1 row)
nango=# SELECT c.id, c.connection_id FROM _nango_connections AS c JOIN _nango_environments AS e ON e.id = c.environment_id JOIN _nango_accounts AS a ON a.id = e.account_id WHERE c.connection_id
nango-# = 'account-' || a.uuid || '-' || e.name AND c.provider_config_key = 'slack' AND a.uuid = '5f589ece-7f5a-41cd-a7f0-d5a3b9114b00' AND e.slack_notifications = true AND c.deleted = false;
id | connection_id
----+---------------
(0 rows)
Im not super happy with the original query, since it will expand the initial set of rows to any permutation of (c, e, a) and then will filter the ones that match the name, the ones that are from the root account, the ones that are slack name, etc.
There was a problem hiding this comment.
Oks finnaly manged to run the SELECT for getting a plan and check how much this update could cost :
explain analyze SELECT count(*)
FROM _nango_connections AS c
JOIN _nango_environments AS e ON true
JOIN _nango_accounts AS a ON a.id = e.account_id
WHERE c.connection_id = 'account-' || a.uuid || '-' || e.name
AND c.provider_config_key = 'slack'
AND c.environment_id IN (
SELECT e2.id FROM _nango_environments AS e2
JOIN _nango_accounts AS a2 ON e2.account_id = a2.id
WHERE a2.uuid = 'b209cb00-81f6-4b98-8395-29d553be7348'
)
AND e.slack_notifications = true
AND c.deleted = false;
QUERY PLAN
Aggregate (cost=1915.61..1915.62 rows=1 width=8) (actual time=1126.026..1126.030 rows=1 loops=1)
-> Hash Join (cost=797.08..1915.60 rows=1 width=0) (actual time=6.555..1125.916 rows=337 loops=1)
Hash Cond: (e.account_id = a.id)
Join Filter: ((c.connection_id)::text = ((('account-'::text || (a.uuid)::text) || '-'::text) || (e.name)::text))
Rows Removed by Join Filter: 126031
-> Nested Loop (cost=335.59..1453.15 rows=366 width=45) (actual time=0.897..1074.595 rows=126368 loops=1)
-> Nested Loop (cost=335.59..356.10 rows=1 width=37) (actual time=0.849..1.887 rows=359 loops=1)
-> Unique (cost=335.16..335.17 rows=2 width=4) (actual time=0.824..0.833 rows=3 loops=1)
-> Sort (cost=335.16..335.17 rows=2 width=4) (actual time=0.823..0.828 rows=3 loops=1)
Sort Key: e2.id
Sort Method: quicksort Memory: 25kB
-> Nested Loop (cost=0.29..335.15 rows=2 width=4) (actual time=0.592..0.820 rows=3 loops=1)
-> Seq Scan on _nango_accounts a2 (cost=0.00..326.16 rows=1 width=4) (actual time=0.581..0.804 rows=1 loops=1)
Filter: (uuid = 'b209cb00-81f6-4b98-8395-29d553be7348'::uuid)
Rows Removed by Filter: 13525
-> Index Scan using _nango_environments_account_id_index on _nango_environments e2 (cost=0.29..8.97 rows=2 width=8) (actual time=0.009..0.012 rows=3 loops=1)
Index Cond: (account_id = a2.id)
-> Index Only Scan using idx_connections_envid_connectionid_provider_where_deleted on _nango_connections c (cost=0.42..10.36 rows=10 width=41) (actual time=0.015..0.318 rows=120 loops=3)
Index Cond: ((environment_id = e2.id) AND (provider_config_key = 'slack'::text))
Heap Fetches: 60
-> Seq Scan on _nango_environments e (cost=0.00..1093.39 rows=366 width=8) (actual time=0.041..2.954 rows=352 loops=359)
Filter: slack_notifications
Rows Removed by Filter: 27050
-> Hash (cost=292.33..292.33 rows=13533 width=20) (actual time=3.071..3.071 rows=13526 loops=1)
Buckets: 16384 Batches: 1 Memory Usage: 815kB
-> Seq Scan on _nango_accounts a (cost=0.00..292.33 rows=13533 width=20) (actual time=0.005..1.570 rows=13526 loops=1)
Planning Time: 0.683 ms
Execution Time: 1126.099 ms
Execution Time: 1126.099 ms Im not super conderned
| RETURNING c.id | ||
| ) | ||
| SELECT | ||
| (SELECT COUNT(*) FROM updated) AS updated_rows; |
There was a problem hiding this comment.
UPDATE queries already returned the number of rows being touched. I don't think you need the CTE and count
Preview was already run and output saved externally. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a knex migration that rewrites Slack notification connection IDs in the admin account from the legacy
account-{uuid}-{envName}format to the newaccount-{uuid}-{envId}format, scoped to environments withslack_notifications = true.Includes a preview SQL helper to capture before/after state for manual rollback (the knex down is a no-op since new and migrated connections are indistinguishable after the dual-lookup deploy).