Invitation Log Entry Global Unique Constraint Failure
Date: 2026-04-10
Status: Fixed in PR #2560
Problem
Invitation batch #28 for Berlin chapter students (Workshop #3668) became stuck in running status after processing only 5 of 431 eligible students. The batch crashed with:
ActiveRecord::RecordInvalid: Validation failed: Entries is invalid
InvitationLogger#fail_batch(/app/app/services/invitation_logger.rb:71)
Investigation
Batch State Analysis
Workshop #3668 (Berlin Chapter):
563 students subscribed to "Students" group
133 students already had invitations (from previous "everyone" batches)
431 students still needed invitations
Batch member waiting list (sessions) #28 processed only 5 students before crashing
Batch #28 Details:
Started: 2026-04-10 08:44:14
Status: running (stuck)
Success count: 5
Audience: "students"
Only 5 log entries created (members 14756, 29864, 19137, 25821, 28609)
Root Cause
The database schema has a mismatch between the model validation and the database constraint:
Database constraint (migration line 40):
add_index :invitation_log_entries , %i[ invitation_type invitation_id ] , unique : true
This enforces global uniqueness - each invitation can only have ONE log entry across ALL batches.
Model validation:
validates :member_id , uniqueness : { scope : %i[ invitation_type invitation_id ] } , allow_nil : true
This validates (member_id, invitation_type, invitation_id) is unique - each member can only have one entry per invitation.
The constraint is MORE restrictive than the validation.
Failure Sequence
A previous batch (e.g., "everyone" batch Moves seats from sessions to sponsor #22 , digital-science #24 , Add gittip #25 , or add spots available email #26 ) created log entries for some invitations
Batch member waiting list (sessions) #28 ("students") starts processing Berlin students
For each student, find_or_build_entry attempts to create a log entry
When the database INSERT hits the unique constraint violation, find_or_create_by raises ActiveRecord::RecordNotUnique
The exception triggers fail_batch(e) in the rescue block
fail_batch tries to update the log: @log.update!(status: :failed, ...)
Rails validates all entries in @log.entries, including the unsaved invalid entry
Validation fails: "Entries is invalid"
The log remains stuck in running status
Why the Constraint Was Wrong
Multiple audiences: Different batches (students, coaches, everyone) may process overlapping member lists
Retry scenarios: Failed batches can be retried, requiring re-logging of entries
Within-batch uniqueness: Already handled by find_or_create_by(member: member, invitation: invitation) + processed_at check in the PR fix: handle duplicate InvitationLogEntry on retry (v2) #2558 fix
The global constraint prevents legitimate use cases:
Member subscribed to both "Students" and "Coaches" groups (sends to both groups)
Retrying a failed batch (entries can be re-logged)
Running separate "students" and "coaches" batches for the same workshop
Solution
Migration: Remove Global Unique Constraint
Remove the overly restrictive unique index and add a batch-scoped index for performance.
Model: Remove Validation
The validation matched the wrong constraint. Within-batch uniqueness is enforced by find_or_create_by on @log.entries.
Post-Deployment Actions
Clear stuck batch: UPDATE invitation_logs SET status = 'failed' WHERE id = 28;
Verify affected workshops: SELECT id, loggable_id, audience, status, success_count FROM invitation_logs WHERE status = 'running';
Trigger new invitation batches for any workshops with stuck batches
Related Issues
Lessons Learned
Database constraints must match business logic: The global unique constraint was more restrictive than the model validation, creating a hidden bug
Test cross-batch scenarios: Tests only covered within-batch uniqueness, not cross-batch logging
Index naming matters: Random hash suffixes in index names (_6d6ef495e6) make debugging harder
Consider retry semantics early: If batches can be retried, log entries must allow duplicates across batches
Invitation Log Entry Global Unique Constraint Failure
Date: 2026-04-10
Status: Fixed in PR #2560
Problem
Invitation batch #28 for Berlin chapter students (Workshop #3668) became stuck in
runningstatus after processing only 5 of 431 eligible students. The batch crashed with:Investigation
Batch State Analysis
Workshop #3668 (Berlin Chapter):
Batch #28 Details:
running(stuck)Root Cause
The database schema has a mismatch between the model validation and the database constraint:
Database constraint (migration line 40):
This enforces global uniqueness - each invitation can only have ONE log entry across ALL batches.
Model validation:
This validates
(member_id, invitation_type, invitation_id)is unique - each member can only have one entry per invitation.The constraint is MORE restrictive than the validation.
Failure Sequence
find_or_build_entryattempts to create a log entryfind_or_create_byraisesActiveRecord::RecordNotUniquefail_batch(e)in the rescue blockfail_batchtries to update the log:@log.update!(status: :failed, ...)@log.entries, including the unsaved invalid entryrunningstatusWhy the Constraint Was Wrong
students,coaches,everyone) may process overlapping member listsfind_or_create_by(member: member, invitation: invitation)+processed_atcheck in the PR fix: handle duplicate InvitationLogEntry on retry (v2) #2558 fixThe global constraint prevents legitimate use cases:
Solution
Migration: Remove Global Unique Constraint
Remove the overly restrictive unique index and add a batch-scoped index for performance.
Model: Remove Validation
The validation matched the wrong constraint. Within-batch uniqueness is enforced by
find_or_create_byon@log.entries.Post-Deployment Actions
UPDATE invitation_logs SET status = 'failed' WHERE id = 28;SELECT id, loggable_id, audience, status, success_count FROM invitation_logs WHERE status = 'running';Related Issues
find_or_create_by+processed_atcheck)Lessons Learned
_6d6ef495e6) make debugging harder