Active Record와 mini_sql을 함께 사용하면 예상치 못한 동작이 발생합니다

일부 SQL 로그를 검토하던 중, Active Record와 mini_sql의 혼용 사용으로 인해 일부 쿼리가 코드의 의도와 일치하지 않는 것을 발견했습니다.

일부 reviewables를 업데이트할 때, 가능한 레이스 컨디션(race condition)을 방지하기 위해 트랜잭션 내에서 version을 증가시킵니다(아래 코드 스니펫 참조). 증가 연산이 트랜잭션의 일부인 것처럼 보이지만, 실제로는 그렇지 않습니다.

여기서 Reviewable.transaction은 Active Record의 기능인 반면, increment_version!은 mini_sql을 사용합니다:

Active Record는 mini_sql이 수행하는 작업을 인지하지 못합니다. Active Record는 어떤 지연(lazy) 기능을 사용하여 트랜잭션 내부의 첫 번째 쿼리가 발행되기 직전에 BEGIN을 발행하는 것 같습니다. 따라서 version이 먼저 증가되고, Active Record의 첫 번째 쿼리가 발행되기 직전에 트랜잭션이 시작됩니다. 그 결과 다음과 같은 상황이 발생합니다:

UPDATE reviewables SET version = version + 1 WHERE version=version AND id = reviewable.id RETURNING version;
BEGIN;
...

version 증가가 트랜잭션 외부에서 수행되면, 낙관적 잠금(optimistic locking)이 제공하는 일부 보장이 달성되지 않습니다. 증가 연산이 트랜잭션과 원자적(atomic)으로 처리되지 않기 때문입니다.

4개의 좋아요

흥미롭네요. mini sql을 통해 AR에 정보를 전달하는 방식을 가르쳐야 할 것 같습니다. 분명히 수정할 가치가 있습니다.

3개의 좋아요