Cosmemoでのデータベース設計

データの永続化のためには、データベース設計は避けて通れないと思います。個人開発であるCosmemoの開発で考えていることをまとめてみました。

はじめに

ごきげんよう。皆さんは、データベース設計はお好きでしょうか?

正規化、インデックス、情報のライフサイクル・・・考えることが多すぎて、正直わからないことしかありません。
ただ、パズルみたいなので好きか嫌いかでいえば好きです。

そこで、個人開発、特にCosmemoでのデータベース設計時に考えていることをまとめてみました。
考えの根底にある、仕事で経験した障害の話から始めようと思います。

アプリのタイムアウトを直した話

あるとき、ユーザーから「アプリにデータが表示されない」、という問い合わせが来ました。
調べてみると、サーバーとの通信がタイムアウトしている状況でした。

原因を掘り下げたところ、以下のような複合的な問題でした。

それぞれ単体でも十分しんどいですが、全部重なっていました。
レスポンスタイムはよくて数十秒。よく今まで動いていたな、という感じです。

なぜこうなっていたかというと、初期のデータ量では問題が表面化しなかったから、というシンプルな理由です。
ユーザーが増えてデータが積み上がってはじめて、設計のひずみが露呈した形です。

対応と、思ったより難しかった話

頑張って対応したので、データベース周りでどのような対応をしたのかの話をします。

まずは、どのようなクエリが投げられているかの確認です。
pg_stat_statementsがONになっていなかったのでONにし、コードベースからもどのようなクエリが投げられうるかのチェックを行います。

・・・IN句に1000件以上渡すケースがありました。
はじめはデータベースなんてインデックス張れば読み取りは早くなるでしょ、くらいのテンションでした。しかし、IN句が爆発している時点で、結果的にシーケンシャルスキャンが選択されてしまうような状況です。シンプルにインデックスを張って改善!とはいきません。

これはまずい、とか考えつつ、そもそものクエリを改善できないかを検討を行います。
結果的には、機能要件&テーブル設計的にそのクエリを投げざるを得ない状況でした。詰みです。
既存ユーザーへの影響や関係各所への調整との天秤で、何とか現状のまま改善することを選択しました。

ここまで来たら、後は泥臭くindex作成 → EXPLAINで改善確認、のループです。
B-treeだけではなく、AIに進められたGiSTを使ってみたり(そもそも用途が違いました・・・)、カバリングインデックスや、逆にカラムを絞ったインデックスを作成しながら試行錯誤しました。

最終的には、インデックスの見直し・クエリの(多少の)改善に加えて、そのテーブルをread replicaに分離し負荷分散することで、ある程度の速度で表示できるようになりました。
要件的に根本解決はできていませんが、ひとまず使えるレベルには戻せた、という感じです。

この経験から、投げられる可能性のあるクエリの意識をする、EXPLAINを見てボトルネックを特定してから手を動かす、という習慣がつきました。
インデックスは「貼れば速くなる」ではなく、カーディナリティやクエリのパターンを考えないと逆効果になることも身をもって知りました。

個人開発への教訓

じゃあCosmemoではどうしているか、という話です。

正直なところ、個人開発のスケールでは最初からガチガチに設計する必要はないと思っています。
1000万規模のレコードを持つテーブルを想定するのはYAGNIですし、インデックスも必要に迫られてから貼っても遅くはないです。

ただ、知らずに設計することと割り切った設計は違う、とは思っています。
「後でインデックス貼ればいい」と「そもそも知らない」では、問題が起きたときの対応速度が全然違いますし、そもそもの要件とテーブル設計のバランスをとることも可能です。

その上で、Cosmemoで意識していることは、大きく2つです。

クエリが破綻するような機能は作らない、作るとしても制限を設ける
サーバーのスペックなど性能を上げる、レプリケーションするなどコストをかけない限り、データベースはスケールには制約が多い印象です。
そのため、要件の時点でフルスキャンが確定するような機能は、仕様を変えるか、件数制限をかけます。アプリ側で頑張るより、そもそもの問い合わせを絞る方が健全です。

迷ったときはデータのライフサイクルを考える
データの更新頻度や保持期間で、最適な構造が変わります。
INSERT ONLYにするのか、後からUPDATEが走るのか、どれくらいの頻度で読み取られるデータなのか・・・
そういった、「このデータはいつ生まれて、どう使われて、いつ消えるか」を整理すると、テーブル設計の方針が見えてくることが多いです。過度な正規化も、ここを考えずに進めると後で詰みます。

まとめ

データが取得できないという問題をきっかけに、設計への向き合い方がだいぶ変わりました。
最初から完璧にやろうとしなくていいですが、問題が起きたときに「なぜ遅いのか」を追えるだけの知識は持っておきたいな、と思っています。

それでは。

ブログ一覧へ戻る