Ruby on Rails에서 데이터베이스 Advisory Locks 비활성화하기
문제: PgBouncer 트랜젝션 모드와 레일스 마이그레이션 충돌
루비 온 레일스Ruby on Rails에서는 마이그레이션 작업을 실행할 때 기본적으로 데이터베이스의 Advisory Locks 기능을 사용하고 있습니다.* 이는 마이그레이션이 동시에 일어나는 것을 방지하고 위한 목적으로 사용되며, 잠금이 되어있는 상태에서 마이그레이션을 실행할 경우 ActiveRecord::ConcurrentMigrationError
가 발생합니다.
* PostgreSQL의 Advisory Locks 기능에 대한 더 자세한 정보는 PostgreSQL의 문서를 참고해주세요: PostgreSQL: Documentation: 11: 13.3. Explicit Locking - Advisory Locks
원래는 마이그레이션의 동시 실행을 방지하기 위한 목적으로 사용됩니다만, PostgreSQL의 커넥션 풀링 용도로 PgBouncer의 트랜젝션 모드를 사용하는 경우 마찬가지로 ActiveRecord::ConcurrentMigrationError
이 발생할 수 있습니다. 이는 PgBouncer 내부 로직에서 Advisory Locks 기능이 사용되고 있기 때문이며, 이 문제는 레일스 쪽에도 이미 알려져있습니다(#31190). 이 문제를 해결하기 위해서는 마이그레이션 이전에 Advisory Locks을 해제해주거나, 마이그레이션에서 사용되는 잠금을 비활성화해야합니다. 잠금을 해제하려면 pg_advisory_unlock
와 같은 PostgreSQL의 함수를 사용하면 됩니다만, 커넥션 풀 관리를 위해 PgBouncer를 사용하는 경우 내부 로직에서 Advisory Locks이 사용되기 때문에 타이밍 이슈나 다른 문제가 발생할 수 있습니다.
이 글에서는 Advisory Locks를 사용하지 않도록 하는 방법을 소개합니다. 단, 이는 데이터베이스 어댑터 자체에 적용이되는 설정이므로 부작용이 생길 수 있고 프로덕션 환경에서는 상황에 맞춰 사용해야 합니다.
Ruby on Rails 6 이상의 경우
레일스 6 버전 이상의 버전에는 데이터베이스 설정에서 Advisory Locks를 사용하지 않도록 설정하는 기능이 추가되었습니다. 다음 내용을 database.yml
에서 적절한 위치에 추가해주면됩니다.
production:
adapter: postgresql
advisory_locks: false
자세한 내용은 다음 블로그의 내용을 참고해주세요.
Ruby on Rails 5의 경우
레일스 5에서는 advisory_locks: false
이 동작하지 않습니다. 하지만 Advisory Locks를 처리하는 PR(#33691)과 마이그레이션 관련 레일스 코드를 쫓아가 보면 Advisory Locks를 사용하지 않도록 하는 부분은 레일스 5에도 이미 구현이 되어있는 것을 확인할 수 있습니다. 단 supports_advisory_locks?
함수가 true
고정값으로 설정되어있어서 이 설정을 끄는 것이 불가능합니다. 이를 해결하기 위해서 해당 부분을 몽키패칭할 수 있습니다.
config/initializers/disable_advisory_locks.rb
파일을 생성하고 다음 내용을 추가해줍니다.
module ActiveRecord
module ConnectionAdapters
class PostgreSQLAdapter
def supports_advisory_locks?
false
end
end
end
end
이는 레일스에서 사용하는 PostgeSQLAdapter 클래스를 몽키패치하는 내용으로 Advisory Locks 기능을 비활성화하도록 강제합니다. 이는 true
를 반환하도록 되어있는 supports_advisory_locks?
메서드가 false
를 반환하도록 직접 수정하는 방법입니다. 마이그레이션 코드 상에는 이 부분을 처리하는 내용이 이미 포함되어있기 때문에 에러 없이 마이그레이션이 실행됩니다.
PostGIS를 사용하는 경우 아래와 같이 추가해줍니다.
module ActiveRecord
module ConnectionAdapters
class PostGISAdapter
def supports_advisory_locks?
false
end
end
end
end
Advisory Locks을 마이그레이션에서만 사용한다는 가정 하에서는 큰 문제가 될 부분은 없어 보입니다만, 혹시 모를 부작용이 생길 수 있으니 레일스 쪽의 이슈나 PR을 참고해주시기 바랍니다.