Yakstは、海外の役立つブログ記事などを人力で翻訳して公開するプロジェクトです。
9ヶ月前投稿

初心者がデータベースを扱うときにやってしまいがちな5つのミス

主にPostgreSQLで、アプリケーションがスケールしていくことを考慮に入れずに後で困ることになりがちな設計のポイント。今後巨大にスケールする必要が分かりきっている際には特に注意すべき点。
原文 Five mistakes beginners make when working with databases Craig Kerstiens (English)
翻訳者 D98ee74ffe0fafbdc83b23907dda3665 doublemarket


開発者として仕事を始める時には、参ってしまうほど覚えなくてはいけないことがあります。まず最初に言語自体、それから使っているフレームワーク特有のクセ、さらにその後(あるいはその前)にフロントエンド開発を織り交ぜ、そしてその先でデータをどこに保存するかを決めなくてはなりません。

最初の頃は、素早く身につけるべきことが多すぎて、アプリケーションのデザインにおいてデータベースは後から付け足すものになりがちです(おそらくこれはエンドユーザーエクスペリエンスに影響がないからでしょう)。その結果、データベースが動き始めてから直さなくてはならない数々の悪い習慣が存在しています。ここでは、そのうちのいくつかについて概要を書いてみます。

画像を保存してしまう

画像はデータベースに置くべきではありません。そうできるからといって、そうすべきだということにはなりません。画像はデータベース上で非常に大きな容量を必要としますし、データベースのIOリソースを必要以上に食いつぶしてアプリケーションを遅くしてしまいます。この手の間違いの一番多いのは、開発者が画像をbase64エンコードして、それをデータベースに巨大なtextあるいはblobとして保存してしまうという流れです。

よりよい方法としては、画像をAmazon S3のようなサービスに直接アップロードしてしまい、(画像をホストしているAmazonの)画像のURLをデータベースにtextとして保存することです。これで、画像をロードする必要があるたびに、<img>タグに画像のURLを単純にアウトプットするだけでよくなります。この方法はウェブサイトのレスポンスを大きく改善し、ウェブアプリケーションのスケールに対しても役立つでしょう。

LIMIT / OFFSETを使ってしまう

ページネーションは多くのアプリケーションで使われており広く知られています。SQLを学び始めるてすぐだと、ページネーションを実装する一番分かりやすい方法は、どれかの列でORDER BYしてからその結果をLIMITし、たくさんのレコードをページごとにOFFSETすることになります。これは論理的に完全に正しそうに見えますが、そこそこのスケールに到達してくるとそう言えなくなってきます。

  1. この仕組みをデータベース上で動かす負荷が耐えられなくなる。
  2. 決定論的でなく、ユーザーがページ間を移動しているうちにレコードが変わる可能性がある。

残念なところは、ページネーションはかなり複雑で、しかもどこにでも適用できる解決法はありません。ページネーションの問題を解決するためのさらなる情報を知るには、たくさんのオプションを調べる必要があります。

プライマリーキーを整数にしてしまう

プライマリーキーを作る時、ほとんどのORMのデフォルトは、serial型を作ることです。これは自動でインクリメントされるシーケンスで、それをプライマリーキーとして使うことになります。管理者にとって、/users/1/users/2と見ていけることからもこれは分かりやすく思えます。また、多くのアプリケーションにとってもこれは問題ありません。しかし、スケールしていくに従って、整数型のプライマリーキーが使い果たされることがあり、これは巨大なスケールに限った話ではありません。さらに、キーを生成するのに一つのシステムに依存することになります。もしその規模にスケールすることになる時がこれば、その辛さは非常に大きくなります。よりよいアプローチとしては、初めからUUIDの利点を利用することです。

うっかりキーが直接参照することでどのくらいのユーザー数やページ数などがあるのかを見せてしまうことがなくなるという利点もあります。

新しい列に付けるデフォルト値の間違い

どんなに長くスキーマを扱ってきても、開発初日から完璧なスキーマは作れないでしょう。データベースのスキーマは、継続的に更新されていくドキュメントと考えるべきです。幸いなことにデータベースに列を追加するのは簡単です。しかし、間違った方法でやってしまうのもまた簡単です。デフォルトでは、単に列を追加するとNULL値を許容するようになります。この操作は高速ですが、多くのアプリケーションではデータにはNULL値は含まず、デフォルト値が入って欲しいのが本当のところでしょう。

デフォルト値がある列をテーブルに追加すると、テーブルの全データの書き換えが始まります。これは、大きなテーブルでは非常にまずいことであるのに注意しましょう。そのため、代わりに初めはNULLを許容するように作ってすぐ処理が終わるようにし、それからデフォルト値を設定し、バックグラウンドプロセスを走らせて過去に遡ってデータを更新しましょう。

これは考えるよりも複雑ですが、便利なガイドがあるので参考に。

正規化のやりすぎ

正規化を学び始めると、それはやった方がいい行為な気がするでしょう。著者情報authorsを含むpostsテーブルを作り、各記事はカテゴリーに属しているとします。そうすると、あなたはcategoriesテーブルを作り、それからpost_categoriesという結合テーブルを作るでしょう。これはデータを正規化するという観点からは基本的に何も悪いことはないのですが、ある時点に至ると、利点が小さくなります。

この例では、カテゴリーは各記事の列に非常に簡単にvarcharの配列として入れることができます。正規化は非常に意味のあることですが、多対多のテーブルを作る時には常に、全ての情報を持つひとつのテーブルを使うという2つ目の考え方が必要かどうかを考える必要があります。

追記 : 正規化の不足も同じく問題になることもここで言及しておくべきでしょう。何にでもうまくいく方法というのはありません。一般的には、完全に非正規化した方がいい時と、完全に正規化した方がいい時とがあります。@fuzzychefの言ったことが参考になります。「適度な正規化を行おう。3びきのくま(Goldilocks)の原理のように。」 (訳注 : 3びきのくまを参照)

まとめ

ここに書いたことについてTwitterで聞いた時には、非常にいい答えを受け取りました。しかし、それはバラバラに散らばった情報でした。ORMが生成したクエリを見ないという基本的なところから、分離レベルのように高度なことまでありました。実世界のアプリケーションを作る人なら誰しもに価値があると思われるのに出てこなかったのは、インデックスについてです。どのようにインデックスがはたらくのかや、どんなインデックスが必要なのかを理解することは、データベースで良いパフォーマンスを得るには重要な点です。インデックスの基本的なことを教えてくれる記事はたくさんありますし、PostgreSQLにおいてパフォーマンスを計測する実践的な方法が分かる記事もあります。

一般的には、データベースを必要悪ではなく中心に据えたツールとして扱うべきだと思ってそう伝えています。ここに挙げたヒントが少なくとも初心者が最初に間違いをやらかしてしまうのを防ぐことを願っています。

PyConでこの記事のきっかけとなった最初の会話をした@mdeggies@rdeggesにありがとう。

次の記事
git diffコマンドで比較する時のダブルドット(..)とトリプルドット(...)の違いとは?
前の記事
なぜAtomはVimを置き換えられないのか

Feed small 記事フィード