Rails 트랜잭션 콜백의 신기한 동작

사승준·2022년 8월 8일
0
post-thumbnail

기본적으로 액티브 레코드 콜백은 정의한 순서대로 실행이 된다. 하지만 트랜잭션 콜백의 경우에는 역순으로 실행이 되는데 그 이유에 대해서 알아보자

트랜잭션 콜백이란?

orm 메서드를 사용해서 crud를 할 때 트랜잭션 안에서 쿼리를 실행하게 되는데 콜백이 트리거 되는 기준이 트랜잭션이 끝난 후에 호출이 되는 콜백이다.

수많은 콜백 들 중에 트랜잭션 콜백은 딱 두 가지가 있는데 after_commit, after_rollback 이렇게 두 가지가 있다.

비트랜잭션 콜백 실행

after_save: order1
after_save: order2

def order1
  puts 'order1'
end

def order2
  puts 'order1'
end

after_save 콜백이 실행됐을 때 콜백이 차례대로 실행된다.

order1
order2

트랜잭션 콜백 실행

after_commit: order1
after_commit: order2

def order1
  puts 'order1'
end

def order2
  puts 'order1'
end

after_commit 콜백이 실행됐을 때 콜백이 역순으로 실행된다.

order2
order1

트랜잭션 콜백이 역순으로 실행되는 이유?

콜백이 생성된 순서를 보기 위해서before_create, after_create, after_commit을 정의한 뒤에 콜백 체인을 array 형태로 불러왔다.

_*.callbacks로 해당 모델의 정의된 클래스를 불러올 수 있다.


여기서 놀라운 점은 after_commit의 경우에는 콜백 체인의 순서가 정의한 대로 나오고 있고 after_create의 경우 역순으로 나오고 있었다. 트랜잭션 콜백 체인이 역순으로 생성됐기 때문에 역순으로 실행된 거라고 생각했는데 그렇지 않았다.

공식문서를 살펴보니 after_* 콜백의 경우에는 역순으로 실행이 된다고 한다.

결국 after_*콜백의 경우는 모두 역순으로 실행이 되는데 after_* 비트랜잭션 콜백의 경우 콜백이 새롭게 추가되면 기존 콜백 체인의 뒤에 생성이 되는 게 아니라 앞에 생성이 된다. 그래서 이렇게 생성된 콜백 체인을 역순으로 실행하니 차례대로 실행이 되는 것이었다.

# 기본적으로 after_*의 경우 콜백 체인이 오른쪽에서 왼쪽으로 실행이 된다.

비트랜잭션 콜백
[order4, order3] --> 새로운 콜백 추가 시에 콜백 체인 맨 앞에 추가됨

트랜잭션 콜백
[order5, order6] --> 새로운 콜백 추가 시에 콜백 체인 뒤에 추가됨

콜백 체인이 앞에 생기는 이유

그럼 왜 트랜잭션 콜백이 아닌 after_* 콜백은 새롭게 추가되면 콜백 체인 앞에 추가가 되는 것일까? 원인은 바로 prepend옵션 때문이었다.
prepend 옵션의 값이 true일 경우 콜백 체인의 맨 앞에 콜백이 추가가 되는데 비트랜잭션의 경우에는 이 옵션 값을 강제로 true로 박아주고 있었다. 이해를 좀 더 돕고자 rails 프로젝트의 코드를 까봤다. 브랜치는 6-1-stable 브랜치를 기준으로 봤다.

일단 트랜잭션 콜백과 비트랜잭션 콜백이 정의되고 있는 파일이 달랐다.
비트랜잭션 콜백의 경우에는 activerecord/lib/active_record/callbacks.rb에서 정의를 해주고 있다.


types에 before, around, after 가 array 형태로 담겨있고 each를 돌면서 콜백들을 정의해준다.

_define_after_model_callback의 코드를 보면 options[:prepend]를 강제로 true로 정의해주고 있다. 이 코드로 인해서 비트랜잭션 after_* 콜백의 경우에는 새로운 콜백이 추가될 때 콜백 체인의 앞에 생성이 되는 것이다.

반대로 비트랜잭션 콜백의 경우에는 activerecord/lib/active_record/transactions.rb에서 정의를 해주고 있고 해당 코드를 따라갔을 때 prepend 옵션을 정의해주고 있지 않다.

결론

after_* 콜백의 경우에는 역순으로 실행되는데 트랜잭션 콜백의 경우에는 prepend 옵션을 강제로 true로 주지 않고 있다. 그래서 콜백 체인이 정의한 순서대로 생성이 돼고 이 콜백 체인을 역순으로 실행해서 발생한 문제다.

다소 헷갈릴 수도 있고 콜백의 순서가 중요할 경우 문제가 발생할 수 있어 해당 이슈에 대한 pr들이 여러 개 올라왔지만 레일즈 측에서는 따로 대응 하고 있지는 않은 것 같다.

콜백의 순서가 중요해서 역순으로 실행되는 콜백을 차례대로 실행시키기 위해서는 콜백을 정의할 때 prepend 옵션을 true로 주거나 콜백의 순서를 반대로 정의하면 될 것 같다.

after_commit: order1
after_commit: order2, prepend: true

or 

after_commit: order2
after_commit: order1

링크

profile
Ruby on Rails 개발하고 있는 서버 개발자입니다.

0개의 댓글