Computer >> 컴퓨터 >  >> 프로그램 작성 >> Ruby

몇 가지 콜백 문제(및 Rails 5 수정)

ActiveRecord 콜백은 모델 수명의 여러 단계에서 코드를 쉽게 실행할 수 있는 방법입니다.

예를 들어 Q&A 사이트가 있고 모든 질문을 검색할 수 있기를 원한다고 가정해 보겠습니다. 질문을 변경할 때마다 ElasticSearch와 같은 곳에서 인덱싱하고 싶을 것입니다. 인덱싱은 시간이 걸리고 급하지 않으므로 Sidekiq를 사용하여 백그라운드에서 수행합니다.

지금이 after_save를 사용하기에 완벽한 시기인 것 같습니다. 콜백! 따라서 모델에서 다음과 같이 작성합니다.

앱/모델/question.rb
class Question < ActiveRecord::Base
  after_save :index_for_search

  # ...

  private
  
  def index_for_search
    QuestionIndexerJob.perform_later(self)
  end
end
앱/작업/question_indexer_job.rb
class QuestionIndexerJob < ActiveJob::Base
  queue_as :default

  def perform(question)
    # ... index the question ...
  end
end

이것은 잘 작동합니다! 또는 적어도 그런 것 같습니다. 더 많은 작업을 대기열에 추가하고 다음 오류가 표시될 때까지:

2015-03-10T05:29:02.881Z 52530 TID-oupf889w4 WARN: Error while trying to deserialize arguments: Couldn't find Question with 'id'=3

물론, Sidekiq은 작업을 다시 시도할 것이며 아마도 다음 번에 작동할 것입니다. 그러나 여전히 조금 이상합니다. Sidekiq이 방금 저장한 질문을 찾을 수 없는 이유는 무엇입니까?

프로세스 간의 경쟁 조건

Rails는 after_save를 호출합니다. 레코드가 저장된 직후 콜백. 하지만 Sidekiq가 사용하는 것과 같은 다른 데이터베이스 연결에서는 해당 레코드를 데이터베이스 트랜잭션까지 볼 수 없습니다. 조금 후에 발생합니다. 즉, 사용자가 질문을 저장한 후 커밋하기 전에 Sidekiq에서 질문을 찾으려 할 가능성이 있습니다. 기록을 찾지 못하고 폭발합니다.

이 문제는 매우 일반적이어서 Sidekiq에 이에 대한 FAQ 항목이 있습니다. 그리고 쉽게 고칠 수 있습니다.

after_save 대신 :

앱/모델/question.rb
class Question < ActiveRecord::Base
  after_save :index_for_search

  # ...
end

after_commit 사용 :

앱/모델/question.rb
class Question < ActiveRecord::Base
  after_commit :index_for_search

  # ...
end

또한 Sidekiq에서 모델을 볼 수 있을 때까지 작업이 대기열에 추가되지 않습니다.

따라서 백그라운드 작업을 대기열에 넣거나 방금 변경한 내용을 다른 프로세스에 알릴 때 after_commit를 사용하세요. . 그렇지 않으면 방금 터치한 레코드를 찾지 못할 수 있습니다.

하지만 문제가 하나 더 있습니다...

알겠습니다. after_save after_commit을 사용하기 위한 후크 대신에. 모든 것이 작동하는 것 같습니다. 모두 확인하고 집에 갈 시간이죠?

먼저 테스트를 실행하고 싶을 것입니다.

테스트/모델/question_test.rb
require 'test_helper'

class QuestionTest < ActiveSupport::TestCase
  test "A saved question is queued for indexing" do
    assert_enqueued_with(job: QuestionIndexerJob) do
      Question.create(title: "Is it legal to kill a zombie?")
    end
  end
end
  1) Failure:
QuestionTest#test_A_saved_question_is_queued_for_indexing [/Users/jweiss/Source/testapps/after_commit/test/models/question_test.rb:7]:
No enqueued job found with {:job=>QuestionIndexerJob}

이런! 테스트가 작업을 대기열에 넣어야 하지 않습니까? 방금 무슨 일이?

기본적으로 Rails는 각 테스트 케이스를 자체 데이터베이스 트랜잭션으로 래핑합니다. 이것은 정말 속도를 높일 수 있습니다. 테스트 중에 수행한 모든 변경 사항을 취소하려면 단 하나의 데이터베이스 명령만 있으면 됩니다.

하지만 이것은 after_commit도 의미합니다. 콜백이 실행되지 않습니다. after_commit 때문에 콜백은 가장 바깥쪽일 때만 실행됩니다. 트랜잭션이 커밋되었습니다.

save을 호출할 때 테스트 케이스 내에서 여전히 트랜잭션을 커밋하지만(다소나마) 이는 가장 바깥쪽입니다. 지금 거래. 따라서 after_commit 콜백은 예상할 때 실행되지 않습니다. 그리고 내부에서 일어나는 일을 테스트할 수 없습니다.

이 문제도 쉽게 해결할 수 있습니다. test_after_commit 포함 Gemfile의 gem:

젬 파일
group :test do
  gem "test_after_commit"
end

그리고 after_commit 후크는 마지막에서 두 번째 다음에 실행됩니다. 트랜잭션 커밋. 예상했던 일입니다.

"이상하다. Rails와 함께 제공되는 콜백을 테스트하기 위해 완전히 별도의 gem을 사용해야 하는 이유는 무엇입니까? 그냥 자동으로 일어나야 하는 거 아닌가요?”

네가 옳아. 이상하다. 하지만 오랫동안 이상하지 않을 것입니다.

Rails 5가 출시되면 test_after_commit에 대해 걱정할 필요가 없습니다. . 이 문제는 약 한 달 전에 Rails에서 수정되었기 때문입니다.

내 코드에서는 after_commit을 사용합니다. 많이. 아마도 after_save를 사용하는 것보다 더 많이 사용하는 것 같습니다. ! 그러나 문제와 이상한 경우가 없는 것은 아닙니다.

버전별로 개선되고 있습니다. 그리고 after_commit을 사용할 때 적절한 장소에서 많은 이상하고 무작위적인 예외가 더 이상 발생하지 않을 것입니다.