アトミック処理
前提
アトミックな操作はアトミックな命令によってのみ実現できる. アトミックな命令とは,以下が挙げられる:
CPUのレジスタの操作
メモリ上の1バイトの書込み/読み込み
ディスク上の1ブロックへの書込み
実はこれが大抵のストレージのファームウェアでアトミックになっていない
アトミックな命令はソフトウェアでもハードウェアでも実装可能であるが,
究極的にはハードウェア(CPU等)がアトミック命令の最小単位を決定する.
コンピュータにおいてはCPUのレジスタの操作がアトミック命令の最小単位である.
この小さいアトミック命令を組み合わせることで,より大きな処理もアトミックにすることができる.
ここではファイル操作やデータベース等の大きなデータに対する操作をアトミックにする方法について考える.
アトミックでない処理
たとえば
code: non_atomic
a = large_binary
for bit in a
bit |= 1
のような謎の処理があったとする.バイナリの全ビットを立てている.
この処理の最中に電源が落ちたりすると,中途半端な状態のバイナリだけが残され,途方にくれることになるかもしれない.
アトミックな処理のやりかた
1命令で更新できる参照を用意し,全ての読み書きはその参照を経由してアクセスするようにする,ということだ.
どれだけ大きなファイルの更新でも,かりに物理的に何命令かかろうとも,
たとえば
code:shadowpaging
a = large_binary
copied = a # copy
something_processing(copied)
a = copied
としてやれば,変数 a は,代入がアトミック命令である限り,処理前か処理後のどちらかの値しか返さない.
このように,直接そのデータ領域を操作することなく,一度コピーを行い,コピーに対して変更を加え,
ポインタの操作は1命令で行えることを利用したテクニックである.
たとえば,POSIXの rename メソッドはアトミックである.これを利用すれば, code: js
file = fs.read('file')
something_processing(file)
fs.write('file.tmp', file)
fs.rename('file.tmp', 'file')
のように file というファイル名を間接参照としてシャドーページングを実装することができる.
間接参照では何がつらいか
シャドーページングなどのアプローチの問題は,ファイルやデータ全体を完全にコピーしなければならないことだ.
小さいファイルならこれで結構だが,データベースや巨大なファイルの操作をこれで行うと,あまりに遅いし不要なコピーが発生しすぎる.
1MBの更新にもう1MBかかるのはいい
1TBあるデータベースの1要素を更新するのに,もう1TBいる,って言われたら頭おかしいと思いますよね!
in-placeな変更をアトミックに行うためには,WALを使うことが一般的である. WALを使ってin-placeにアトミック操作をする
あとで書く
Wikipediaに載っていない問題点があるのでとりあえず書いておく
TL;DR
間接参照
not in-place
スレッドローカルで無駄に空間を使う
ロックフリーなのでスレッド数が増えると効率が上がる
WAL
in-place
共有バッファ(ファイルとか)に差分だけ書いていくので軽量
ロックを使わざるを得ない. スレッド数が増えると優先度逆転したりして辛い
Parallel Loggingという手法が近年研究されている
WALには更新の差分のみが書かれる.
もちろんページ全体を書いてもいいけど差分のみで充分という意味
MySQLではアトミック性を担保するためにDouble Writeといってページ全体を二回書く処理がある
ダサい?
更新の差分といっても当然CPUの1命令で処理できるサイズとは限らない
update クエリの内容が64bit以上になるのはよくある,というか普通
ということはWALを書いている最中に他のスレッドがWALを書く,ということが難しい
Aの更新A1~A10とBの更新B1~B10があって,A1B1A2A3B2...と入り混じるとデコードが難しい
Parallel Loggingという手法があって,不可能ではない
間接参照はポインタ一つまで間接化可能なので,CPUのアトミック命令一つで操作できる