情報セキュリティの啓発を目指した、技術系コメディー自主制作アニメ「こうしす!」の@ITバージョン。第38列車は「レースコンディション」です。※このマンガはフィクションです。
この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
ここは姫路と京都を結ぶ中堅私鉄、京姫鉄道株式会社。
その情報システム(鉄道システムを除く)の管理を一手に引き受ける広報部システム課は、いつもセキュリティトラブルにてんてこ舞い。うわーん、アカネちゃーん。
「こうしす!」制作参加スタッフが、@IT読者にお届けするセキュリティ啓発4コマ漫画。
マンガのテーマは、「レースコンディション」です。
記念きっぷのシリアルナンバーのために、全駅共通の連番を新たに採番する仕組みを導入したところ、発売の瞬間に購入が集中し、シリアルナンバーが重複するきっぷが発行されてしまったという設定です。
例えば、AさんとBさんが順番にシリアルナンバーを採番した場合、意図された通りに採番が行われます。
しかしAさんとBさんが同時にシリアルナンバーを採番した場合は、以下のような事態が発生します。
これによって、AさんとBさんは同じシリアルナンバーのきっぷを手にしてしまいます。
このように複数のトランザクションが同一のデータに対して同時にアクセスすることにより、予期せぬ結果となってしまうことを「レースコンディション(競合状態)」といいます。
マンガはフィクションなので、現実の券売機でこのようなことは起こらないかもしれません。しかし、一般の開発現場ではよく遭遇する代表的な問題の一つでしょう。
レースコンディションへの対策として考えられる方法の一つとして、「リレーショナルデータベースのトランザクションを使用する」という方法があります。
このように片側のトランザクションを失敗させることで、一貫性を保つことができる――というのが教科書の世界での出来事です。
しかし現実の世界においては、ただトランザクションを使用すればいいというわけではありません。
それは、使用するデータベース製品によってデフォルトの挙動が異なるからです。重要なキーワードは、「トランザクション分離レベル」(Transaction Isolation Level)です。
トランザクション分離レベルには、一般的には以下の4種類が存在します。
詳しい説明は他に譲りますが、これらの分離レベルの違いを一言で説明すれば、SELECT(取得)クエリで読み取る際のロックの仕方や、ロックを待機するかどうかです。
この中で、どのようなトランザクションでもレースコンディションを防ぎ一貫性を保てるのは、「SERIALIZABLE」分離レベルのみとなります。
先ほどの例では、「SERIALIZABLE」分離レベルを前提としたものでした。しかしなぜそれが、教科書の世界の出来事なのでしょうか。
それは、前提となるデフォルトのトランザクション分離レベルに違いがあるからです。
教科書的には、デフォルトのトランザクション分離レベルは「SERIALIZABLE」とされることが一般的です。例えば、国内の標準規格であるJIS規格では、「ISO/IEC 9075-2:2011」を基にした「JIS X 3005-2:2015」が最新版ですが、これによると「トランザクション隔離性水準」(「トランザクション分離レベル」と同義)の既定が「SERIALIZABLE」であるとされているからです。
しかし現実世界では、データベース製品によってデフォルトのトランザクション分離レベルが異なります。
実際のところ、既定値を「SERIALIZABLE」としているリレーショナルデータベース製品は少数派です。筆者が探した限りでは1製品しか見つけられませんでした。これは「SERIALIZABLE」は同時実効性能が低く、パフォーマンスの問題から「READ COMITTED」分離レベルを既定値としている製品が多いことによります。
そのため大半のデータベース製品においては、トランザクション分離レベルを明示的に指定せず、教科書的な挙動を期待してデフォルト設定のままトランザクションを使用しても、期待通りの挙動になりません。レースコンディション対策としてトランザクションを使用したつもりが、結果として対策になっていないというケースがあるのです。
もし「レースコンディション対策のために、トランザクションを使用する」というようなことが仕様に記載されていた場合、単にトランザクションを使用するのでは不足があります。
まずは、使用するデータベース製品のデフォルトのトランザクション分離レベルを調べる必要があります。
一般的に「SERIALIZABLE」分離レベルは、パフォーマンスの問題があるとされています。
これには2つの理由があります。1つは共有ロックをコミットされるまで保持すること、もう1つはデッドロックが発生しやすいことです。
先ほどの例では、「Aさんのトランザクションがタイムアウトで失敗する」という事態が発生しました。
これは、トランザクションがコミットされるまで共有ロックが保持されることによって起こる事象です。共有ロックは複数のトランザクションが同時に掛けられる読み取り専用のロックで、これが掛かっている間は、他のトランザクションは排他ロック(書き込みロック)を取得できません。そのため、タイムアウトが発生するまで待機し続けることになります。更新トランザクションが殺到している状況では、常に待機とリトライを繰り返す状況となり、状況は悪化する一方となります。
ただ、そこで無思慮に「トランザクションは危険だ」とトランザクションの使用をやめたり、「SERIALIZABLEは悪い」とSERIALIZABLEトランザクション分離レベルの使用をやめたりするのは最悪手です。
この問題に対するアプローチは3つあります。
1. トランザクションの最初から排他ロック(書き込みロック)を取得しておく
1つ目は、データを取得してから更新するのではなく、データを更新してから取得するという方法です。
デッドロックが発生するのは、他のトランザクションが共有ロック(読み取りロック)を取得するからでした。
採番処理のような単純なケースでは、上記のようにいきなり更新してから結果を取得することで、共有ロック(読み取りロック)を取得せずに排他ロック(書き込みロック)のみを取得して更新する方法で、デッドロックを防げます。
余談になりますが、先ほど説明した通り、トランザクション分離レベルはあくまでもデータを取得する際のロックを制御するものでした。
このように、いきなり更新クエリを実行する場合では、実は「SERIALIZABLE」分離レベルを使用する必要がない場合があります。「SERIALIZABLE」分離レベルは、他のトランザクションが行を挿入するのを防ぐためにキー範囲ロックを取得しますが、常に1行しかないテーブルで行の更新(UPDATE)のみで採番するのであれば、行の挿入を防ぐ必要がありません。このようなケースでは、そもそも「READ COMITTED」分離レベル以上であれば何でもいいということになります。
2. 飛び番を許容し、トランザクションを可能な限り短くすること、または自動採番列を使用すること
次に考えられるのが、トランザクションを可能な限り短くすることです。
現実の業務システムにおいては、採番するだけで処理が完結することはまれで、採番したシリアルナンバーを用いて他の更新処理を行う必要があります。
その際、採番後、その他の更新を終えるまで同一のトランザクションで処理を実行し、全て成功すればコミット、失敗すれば採番も含めてロールバックするという仕様であれば、ロックを長時間取得したままとなってしまいます。ロックが長時間続けば、同時実効性能が低下するという結果となります。
そこで、採番だけを別トランザクションとし、後続処理が失敗したときに飛び番を許容するという方法があります。必ずしも連続していなくてもよいのであれば、この方法を採るのがいいでしょう。
実質的に同じことですが、自動採番列を使用するという方法もあります。
データベース製品によって名前は異なりますが、列の属性にIDENTITY、AUTO_INCREMENTなどを付加することで、行を追加した際に、自動で採番される仕組みがあります。
3. トランザクションではなく楽観的ロックを使用する
データの更新条件に、更新前のタイムスタンプや更新前の値を使用し、実際に更新された行数と、期待された更新行数とを比較する方法があります。
このような方法を「楽観的ロック」(optimistic locking)といいます。
採番処理のように頻繁に更新されるような処理には楽観的ロックは向きませんが、Entity FrameworkのようなO/Rマッパーでは、標準の同時実行制御として採用されたりします。
たかが採番処理、されど採番処理。
このように採番処理一つをとってみても、考慮するべきことがたくさんあります。トランザクションを使えば必ずしも安全とは限らず、トランザクション分離レベルの挙動や、トランザクションを使用しない方法など、さまざまな点を考慮して実装方法を検討しなければなりません。この機会にお使いのデータベース製品で挙動を試してみるのもいいかもしれません。
Copyright 2012-2023 OPAP-JP contributors.
本作品は特に注記がない限りCC-BY 4.0の下にご利用いただけます
アニメ「こうしす!」監督、脚本。情報処理安全確保支援士。プログラマーの本業の傍ら、セキュリティ普及啓発活動を行う。
著書:「こうしす!社内SE 祝園アカネの情報セキュリティ事件簿」(翔泳社)
「ハックしないで監査役!! 小説こうしす!EEシリーズ 元社内SE祝園アカネ 監査役編 [1]」(京姫鉄道出版)
dアニメストアにて、アニメ『こうしす!EE』配信中。
「物語の力でIT、セキュリティをもっと面白く」をモットーに、作品制作を行っています。
オープンソースなアニメを作ろうというプロジェクト。現在はアニメ「こうしす!」を制作中。
Copyright © ITmedia, Inc. All Rights Reserved.