成長しても慌てない!データベースのスケーリングを楽にするためのシンプルなヒント
若いプロジェクトに取り組んでいるとき、私たちは常に、後々のスケールを容易にするか、それとも困難にするかの決断を迫られています。時には短期的なメリットのためにあえて技術的負債を抱えるのも賢明です。早くリリースするためです。しかし中には、別の選択肢があることに気づかないまま、不必要に技術的負債を背負ってしまうケースもあります。
私たちHoneybadgerでも、いくつかの判断ミスによって、必要以上に苦労した経験があります。いくつかの重要なポイントを最初から理解していれば、スケーリングはずっと楽だったはずです。
UUIDを採用する
「プライマリーキー」と聞いて、多くの人が思い浮かべるのは自動増分される整数でしょう。小規模なシステムではこれで十分機能しますが、スケールする段階になると大きな問題が生じます。
ある瞬間において、プライマリーキーを生成できるデータベースサーバーは1台だけだからです。つまりすべての書き込みが単一のサーバーを経由しなければなりません。毎秒数千件の書き込みを実現したい場合、これは深刻なボトルネックになります。
UUIDをプライマリーキーとして使えば、この問題を回避できます。ご存じない方のために説明すると、UUIDとは 123e4567-e89b-12d3-a456-426655440000 のような形式の一意な識別子のことです。
Wikipediaでは以下のように解説されています。
標準的な方法で生成されたUUIDは、中央の登録機関や生成者間の調整を一切必要とせず、実用上ほぼ確実に一意となります。UUIDが重複する確率はゼロではありませんが、無視できるほど微小です。
したがって、誰でもUUIDを作成し、それが既存の識別子と重複せず、今後も重複しないことをほぼ確信したうえで利用できます。そのため、独立した組織がそれぞれUUIDでラベル付けした情報でも、識別子の衝突を解決することなく、後から1つのデータベースへ統合したり、同一チャネルで送信したりすることが可能です。
UUIDをプライマリーキーにすれば、すべての書き込みを1つのデータベースに集中させる必要がなくなり、複数のサーバーへ分散できます。
さらに、レコードをデータベースに保存する前にIDを生成できるという柔軟性も得られます。キャッシュサーバーや検索サーバーへレコードを送りたいけれど、データベーストランザクションの完了は待ちたくない、といった場面で役立ちます。
RailsアプリでデフォルトのプライマリーキーとしてUUIDを有効化するのは非常に簡単で、設定ファイルを編集するだけです。
# config/application.rb
config.active_record.primary_key = :uuid
また、マイグレーションを使えば、特定のテーブルだけUUIDを適用することもできます。
create_table :users, id: :uuid do |t|
t.string :name
end
これはたった1行の設定ですが、開発開始時に有効化しておくだけで、スケール段階での膨大な手間を省けます。おまけとして、ボットや悪意のある第三者がプライベートURLを推測することも難しくなるというセキュリティ面のメリットもあります。
カウントとカウンターを減らす
一度意識し始めると、カウントやカウンターはアプリのあらゆる場所に存在していることがわかります。メールクライアントは未読メールの件数を表示します。ブログのページネーションは、投稿の総件数をもとにページ数を計算しているのです。
カウンターには、次の2つのスケーリング上の問題があります。
select count(*) from usersのようなデータベースクエリは本質的に遅い。結果を返すために、実際にレコードセット内の全レコードを1件ずつ走査します。100万件のレコードがあれば、それ相応の時間がかかります。- 「カウンターキャッシュ」による高速化は機能こそしますが、書き込みを複数のデータベースサーバーに分散する能力が損なわれます。
最も簡単な解決策は、カウンターの使用を可能な限り避けることです。そしてこれは、初期設計の段階で取り組めば格段に容易になります。
たとえば、件数ではなく日付範囲でページングする方式を選んだり、後で生成コストが莫大になる割にさほど有用でない統計表示は思い切って見送ったりするのも一案です。
データの期限切れ管理とアーカイブ(ウェアハウジング)
搭載されているRAM・CPU・ディスクIOの量を考慮すると、単一のPostgresテーブルに保存できるデータ量には上限があります。つまり、いつか必ず古くなったデータをメインテーブルから退避させる必要が出てきます。
簡単な例を挙げましょう。数ギガバイト規模のデータベースから、1年以上前のレコードを削除したいとします。
この問題に対処した経験がなければ、次のようなコードを書きたくなるかもしれません。
MyRecords.where("created_at < ?", 1.year.ago).destroy
しかし問題は、このクエリの実行に数日、場合によっては数週間かかってしまうことです。データベースが大きすぎるためです。
この問題が特に厄介なのは、気づいたときにはすでに手遅れになっていることが多い点です。会社がまだ若く、データベースのレコードが1,000件程度しかない頃に、データ削除戦略まで考えておく人はほとんどいません。
もし事前に計画できているなら、簡単な解決策があります。テーブルをパーティショニングすればよいのです。すべてのデータを my_records に書き込む代わりに、今週のデータは my_records_1 へ、翌週のデータは my_records_2 へと振り分けて書き込みます。先週分のデータを削除するタイミングが来たら、 DROP TABLE my_records_1 を実行するだけです。DELETE文とは異なり、この操作は瞬時に完了します。
パーティショニングのキーは日付である必要もありません。ユースケースに合うフィールドを選べばOKです。
さらに、pg_partmanというPostgres拡張機能を使えば、細かい処理はすべて自動化され、アプリケーションコードを1行も変更せずにパーティショニングを実現できます。Ruby側でパーティショニングを管理したい方には、partitionableという便利なgemもあります。
おわりに
次回、ゼロから新しいプロジェクトを立ち上げるときは、少し時間を取ってスケーリングについて考えてみてください。過度に悩む必要はありませんし、HAMLにするかERBにするかで何日も費やす必要もありません。ただ、「事前の計画だけで簡単に獲得できる勝ち筋はないだろうか」と自問してみてください。その小さな一手が、将来の大きな差につながります。
-
Google Replyでチャットをもっとスマートに――AIが自動返信する新アプリとは
AI技術は、私たちの生活をより便利にするための新しいアプリケーションを日々生み出し続けています。そんな中、Googleから「Reply」という自動返信アプリが登場しようとしています。このアプリは、メッセージのやり取りを自動化することを目的としており、WhatsApp、Facebook Messenger、Hangouts、Android Messages、TwitterのDMなど、主要なメッセージングアプリに対応しているのが特徴です。 さらに、Gmailアカウントと連携することで、よりパーソナライズされた快適な体験を実現できるようになっています。ここでは、Google Replyの詳細について
-
Amazon Alexaをさらに賢くする5つの簡単な設定方法
10年前、声の命令だけで音楽を購入したり好きな曲を再生したりできるなんて、誰も想像できなかったでしょう。長い年月の中で、テクノロジーは私たちの想像をはるかに超える素晴らしいイノベーションを次々と生み出してきました。2017年は、技術革新や新しいガジェットの面で特筆すべき年となりました。その中でも注目のデバイスが「Amazon Alexa」スマートスピーカーです。しかし、ちょっとした設定を加えるだけで、Alexaをさらに「賢く」できることをご存知でしたか?ご存じない方のために、さっそく見ていきましょう!1. 音声プロファイルを設定するAlexaは音声コマンドを聞き取って動作しますが、あなたの言葉