Django + sqlite3 のテスト環境で、Database is lockedエラーでハマった話


メモ書きに近い内容で恐縮ですが、同様のエラーが発生した方は参考にしてみてください。

対象の読者

  • Djangoでサーバサイドプログラミングを実施する方。
  • テスト環境でデータベースにsqlite3を用いている方。
  • 自動テストなどで、sqlite3に並行負荷をかける可能性のある方。

前提条件

  • Python: 3.6.3
  • Django: 2.1.3
  • TestCafe: 0.23.3
  • TestCafe Studio: 0.4.1

今回対象とする問題

OperationalError: database is locked

  • 画面操作の自動テスト時に、たまにテストが失敗する。(Django側でクラッシュする)
  • 用いるテストツールやタイミングによって、大丈夫な時とそうでない時がある。
  • 上記のエラーメッセージが、Djangoの標準エラー出力に出力される。

結論 (忙しい人はここまで)

  • Django + sqlite3の組み合わせで、設定で ATOMIC_REQUESTS = Trueを有効化している場合、ほぼ同時に更新リクエストが来た際、クラッシュする。

回避方法

  • settings.pyのsqliteに、ATOMIC_REQUESTSオプションがあれば、削除する。
settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
        # 'ATOMIC_REQUESTS': True # この行を無効化
    }
}
  • デフォルトではこのオプションは設定されていないため、設定済の方のみが今回の対象となります。
  • なお、公式のドキュメントにあるタイムアウト値を大きくする対処法を実施しても効果がありませんでしたが、ATOMIC_REQUESTSが無効でこのエラーが起きている場合は、試してみる価値はありそうです。

この回避方法について

  • あくまで、テスト環境向けの対処法であり、本番環境では非推奨です。
    • (そもそもsqlite3を使わないので、この問題自体が発生しない可能性あり)

備考

  • TestCafeのGUI版 (TestCafe Studio)で、たまたま、Javascriptが(通常のブラウザやCUI版 TestCafeと違い)多重実行されるバグ(?)に遭遇し、TestCafe Studioが管理するブラウザからDjango側にほぼ同時に2つの更新リクエストが発生していた。
  • 2個目の更新リクエストがエラーになり、E2Eテストが失敗扱いになった。

原因をどう発見したか

  • Djangoのエラーメッセージがトランザクションの衝突を示唆していたため、トランザクションおよびデータベースに関する設定を調べ、試行錯誤的に設定を変え発見した。

まとめ

  • テスト用ならsqlite3でも大丈夫と思っていると、思わぬ落とし穴にはまることがあります!

  • 皆様もお気をつけくださいm(_ _)m