はじめに
Twitterで下記のようなツイートをしたのですが、それの解説をする必要があるのでは、と思ったのでします。
これ「シャーディングで連番id振りたいときに困るから自前で連番idを振る」っていうことをしてるblog記事なんだけど、シャーディングするとその連番id振る処理そのものがネックになってスケールしない」から設計ミスなんだけど、指摘するべきか悩むhttps://t.co/kNcNhiHEtF
— ぽんこつ@本骨システム (@ponkotuy) 2018年11月15日
DBは正直あんまちゃんと勉強してないのであれなんですが、運用で得た知見で話をします。なんか指摘あったら下さい。
RDBMSの特徴と問題点
MySQLとかPostgreSQLとかSQLServerなど、お仕事なんかで良く使うRDBMSなんですが、共通していくつかの特徴があります。特にここで重要なのは正しいデータのみを保存する「データ完全性」を重視してる点と、仕様変更に対して(特にNoSQLと比較して)追従しやすい、どんなクエリでもそこまでコストかけずにできる、などの特徴があります。
一方で、NoSQLが一部で流行ってることからも分かるように万能ではなく、最も重要な問題として「本質的に書き込みの速度がスケールしない」という問題があります。なぜなら、RDBMSの主要実装は、正しいデータが書き込まれたことを保証するために、書き込みを1箇所に集約しているからです。これが一般に言うmasterです。読み込みはslaveを立てることによって書き込みよりはスケールします。
また、RDBMSが本質的に苦手とするいくらかのクエリがあったり(集計なんかは割と苦手なイメージあります)、特化したタイプのNoSQLであればRDBMSより特定クエリが圧倒的に早かったりもします。
つまり簡単に言えば速度を若干犠牲にしつつ、正しいデータを柔軟に扱うことを重視したのがRDBMSと言えます。
シャーディング
先程RDBMSは書き込みがスケールしないと言いましたが、ほぼ全てのRDBMS実装に解決する手段が用意されています。シャーディングです。シャーディングを利用して、そもそもの書き込み先を予め決めたルールに従って処理するサーバを割り振るようにしてしまえばよいという訳です。
そのようなものなので、シャーディングには様々な技術的な制約が付き纏いますが、RDBMSでちゃんとスケールしようとした場合必須になります。
連番ID
ここで本題に戻って連番IDの話をします。insertした順番にidを1→2→3と振ってく仕組みで、MySQLにはAUTO_INCREMENTとかあります。
ところでこの連番ID、きちんと重複無しに振っていくのは意外と難しいです。次のIDを保存する領域作って、取得する際にTransactionなどでロックを掛けて他のスレッドの割り込みが無いことを保証してIDを取得する必要があります。
1台ならあまり問題にならないこの仕組みですが、複数台を跨ぐと途端に問題が爆発します。複数台にIDを発行する必要があり、重複しないように管理しようとすると当然masterのような仕組みが必要になるでしょう。そうするとIDの発行がネックになってスケールしないことが分かります!なんのためにシャーディングにしたのでしょうか。RDBMSをスケールするためだったとおもうのですが。
そもそもなんですが、本質的にIDって被らなければ何でもいいものなので、連番である必要はない筈なんですね。勿論連番ID便利なこともちょいちょいあるのでスケールする必要がなければ使えばいいんですが。
そのようなIDを振る候補としては、素直にUUID(128bit)を使うか、シャーディングのルール内でunique制約を付けたランダムな値を含んだ64bit整数とかを使うといいです。どういうものかというと、シャーディングルールで64bitのidを32で割った余りが担当するサーバという設定にしておき、このidに対してunique制約を張るというものです。idチェックは1台のノード内で完結するのであまり大きなオーバーヘッド無しでidを設定することができます。(失敗したら重複したので再設定して再投入)