63 ポイント 投稿者 GN⁺ 2025-04-01 | 4件のコメント | WhatsAppで共有
  • Postgres をより生産的かつ安全に使うのに役立つ実践的なパターンをまとめた記事
  • それぞれのパターンは小さいものの、積み重なると大きな違いを生む

UUID主キーを使う

  • UUID はランダムであるため、ソートやインデックス性能の面で不利な点がある
  • 数値 ID よりも多くの領域を消費する
  • ただし、次のような利点がある
    • DB に接続しなくても UUID を生成できる
    • 外部に安全に公開できる
  • gen_random_uuid() を使って主キーとして UUID を自動生成できる

created_at と updated_at フィールドは常に追加する

  • デバッグ時には、レコードの作成時点と変更時点が分かることが非常に有用
  • updated_at はトリガーによって自動更新されるように設定できる
  • 関数は一度だけ作成し、トリガーは各テーブルに適用する必要がある

外部キーには on update/delete restrict を設定する

  • 外部キー制約を設定する際は、on update restrict on delete restrict を必ず使うべき
  • データ削除時に誤って連鎖削除が発生するのを防ぐ
  • ストレージは安価だが、データ復旧は非常に難しいため、保守的に扱うのがよい

スキーマの利用を推奨

  • デフォルトスキーマは public だが、アプリケーションが大きくなったら別スキーマに分けるのがよい
  • スキーマは名前空間のように機能し、異なるスキーマ間でも JOIN できる
  • テーブル数が増えるほど、スキーマを活用する方が可読性と保守性に優れる

Enumテーブルパターンを使う

  • PostgreSQL の enum 型や check constraint の代わりに、enum テーブルを使う方が柔軟
  • enum 値を別テーブルで管理すれば、メタデータを追加したり、enum 値を簡単に拡張したりできる
  • 外部キーで enum テーブルの値を参照して制約を維持する

テーブル名は単数形にする

  • テーブル名は複数形ではなく単数形にするのが望ましい
  • クエリを書く際に単数形の方が明確で、複数形は所有格や意味上の混乱を招くことがある

JOINテーブルは機械的に命名する

  • 多対多関係のための JOIN テーブルは、2つのテーブル名をつなげて命名するのが安全で明確
  • 例: person_pet
  • 組み合わせに対するユニークインデックスを追加して重複を防ぐ

削除の代わりに soft delete を使う

  • データを実際に削除するより、削除時点を示す revoked_at のような timestamp フィールドを使う方がよい
  • 削除されたかどうかだけでなく、いつ削除されたかも追跡できる
  • Boolean 値より timestamp の方が多くの情報を提供する

ステータスはログテーブルで表現する

  • 単一カラムでステータスを表す代わりに、ステータス変更履歴を別テーブルに保存する
  • ステータス発生時点は valid_at カラムで明示する
  • 最新ステータスを素早く取得できるよう、latest フラグとユニークインデックス + トリガーを設定する
  • これは非同期イベント処理や順序が前後し得る状況で有利

特別な行には system_id を追加する

  • enum テーブル以外でも、特定の「システム行」が必要な場合がある
  • system_id テキストフィールドを nullable で追加し、ユニークインデックスを設定する
  • system_id によって特定の行を明確に取得できる

ビュー(View)は最小限に使う

  • ビューは複雑なクエリを抽象化するのに便利だが、保守が難しい
    • カラム削除時にビューの再作成が必要
    • ビューの上にビューを作ると、性能や可読性の問題が生じる
  • 必要な分だけ慎重に使うこと

JSONクエリを積極的に活用する

  • Postgres は JSON の保存だけでなく、JSON を返すクエリも非常に強力
  • ネストした関係を1回のクエリで JSON 形式として返せる
  • N+1 問題なしに必要なすべてのデータを一度に取得できる
  • 欠点: 型情報が失われること、全データを一度にメモリへ読み込む必要があること
  • それでも性能面や構造面での利点の方が大きい

4件のコメント

 
jhj0517 2025-04-01

> 中間テーブルの名前は機械的に付ける

名前を付けるときにこういうルールがあること自体が良いと思います〜

 
halfenif 2025-04-01

UUID7を考慮すれば、時系列でのソートは可能なのではないでしょうか?

 
winterjung 2025-04-01
 
t7vonn 2025-04-01

soft delete 時に timestamp を入れる方法、いいですね。 基本キーに UUID を入れると時系列順に並べられないので、snowflake id や ulid を使うのもよさそうです。この場合は各サーバーが sequence number を持っている必要はありますが。