開発におけるロックの重要性と ORM でのロックの実現例

アプリケーション開発においてデータを扱う時にロックを行うことはデータ保全性の観点から重要です。(もちろん SEROKU フリーランスでもシステム側ではデータのロックを行っています)

本記事では、一般的な話としてアプリケーションの「ロック」について掘り下げ、ロック方法の種類や DB のトランザクションとの関連性を紹介します。

次回以降は、楽観的ロックについてと、ORM を使ったロックの実現方法をアプリケーションフレームワークごとに紹介する予定です。

ロックとは

まず、ロックとはデータに対して読み書きの処理を同時に行うことを排除する仕組みです。

ロックを行わない場合、同じデータに対して複数の処理を実行すると、結果に整合性が取れなくなったりします。

例えば、商品カテゴリごとの売上の合計を計算する処理が実行されている間に、商品カテゴリが変更されると、商品カテゴリごとの売上の合計は総売り上げと違う値となってしまいます。

そこでロックを用いることで、一方の処理が完了するまでもう一方の処理の読み書きを禁止させることができ、結果の整合性を取ることが出来ます。

ロックを掘り下げる

システムの「どのレベル」でロックを行うかにより、考慮すべきポイントやロックできるデータの単位が異なります。レベル毎に掘り下げていきます。

DB レベルのロックとアプリケーションレベルのロック

DB には MySQL, PostgreSQL, Oracle, Microsoft SQL Server, MongoDB, Cassandra, Redis 等幾つか種類がありますが、ほぼ例外なくロック機能を有しています。

DB でロックを行う場合 DB によって詳細な挙動は異なりますが、読み書き(RW)を同時に排除するロック、書き込みのみを排除するロック等、「どの操作」に対してロックを行うかを決めることが出来たり、RDB におけるテーブルに対する操作を排除するロック、レコードに対する操作を排除するロック等、「どのデータ単位」に対してロックを行うかを決めることも出来ます。

一方で、アプリケーション内で行われる複数の処理において、それぞれ共通のルールを持たせることでロックを実現することが出来ます。(ルールを破る処理が無い前提)

アプリケーション全体で共有できるメモリ空間にロック有無を示す boolean 変数を用意し次のルールを設けることでロックが行えます。

  1. DB へ読み書きを行う際は必ずその変数を参照する
  2. ロック有無が false であれば変数を true にして DB へ読み書きを行い、終わったタイミングで変数を false に戻す
  3. ロック有無が true であれば変数が false となるまで待機する

トランザクションについて

トランザクションとは DB に対する一連の操作を一つの処理単位として扱う機能です。
処理の途中で失敗した場合は、操作を開始した時の状態にロールバックされます。
処理の完了はコミットと呼ばれます。

並列でトランザクションが実行される場合に、操作の一貫性を保てるようにするためにはロックが必要となります。
この時、どのような処理をロックするかをトランザクションの分離レベルと呼び、DB 毎に分離レベルがいくつか用意されています。

例えば、MySQL の分離レベルは次の種類があります。

  • READ UNCOMMITTED
  • READ COMMITTED
  • REPEATABLE READ
  • SERIALIZABLE

それぞれ簡単に説明します。

分離レベルを READ UNCOMMITTED に設定すると、トランザクション中に変更された非コミットな値の読み込みが可能です。

トランザクションを2つ実行した際に、一方で値の変更を行ったもののロールバックされた場合、もう一方で変更後の値が読み込まれると結果の整合性がとれない状態となる可能性があります。

分離レベルを READ COMMITTED に設定すると、コミットされた値が読み込まれるようになります。

しかし、トランザクションを2つ実行した際に、一方で値の変更が行われ、もう一方で同じ値を参照していたとした場合、値を変更するトランザクションが完了次第、もう一方のトランザクションからは変更後の値が参照されることになります。

そのため、値の参照を行う側のトランザクションを考えると、トランザクションの途中から同じ値の呼び出しを行った結果が異なることとなります。

分離レベルを REPEATABLE READ に設定すると、トランザクション中に何度値を読み込んでも結果が変わらないことが保証されます。

最後に、分離レベルを SERIALIZABLE に設定すると、トランザクションは並列に行われず順次実行されるようになります。

トランザクションシステムの信頼性を高める特性

トランザクションを行うことができるシステムにおいて、その信頼性を高めるために実現するとよいとされる特性があります。
特性の種類としては、トランザクション自体についての特性を示す ACID 特性、システム自体の特性を示す BASE 特性があります。

システム自体の特性として ACID 特性を持たせようとすると、可用性や性能が低下するトレードオフとなることから、可用性や性能を重視した BASE 特性が Eric Brewer により提唱されました。(参考情報: yohei-y:weblog: CAP とBASE について調べたこと)

ACID 特性と BASE 特性は共存可能なので、DB は ACID 特性を保ち、システムは BASE 特性に従うといった構成をとることができます。

ACID 特性

ACID 特性とは、次の4つの特性を指します。

  • 原子性(Atomicity)
  • 一貫性(Consistency)
  • 独立性(Isolation)
  • 永続性(Durability)

それぞれ簡単に説明すると、次のとおりです。

  • 原子性とはトランザクション処理が一連の処理「全てが実行された」か、あるいは「全てが実行されなかった」(実行前の状態に戻す) ことを保証する特性のこと
  • 一貫性とはトランザクション処理があらかじめ決められたルールに従って整合性が保たれていることを保証する特性のこと
  • 独立性とはトランザクション処理の途中経過が隠蔽できる特性のこと
  • 永続性とはトランザクション処理が完了したことをユーザに通知した時点で、その操作が永続化されたことを保証する特性のこと

詳細については ACID (コンピュータ科学) - Wikipedia 等を確認してみてください。

BASE 特性

BASE 特性とは、次の特性を指します。

  • Basically Available
  • Soft-State
  • Eventual Consistency

それぞれ簡単に説明すると、次のとおりです。

  • Basically Available とは可用性が基本である特性のこと

    • ロックにより処理が待機されることがないことを指します (楽観的ロック)
  • Soft-State とは緩いステート管理を行う特性のこと

  • Eventual Consistency とは途中で整合性が保たれていなくても、結果的に整合性が保たれていればよしとする特性のこと

まとめ

今回はロックについて掘り下げ、トランザクションについてとその特徴を紹介しました。

次回は、楽観的ロックについて紹介したいと思います。

関連記事

開発でのロックの重要性とORMのロック実現例〜楽観的ロックの利用例|フレームワーク O/R マッパー

開発でのロックの重要性とORMでのロックの実現例 |楽観的ロックの紹介