migration-lock-timeout icon indicating copy to clipboard operation
migration-lock-timeout copied to clipboard

Use session-level lock timeout for migrations with disable_ddl_transaction!

Open pjmartorell opened this issue 2 months ago • 0 comments

Summary

This PR addresses issue #16 by implementing session-level lock timeouts for migrations that use disable_ddl_transaction!.

Problem

Previously, migrations using disable_ddl_transaction! had no lock timeout protection because the gem skipped timeout application entirely. This left concurrent index creation and other non-transactional operations vulnerable to indefinite lock waits, which could cause production issues (as mentioned in the issue comments).

The original implementation couldn't use SET LOCAL lock_timeout because it only works within transactions, and disable_ddl_transaction! runs migrations outside of transactions.

Solution

  • Non-transactional migrations: Now use session-level timeout (SET lock_timeout = '5s')
  • Transactional migrations: Continue using transaction-level timeout (SET LOCAL lock_timeout = '5s')
  • Automatic cleanup: Session-level timeout is automatically reset after non-transactional migrations complete (RESET lock_timeout)

Breaking Change ⚠️

This is a breaking change that bumps the version to 2.0.0:

Before: Migrations with disable_ddl_transaction! had no timeout → waited indefinitely
After: These migrations now fail if locks can't be acquired within the configured timeout

Migration Guide

Users can adapt in three ways:

  1. Disable timeout for specific migration:
class AddIndexConcurrently < ActiveRecord::Migration
  disable_ddl_transaction!
  disable_lock_timeout!  # Explicitly opt-out
  
  def change
    add_index :large_table, :column, algorithm: :concurrently
  end
end
  1. Set custom timeout:
class AddIndexConcurrently < ActiveRecord::Migration
  disable_ddl_transaction!
  set_lock_timeout 30  # Wait up to 30 seconds
  
  def change
    add_index :large_table, :column, algorithm: :concurrently
  end
end
  1. Adjust default configuration in the initializer

Changes

  • Modified LockManager#migrate to detect disable_ddl_transaction and use appropriate timeout type
  • Added ensure block to reset session-level timeout after non-transactional migrations
  • Updated tests to verify session-level timeout behavior (all tests passing ✅)
  • Comprehensive documentation updates in README and CHANGELOG
  • Added migration examples and troubleshooting guide

Testing

All tests pass on ActiveRecord 7.1 and 7.1 with strong_migrations:

13 examples, 0 failures

Documentation

  • Updated README with technical explanation of why session-level timeouts are needed
  • Added code examples for both opting out and customizing timeouts
  • Added CHANGELOG entry with breaking change notice and migration guide
  • Marked as version 2.0.0 per semantic versioning

Fixes #16

pjmartorell avatar Oct 23 '25 10:10 pjmartorell