Skip to content

Commit d881296

Browse files
committed
fix: add unique index on subscriptions to prevent duplicate entries
- Uses disable_ddl_transaction! (https://api.rubyonrails.org/classes/ActiveRecord/Migration.html#method-i-disable_ddl_transaction-21) - Uses algorithm: :concurrently (https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_index) - Removes 31 duplicate subscriptions from production database - Prevents race conditions from causing duplicate entries in future Why (member_id, group_id) not (member_id, chapter_id)? - Index on (member_id, group_id) allows a member to be both student AND coach in the same chapter - Each chapter has separate "Students" and "Coaches" groups - different group_id values - This index prevents duplicate subscriptions to the SAME group (the race condition bug) - But allows valid dual membership: member can subscribe to group_id=1 (Students) AND group_id=2 (Coaches)
1 parent 75ecc09 commit d881296

File tree

2 files changed

+32
-1
lines changed

2 files changed

+32
-1
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
class FixDuplicateSubscriptionsAndAddUniqueIndex < ActiveRecord::Migration[8.1]
2+
disable_ddl_transaction!
3+
4+
def up
5+
execute <<~SQL
6+
DELETE FROM subscriptions
7+
WHERE id NOT IN (
8+
SELECT MIN(id)
9+
FROM subscriptions
10+
GROUP BY member_id, group_id
11+
)
12+
AND (member_id, group_id) IN (
13+
SELECT member_id, group_id
14+
FROM subscriptions
15+
GROUP BY member_id, group_id
16+
HAVING COUNT(*) > 1
17+
)
18+
SQL
19+
20+
add_index :subscriptions,
21+
%i[member_id group_id],
22+
unique: true,
23+
name: 'index_subscriptions_on_member_id_group_id',
24+
algorithm: :concurrently
25+
end
26+
27+
def down
28+
remove_index :subscriptions, name: 'index_subscriptions_on_member_id_group_id'
29+
end
30+
end

db/schema.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#
1111
# It's strongly recommended that you check this file into your version control system.
1212

13-
ActiveRecord::Schema[8.1].define(version: 2026_03_30_193245) do
13+
ActiveRecord::Schema[8.1].define(version: 2026_04_08_220120) do
1414
# These are extensions that must be enabled in order to support this database
1515
enable_extension "pg_catalog.plpgsql"
1616

@@ -525,6 +525,7 @@
525525
t.integer "member_id"
526526
t.datetime "updated_at", precision: nil
527527
t.index ["group_id"], name: "index_subscriptions_on_group_id"
528+
t.index ["member_id", "group_id"], name: "index_subscriptions_on_member_id_group_id", unique: true
528529
t.index ["member_id"], name: "index_subscriptions_on_member_id"
529530
end
530531

0 commit comments

Comments
 (0)