CassandraのCRUDを完全に理解した

<h2>注意</h2>
<strong>NoSQL初心者が自分の理解で書いているので間違ってるとこあったら教えてください。</strong><br>
主にApache Cassandraの商用パッケージDatastax Enterpriseという前提で進め、オープンソースのものと異なる場合があります。<br>
当記事のテーマはアーキテクチャーガイドを参考して分散DBであるCassandraではCRUDはどう動くのか解説していきます。<br>
もしよろしければTwitterもフォローしていただけると。。。<br>
<a href="https://twitter.com/youjie1211">My Twitter Account</a>

<h2>INSERT</h2>
CassandraのINSERTを理解するためにはまずどのようにしてデータが書き込まれるのかというアーキテクチャーから確認していきます。<br>
<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/497626/5e7da427-240b-a880-598d-e3a1fc75434a.png">
この画像を用いて説明します。<br>
<strong>
1.コミットログへデータを書き込む<br>
2.memtableへデータを書き込む<br>
3.memtableをフラッシュして永続化する<br>
4.ディスク上のSSTableへデータを格納する。<br>
</strong>
<br>
順を追って説明します。<br>
まずノードにデータが入ってきてからはcommit_logへとデータが渡されます。<br>
このコミットログに書き込むことによってmemtableがSSTableに書き込まれるまで保持される。起動時にリカバリも行われる<br>
つまり、この時点で障害が起きてもリカバリ可能となります。<br>
<br>
次にmemtableへ書き込み一定量を超えるとフラッシュされます。<br>
memtableそもそもデータをパーティションキーやクラスタリングカラムでソートされた状態で保持されます。<br>
nodetool flushコマンドやnodetool drainコマンドを利用することで主導でフラッシュできるのでノード再起動前に実施します。<br>
<br>
そして各テーブル1つづつ持っているSStableへ保管されます。<br>
これにてデータは不変のものとなりINSERTの流れとしては以上になります。
<br>
上記のコミットログの削除ですが、いくつか注意点があります。<br>
commit logはcommitlog_segment_size_in_mbという閾値を超えると新しいセグメントが作成されます。<br>
そして、セグメント内のデータすべてがフラッシュされた後にパージされます。<br>
commit logの最大サイズを示すcommitlog_total_space_in_mbに到達すると最も古いセグメントがパージされ対象テーブルはフラッシュされてしまいます。
<br>
<br>
<h2>SELECT</h2>
READするときの順番はアーキテクチャーをもとに確認すると以下画像の通りです。<br>
<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/497626/313a3306-4969-1781-8fd5-27f4cd695c1f.png"><br>
結構複雑なのでざっくりして説明します。<br>
基本Cassandraのデータの読み込みには3種類の方法があります。<br>
<strong>
1.memtableから引っ張ってくる方法<br>
2.Row Cacheを用いてデータがあるSStableから引っ張ってくる方法<br>
3.Bloom FilterでどこのSStableにあるか引っ張ってくる方法<br>
</strong>
<br>
1は簡単でCassandraはデータを登録するときcommit_logに書き込んだ後memtableに書き込みますが、このmemtable上にあるものを取ってきます。なので一番早いです。<br>
2はそもそもCassndraがどういった形でSStableに保存しているのか着目する必要があります。<br>
Cassandraはデータを格納するときパーティションキーをハッシュ化してそのハッシュ化したいくつかのパーティションキーはこのバイトからこのバイトまでが領域といった風に別テーブルでソートして保持しています。<br>
なのでRow Cacheを使うときはパーティションキーを別テーブルに問い合わせてそこからどのSStableのどのバイト数から始まっているか把握します。<br>
この動作によってCassandraはSStableからデータを引っ張ってきます。<br>
3はBloom Filterを使った方法です。<br>
Bloom FilterとはパーティションキーのデータがどのSStableにあるかチャックするものです。この中には特殊な形でたくさんのパーティションキーとそのSStableの位置を覚えています。<br>
ただし、これは100%正しいわけではなく、あくまで確率なので偽陽性を出すこともあります。<br>
<br>
基本はこのような形になっていてこの中で索引を使用するといったことが行われます。
<br>
<br>
<h2>UPDATE</h2>
<strong>そもそもOracleのようなUPDATEは存在せず、UPDATEは書き込みする際にとりあえず書き込んでしまいます(=upsert)</strong><br>
ただし、既存行と同じプライマリキーを持っている場合は既存行の更新になります。<br>
とはいってもチェックをしないので古いのと新しいのが混在する可能性があります。<br>
ではどうやって対処するのでしょうか？？<br>
そのためにはコンパクションを利用します。コンパクションの説明は下記サイトを参照<br>
<a href = "https://docs.datastax.com/ja/dse/6.7/dse-arch/datastax_enterprise/dbInternals/dbIntHowDataMaintain.html#dbIntHowDataMaintain__dml-compaction">Compactionに関して</a><br>
つまり、SSTableから一意の各行のバージョンをすべて収集し、各行のカラムの最新バージョン（タイムスタンプ順）を使用して1つの完全な行にまとめて古い物は固めて削除しちゃいます。<br>
このコンパクションは単独のノードで行われていきます。<br>
Replication_factorが1以上(いや、基本みんな1以上なんだけど、、)なら読み取り中にタイムスタンプを比較して一番新しいのを返してGossipの技術で古いものを新しいものへ置き換えてしまいます。
<br>
<br>
<h2>DELETE</h2>
こいつがやばい。Oracleと比べて考えることが多すぎる。。。初回じゃ理解できない。<br>
初回見たときの自分の図<br>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/497626/83ca0c86-b70c-e698-afca-21d27f37f570.jpeg"><br>
そもそも削除を理解するためにはtombstoneを理解する必要があります。<br>
<strong>Cassandraの削除はOracleのようにすぐに消してくれるわけではなくパラメータgc_grace_second(10日)デフォルトによって制御されています。</strong><br>
このtombstoneっていうのはシンプルにDELETE操作の時だけ作成されるものじゃなくて、、、<br>
ドキュメントを参照すれば多分わかるけど、一応簡単に、、<br>
<a href = "https://docs.datastax.com/ja/dse/6.7/dse-arch/datastax_enterprise/dbInternals/archTombstones.html">tombstoneに関して</a><br>
<strong>
1.DELETE文を打ったとき<br>
2.Time to Liveで有効期限が切れてしまったとき<br>
3.マテリアライズドビューの使用などによる内部操作<br>
4.nullを使用したINSERTないしはUPDATE操作←めっちゃ大事<br>
期限が切れたtombstoneは削除対象になってコンパクションの中で削除されてしまう。<br>
</strong>
<br>
Cassandraはノードがいくつもあるので削除プロセスが結構複雑になりがち、、、<br>
ある特定のノードがレコードの登録を行い、別ノードに伝搬した後削除を受け取ってtombstoneを立てる。<br>
この時にもし、受け取ったノードがダウンしててこの間に最初に削除を受け取ったノードがデータを削除してしまう<br>
zombieができて復活してきてしまう。(・ω・)ノシ<br>
<br>
これを防ぐためにはtombstoneに猶予、つまりtombstoneを回復して正常に処理する時間を与える必要がある。<br>
この時もタイムスタンプが一番新しいものが上書きされることになる。<br>
幸いgc_grace_secondは10日あるのでこの間にノードを復活させないといけない。じゃないと一貫性が保てない。<br>
zombieを防ぐためにはgc_grace_second内にノードを復活させnodetool repairコマンドを使って主導でリペアして行くことが大事<br>
<br>
<br>
<h2>最後に</h2>
今までOracleを触ってきましたが、Cassandraになると異なることばかりで驚くことばかりです。。<br>
今後もこんな感じで週１ペースでCassandraで分かりにくくないかと主観で思ったところをまとめていきます。<br>
<strong>*今回コード等も準備しておりましたが、参照先のドキュメントのほうにも豊富にありましたのでカットさせていただきました*</strong>