なぜ「とりあえずnumeric」がデータベースを殺すのか —— PostgreSQLの数値型を再考する
PostgreSQLの設計レビューをしていると、必ずと言っていいほど直面する光景がある。テーブル定義を見ると、金額も、IDも、ただのフラグのような値まで、すべて一律に `NUMERIC` 型で定義されている……。
「精度が落ちるのが怖いから」
その気持ちは痛いほどわかる。しかし、データベースエンジニアとしてあえて言おう。その「安全策」が、数年後のあなたのシステムの首を絞めることになる。今日は、PostgreSQLにおける数値型の特性を、内部構造とパフォーマンスの観点から解剖していこう。
—
1. 整数型:至高のパフォーマンス
まずは基本の `smallint`, `integer`, `bigint` だ。これらはCPUのネイティブな演算命令をそのまま利用できる。
- smallint (2 bytes)
- integer (4 bytes)
- bigint (8 bytes)
これらは固定長であり、アライメントや比較処理が極めて高速だ。特に `bigint` は、現代のシステムであれば主キー(PK)としてまず第一に検討すべき型だ。
注意点: 意外と忘れがちなのが、`integer` が取り扱う範囲だ。約21億(2,147,483,647)は、ユーザー数やトランザクション数が増えてくると、あっという間に枯渇する。ある日突然、シーケンスが上限に達してシステムが停止する悪夢を見たくないなら、迷わず `bigint` を選んでおこう。ストレージ容量の差など、現代のサーバー環境では誤差に過ぎない。
—
2. NUMERIC型:便利さと引き換えにする「代償」
多くの開発者が愛用する `NUMERIC`(または `DECIMAL`)は、金融計算には欠かせない。だが、これには強力な代償が伴う。
PostgreSQL内部において、`NUMERIC` は固定長の型ではない。可変長の構造体として実装されており、数値の精度に応じてメモリを消費し、計算はCPU命令ではなくライブラリレベルでのソフトウェア処理になる。
- 演算コスト: 整数型と比較して、計算負荷は桁違いに重い。数百万行のテーブルで `NUMERIC` を使った複雑な集計クエリを投げるのは、エンジンに重い砂袋を背負わせて走らせるようなものだ。
- インデックスの肥大化: `NUMERIC` をB-treeインデックスのキーにすると、当然ながらインデックスサイズが大きくなる。これはキャッシュヒット率を下げ、I/O負荷を直接的に増大させる。
もし、通貨計算で「小数点以下が必要ない」のであれば、「最小単位を整数で持つ」というアプローチを強く推奨する。例えば、円を扱うなら `integer` で「円」単位で保持し、ドルセントなら `integer` で「セント」単位で保持する。これだけで、クエリのレスポンスは劇的に改善するはずだ。
—
3. 浮動小数点型:扱い方を間違えると「死」を招く
`real` (4 bytes) と `double precision` (8 bytes) は、IEEE 754 規格に基づいている。これらは科学技術計算には最適だが、ビジネスロジックには絶対に使ってはいけない。
なぜなら、浮動小数点演算は「近似値」だからだ。
SELECT 0.1 + 0.2 = 0.3; — 結果は FALSE になることがある
この「わずかな誤差」が、会計システムでどれほどの惨劇を引き起こすか想像してみてほしい。これらは、あくまで統計データや、GPS座標、センサーログなど「多少の誤差が許容され、かつ巨大な計算量をこなす必要がある」領域のために存在している。
—
現場で役立つ「型選択」の勘所
設計時に迷ったら、以下の優先順位で自問自答してみてほしい。
1. それは「数」か、「識別子」か?
- 識別子なら、迷わず `bigint`。UUIDをPKにするのもいいが、インデックスの断片化と戦う覚悟が必要だ。
2. それは「計算」が必要か?
- 計算が必要なら、精度はいくら必要か?
- 固定小数点数で足りるなら、 `NUMERIC` を避け、整数にスケールを乗じて計算するロジックをアプリケーション側で実装する検討を。
3. それは「集計」がメインか?
- もし大量のデータに対する統計処理がメインなら、`double precision` も選択肢に入る。ただし、アプリケーション側のドキュメントに「浮動小数点特有の丸め誤差」について明記しておくこと。
—
最後に:データベースは「正直」である
データベースのパフォーマンス問題の多くは、実はアプリケーションのコードではなく、こうした「初期の型選択」に起因していることが多い。
「とりあえず」で選んだ `NUMERIC` が、数年後にクエリの実行計画を歪め、インデックスを肥大化させ、最終的にCPUを食いつぶす。データベースエンジニアの仕事は、そうした「未来の負債」を、設計の段階で一つずつ摘み取っていくことだ。
型を正しく選ぶことは、単なる最適化ではない。それは、あなたが構築するシステムに対する「敬意」の表れでもある。
さあ、次はあなたのテーブル定義を見直す番だ。不要な `NUMERIC` はないだろうか? `integer` で十分な場所に `bigint` を使いすぎて、メモリを無駄にしていないだろうか?
エンジニアリングの深淵は、こうした地味な積み重ねの先にある。
コメント