デッドロック後のデータ損失 - SQL Server 2008、Ruby on Rails、Phusion Passenger、Linux、FreeTDS
私が担当している Ruby on Rails イントラネット アプリケーションで、データ損失を引き起こす原因となった謎の問題に直面しています。これがプログラミングの問題について厳密に話しているわけではない場合はお詫びします - 少なくとも私はアプリケーションの Ruby コードを保守しています。この問題は、これまで 2 年間で 3 回発生しています。
環境:
- Linux - RedHat エンタープライズ サーバー 5.2
- Apache 2 Web サーバー (httpd-2.2.3-11.el5_2.4.rpm)
- Phusion Passenger 2.2.15
- Ruby 1.8.7、Rails 2.3.8、gem:
- アクションメーラー (2.3.8)
- アクションパック (2.3.8)
- アクティブレコード (2.3.8)
- activerecord-sqlserver-adapter (2.3.8)
- アクティブリソース (2.3.8)
- アクティブサポート (2.3.8)
- あかみ(1.2.0)
- ビルダー (3.0.0)
- exception_notification (2.3.3.0)
- ファストスレッド (1.0.7)
- ぎょく (0.4.6)
- httpi (1.1.1)
- MIME タイプ (1.16)
- ノコギリ (1.4.4)
- 海苔 (1.1.3)
- 乗客 (2.2.15)
- ラック (1.1.0)
- レール (2.3.8)
- レーキ (0.8.7)
- ruby-net-ldap (0.0.4)
- rubyjedi-actionwebservice (2.3.5.20100714122544)
- サボン (1.1.0)
- わさび(2.5.1)
- will_paginate (2.3.14)
- SQL Server 2008 データベース サーバー
- ActiveRecord によるデータベース アクセス
- データベースドライバー: freetds-0.82、unixODBC-2.3.0.tar.gz、ruby-odbc-0.99991.tar.gz
症状:
- データベース リソースのロックを要求するユーザー アクションは、デッドロック状態に関係していました。
- SQL Server は、デッドロックに関係するプロセスを強制終了することでデッドロックを解決し、少なくとも一部のプロセスが正常に完了できるようにしました。
- Rails アプリケーション側では、デッドロックにより未処理の例外が発生しました (exception_notification gem を通じて通知されました)。
- デッドロックの後、アクティブな Rails プロセスの数が増加し (監視システムの別の通知がトリガーされました)、プロセスがハングしているように見えました
- これが起こった理由は不明です。プロセスはデータベース操作でハングしているように見えました (Rails ログによると)。通常、SQL サーバーのデッドロック解決機能によって、ブロッキング プロセスが放置されないことを期待していました。
- 最初の 2 つのケースでは、例外/ハング プロセスへの対応として Web サーバーを再起動しました。3 番目のケース (私は休暇中でした) では、誰も通知に反応しませんでしたが、週末に実行されていた cron ジョブがプロセスも停止していたようです (「restart.txt」にタッチして Passenger をソフト再起動しても、同じ効果が得られました)。
- Web サーバーの再起動後、ユーザーはデータ損失を報告しました。Web サーバーが再起動する前に、ユーザーの観点から、データは期待どおりに処理されました。私たちと通信する他のシステムの Rails ログとデータは、トランザクションが適切にコミットされたことを示しているようです。Web サーバーの再起動後、デッドロックが発生してからのすべてのデータベース変更が突然失われました。たとえば、ユーザー アクションごとに更新される「last_access」列を持つ「users」テーブルがあります。Web サーバーの再起動後、最新の「last_access」値は 1 日経過していました。すべてのトランザクションが欠落しているように見え、@@IDENTITY 値のみがデータ損失前に設定されていた値で継続されました。
- 私たちの IT (データベース サーバーを管理している) から、失われた DB 操作のすべてが 1 つの巨大なトランザクションの一部であり、最後の COMMIT が欠落していたことを示すと思われる情報を受け取りました。もちろん、Rails のすべてのユーザー アクションが 1 つ以上の個別のトランザクションを実行することを期待していますが、SQL Server のトランザクション ログには、すべての操作が 1 つの巨大なトランザクションの一部として表示されます。
このようなことが起こったかのように私には見えます:
- 関連するコンポーネントの 1 つ (Phusion Passenger、FreeTDS、SQL Server など) のバグが原因で、並行して実行されていた Rails プロセスがデータベース接続を共有し、プロセスの停止も引き起こした可能性があります。
- 関連するプロセスの 1 つがトランザクション中で、COMMIT の前のどこかでハングしていました。
- 他のプロセスは同じ接続を共有していたので(私が推測しているように)、それらも同じトランザクションにありました
- プロセスは接続を共有しているため、COMMIT が保留中であっても、ユーザーは (Web サーバーが再起動する前に) データの変更を確認できました。
- Web サーバーの再起動により、接続が中止され、トランザクションがロールバックされました。
それは理にかなっていますか?誰かが同様の経験や、さらに詳しく調べることができるヒントを持っているかどうか疑問に思っています. データベース接続のファイル記述子をフォークした可能性がある Passenger のバグを疑いましたが、再現できませんでした。Passenger は、すべてのフォークで新しい DB 接続を適切に作成しているようです。
デッドロックの数を減らすために、データベースの分離モデルを「コミットされたスナップショットの読み取り」に変更することを検討していますが、これでは根本的な原因が修正されず、他の問題が発生する可能性があることを認識しています。