일부 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)으로 처리되지 않기 때문입니다.