Web エンジニアが
PostgreSQL を選ぶ 3 つの理由
PostgreSQLカンファレンス 2014
What is it?
データベースは何を基準に選んでますか?
What is it?
アプリケーションにとって
データの寿命はコードより長い
という事実
What is it?
なぜPostgreSQLを使うのか
あじぇんだ
1 自己紹介
2 ランキングを作る
3 可変なプロパティを扱う
4 型を極める
5 まとめ
あじぇんだ
1 自己紹介
2 ランキングを作る
3 可変なプロパティを扱う
4 型を極める
5 まとめ
自己紹介
名前:曽根 壮大(そね たけとも)
年齢:30歳(三人の子供がいます)
職業:Webエンジニア
所属:日本PostgreSQLユーザ会
   中国支部 支部長
  技術的にはLL系言語とかRDBが好きです
中国地方DB勉強会
https://dbstudychugoku.github.io/
あじぇんだ
1 自己紹介
2 ランキングを作る
3 可変なプロパティを扱う
4 型を極める
5 まとめ
ランキングを作る
仕様変更に強いランキングを作る
ランキングを作る
Viewに紐づくデータは
仕様変更
の影響を受けやすい
ランキングを作る
ランキング
ランキングを作る
ランキング
↓
要件が多様なので影響を受けやすい
名前 戦闘力
フリーザ 530000
悟飯(幼少期) 1307
クリリン(ラディッツ戦) 206
ヤムチャ(ラディッツ戦) 177
ランキングを作る
要件
ランキングを作る
要件
1 戦闘力の降順(DESC)
ランキングを作る
要件
1 戦闘力の降順(DESC)
2 表示は名前と戦闘力
実際のSQL
SELECT
名前,戦闘力
FROM キャラクター
ORDER BY
戦闘力
DESC
ランキングを作る
要件
1 戦闘力の降順(DESC)
2 表示は名前と戦闘力
3 上位三名を表示
実際のSQL
SELECT
名前,戦闘力
FROM キャラクター
ORDER BY
戦闘力
DESC
LIMIT 3
ランキングを作る
要件
1 戦闘力の降順(DESC)
2 表示は名前と戦闘力
3 上位三名を表示
4 編で分ける
名前 戦闘力 編
フリーザ 530000 フリーザ編
悟飯(幼少期) 1307 ラディッツ編
クリリン 206 ラディッツ編
ヤムチャ 177 ラディッツ編
農夫 5 ラディッツ編
ギニュー 120000 フリーザ編
クリリン 1500 フリーザ編
亀仙人 139 ラディッツ編
※実務では編は正規化するべき
実際のSQL
SELECT
名前,戦闘力
FROM キャラクター
WHERE 編 = ‘フリーザ編’ または 編 = ‘ラディッツ編’
ORDER BY
戦闘力
DESC
LIMIT 3
ランキングを作る
クライアント「それじゃない。」
名前 戦闘力 編
悟飯(幼少期) 1307 ラディッツ編
クリリン 206 ラディッツ編
ヤムチャ 177 ラディッツ編
亀仙人 139 ラディッツ編
農夫 5 ラディッツ編
フリーザ 530000 フリーザ編
ギニュー 120000 フリーザ編
クリリン 1500 フリーザ編
ランキングを作る
SQLを二回投げるか?
ランキングを作る
ウィンドウ関数
ランキングを作る
ウィンドウ関数
ウィンドウ関数は現在の行に何らの
関係するテーブル行の一纏まり全般
の計算を行う。
実際のSQL
SELECT
rank() OVER (
PARTITION BY "編"
ORDER BY "戦闘力" DESC
)
, *
FROM "キャラクター";
名前 戦闘力 編
悟飯(幼少期) 1307 ラディッツ編
クリリン 206 ラディッツ編
ヤムチャ 177 ラディッツ編
亀仙人 139 ラディッツ編
農夫 5 ラディッツ編
フリーザ 530000 フリーザ編
ギニュー 120000 フリーザ編
クリリン 1500 フリーザ編
ランキングを作る
クライアント
「かつキャラの最大戦闘力で並べて」
名前 戦闘力 編
フリーザ 530000 フリーザ編
フリーザ 10000000 フリーザ編
フリーザ 20000000 フリーザ編
悟飯(幼少期) 1307 ラディッツ編
クリリン 206 ラディッツ編
ヤムチャ 177 ラディッツ編
農夫 5 ラディッツ編
ギニュー 120000 フリーザ編
クリリン 1500 フリーザ編
クリリン 0 フリーザ編
クリリン 10000 フリーザ編
亀仙人 139 ラディッツ編
名前 戦闘力 編
フリーザ 2000000 フリーザ編
ギニュー 120000 フリーザ編
クリリン 10000 フリーザ編
悟飯(幼少期) 1307 ラディッツ編
クリリン 206 ラディッツ編
ヤムチャ 177 ラディッツ編
亀仙人 139 ラディッツ編
農夫 5 ラディッツ編
実際のSQL
SELECT
rank() OVER (
PARTITION BY "編"
ORDER BY max("戦闘力") DESC
) , "名前", MAX("戦闘力"), "編"
FROM "キャラクター"
GROUP BY "名前","編";
名前 戦闘力 編
フリーザ 2000000 フリーザ編
ギニュー 120000 フリーザ編
クリリン 1500 フリーザ編
悟飯(幼少期) 1307 ラディッツ編
クリリン 206 ラディッツ編
ヤムチャ 177 ラディッツ編
亀仙人 139 ラディッツ編
農夫 5 ラディッツ編
ランキングを作る
仕様変更に強いランキングを作る
ランキングを作る
開発者
「毎回SQLの差し替えするの辛い」
ランキングを作る
Viewを使う
ランキングを作る
街角の声
「Viewを使うと遅いのでは?」
ランキングを作る
View
ランキングを作る
View
• INDEXは効く
ランキングを作る
View
• INDEXは効く
• 参照の際にSQLを実行するだけ
ランキングを作る
View
• INDEXは効く
• 参照の際にSQLを実行するだけ
• 元のSQLが遅い場合は当然遅い
ランキングを作る
参照元のテーブルが大きくなった
ランキングを作る
参照元のテーブルが大きくなった
↓
参照元のSQLが遅い
ランキングを作る
マテリアライズドビュー
ランキングを作る
マテリアライズドビュー
実体の存在するView。
参照したクエリ結果を保存するため、
参照元を更新した際はマテビューの
更新も必要になる。
※ただしPostgreSQL 9.3からの機能
ランキングを作る
クエリ結果を実体化する
ランキングを作る
クエリ結果を実体化する
↓
高速化
ランキングを作る
マテビューは銀の弾丸ではない
ランキングを作る
マテビューの問題点
・リフレッシュ管理が必要(自動更新しない)
※ただし、9.4から自動更新が可能
・普通のテーブル同様に表領域を消費する
・リフレッシュはそれなりにリソースを使う
ランキングを作る
更新が多いとボトルネックになる
ランキングを作る
まとめ
ランキングを作る
まとめ
1 データをシンプルに保つ
ランキングを作る
まとめ
1 データをシンプルに保つ
2 コード側の実装に依存しない
ランキングを作る
まとめ
1 データをシンプルに保つ
2 コード側の実装に依存しない
3 要件に合わせて選択肢を選ぶ
あじぇんだ
1 自己紹介
2 ランキングを作る
3 可変なプロパティを扱う
4 型を極める
5 まとめ
可変なプロパティを扱う
可変なプロパティを扱う
• アンケートフォーム
可変なプロパティを扱う
• アンケートフォーム
• ユーザの付属情報
可変なプロパティを扱う
• アンケートフォーム
• ユーザの付属情報
• ブログのタブ
可変なプロパティを扱う
• アンケートフォーム
• ユーザの付属情報
• ブログのタブ
などなど…
可変なプロパティを扱う
アンケートフォーム
可変なプロパティを扱う
アンケートフォーム
回答者 キャラクター 回答日
そーだい 榛名 2014/11/28
たけとも 高雄 2014/11/28
soudai1025 大和 2014/11/29
可変なプロパティを扱う
アンケートフォーム
ここに「択一回答」があるじゃろ?
    ( ^ω^)←お客様
択一回答
可変なプロパティを扱う
アンケートフォーム
これを
    ( ^ω^)←お客様
)択一回答(
可変なプロパティを扱う
アンケートフォーム
こうして…
    ( ^ω^)←お客様
可変なプロパティを扱う
アンケートフォーム
こうじゃ!
    ( ^ω^)←お客様
複数回答
可変なプロパティを扱う
アンケートフォーム
可変なプロパティを扱う
アンケートフォーム
テキストフォームまで
こっそり追加される
可変なプロパティを扱う
どのように対応するか
可変なプロパティを扱う
データについて
可変なプロパティを扱う
データについて
• データを消せない
可変なプロパティを扱う
データについて
• データを消せない
• データを変更できない
可変なプロパティを扱う
データについて
• データを消せない
• データを変更できない
• データの追加で対応
可変なプロパティを扱う
SQLアンチパターン
↓
カンマ区切り(CSV)で保存
※ジェイ・ウォーク
回答者 キャラクター 回答日
そーだい 榛名 2014/11/28
たけとも 高雄,榛名 2014/11/28
soudai1025 大和,金剛,武蔵 2014/11/29
回答者 キャラクター 回答日
そーだい 榛名 2014/11/28
たけとも 高雄,榛名 2014/11/28
soudai1025 大和,金剛,武蔵 2014/11/29
保存するデータが
カラムのサイズに依存する
可変なプロパティを扱う
SQLアンチパターン
可変なプロパティを扱う
SQLアンチパターン
• 検索が難しい
可変なプロパティを扱う
SQLアンチパターン
• 検索が難しい
• 集計が難しい
可変なプロパティを扱う
SQLアンチパターン
• 検索が難しい
• 集計が難しい
• 更新が難しい
可変なプロパティを扱う
SQLアンチパターン
↓
データの数だけカラムを増やす
※メタデータトリブン
回答者 キャラ1 キャラ2 キャラ3 回答日
そーだい 榛名 NULL NULL 2014/11/28
たけとも 高雄 榛名 NULL 2014/11/28
soudai1025 大和 金剛 武蔵 2014/11/29
可変なプロパティを扱う
SQLアンチパターン
可変なプロパティを扱う
SQLアンチパターン
• 項目追加の度にカラムが増える
可変なプロパティを扱う
SQLアンチパターン
• 項目追加の度にカラムが増える
• データの可読性が下がる
可変なプロパティを扱う
SQLアンチパターン
• 項目追加の度にカラムが増える
• データの可読性が下がる
• データの整合性を担保が難しい
可変なプロパティを扱う
正規化
回答者 回答日
そーだい 2014/11/28
たけとも 2014/11/28
soudai1025 2014/11/29
回答者 キャラクター
そーだい 榛名
たけとも 高雄
たけとも 榛名
soudai1025 大和
soudai1025 金剛
soudai1025 武蔵
可変なプロパティを扱う
最初から正規化すれば両対応
可変なプロパティを扱う
集合でデータを表現する
可変なプロパティを扱う
変更に強くなる
可変なプロパティを扱う
PostgreSQLのアプローチ
可変なプロパティを扱う
PostgreSQLのアプローチ
↓
配列型
回答者 キャラクター 回答日
そーだい {榛名} 2014/11/28
たけとも {高雄,榛名} 2014/11/28
soudai1025 {大和,金剛,武蔵} 2014/11/29
可変なプロパティを扱う
配列型
可変なプロパティを扱う
配列型
• INDEXが効く
可変なプロパティを扱う
配列型
• INDEXが効く
• 柔軟な検索(内包なども可能)
可変なプロパティを扱う
配列型
• INDEXが効く
• 柔軟な検索(内包なども可能)
• 任意の箇所の更新も可能
可変なプロパティを扱う
配列型の注意点
• 外部制約が使えない
• ORMが多くの場合使えない
可変なプロパティを扱う
配列型のその他の使い方
• タグなどの複数の値を持たせる
• 木構造を表現する
可変なプロパティを扱う
配列型まとめ
可変なプロパティを扱う
配列型まとめ
• 外部制約の不要な場合に使う
可変なプロパティを扱う
配列型まとめ
• 外部制約の不要な場合に使う
• ORMに依存しない場合に使う
可変なプロパティを扱う
配列型まとめ
• 外部制約の不要な場合に使う
• ORMに依存しない場合に使う
• 最初に正規化を検討する
可変なプロパティを扱う
もっと柔軟に対応したい
可変なプロパティを扱う
もっと柔軟に対応したい
• ドキュメント志向
可変なプロパティを扱う
もっと柔軟に対応したい
• ドキュメント志向
• スキーマレス
可変なプロパティを扱う
もっと柔軟に対応したい
• ドキュメント志向
• スキーマレス
• Key=>Valueな関係性を保存
可変なプロパティを扱う
JSON型
可変なプロパティを扱う
JSON型
• JSON本体をカラムに保存
可変なプロパティを扱う
JSON型
• JSON本体をカラムに保存
• 高速な参照(INDEXが効く)
可変なプロパティを扱う
JSON型
• JSON本体をカラムに保存
• 高速な参照(INDEXが効く)
• 各種変換の関数を用意
回答者 JSON 回答日
そーだい {キャラクタ:[榛名]} 2014/11/28
たけとも {キャラクタ:[高雄,榛名]} 2014/11/28
soudai1025 {キャラクタ:[大和,榛名,武蔵]} 2014/11/29
回答者 JSON 回答日
そーだい {キャラクタ:[榛名],Lv:40} 2014/11/28
たけとも {キャラクタ:[高雄,榛名]} 2014/11/28
soudai1025 {キャラクタ:[大和,榛名,武蔵]} 2014/11/29
可変なプロパティを扱う
JSON型
可変なプロパティを扱う
JSON型
• 柔軟にデータを保存できる
可変なプロパティを扱う
JSON型
• 柔軟にデータを保存できる
• View変更によるDB変更が不要
可変なプロパティを扱う
JSON型
• 柔軟にデータを保存できる
• View変更によるDB変更が不要
• 9.4からはより強力なJSONB型
可変なプロパティを扱う
JSON型の注意点
• 外部制約の不要な場合に使う
• ORMに依存しない場合に使う
• 問題点は配列型と同様
あじぇんだ
1 自己紹介
2 ランキングを作る
3 可変なプロパティを扱う
4 型を極める
5 まとめ
型を極める
豊富な型の例
• 列挙(enum)型
• ネットワーク・アドレス型
• 範囲型
• 幾何データ型
• 列挙(enum)型
• ネットワーク・アドレス型
• 範囲型
• 幾何データ型
型を極める
型を選ぶ利点
型を極める
型を選ぶ利点
• 正しいデータのみが保存される
型を極める
型を選ぶ利点
• 正しいデータのみが保存される
• 正しいソートが行われる
型を極める
型を選ぶ利点
• 正しいデータのみが保存される
• 正しいソートが行われる
• 適切な検索が行える
型を極める
ネットワークアドレス型
• 列挙(enum)型
• ネットワーク・アドレス型
• 範囲型
• 幾何データ型
• IPv4もIPv6も対応
• サブネットマスクの整合性
• 文字列ではなくIPとしてソート
IP
192.1.1.1/32
192.2.1.1/32
192.10.1.1/32
型を極める
幾何データ型
• 列挙(enum)型
• ネットワーク・アドレス型
• 範囲型
• 幾何データ型
• point、boxなど豊富な型
• 充実した関数と演算子
• 地図の範囲検索など
店名 緯度 経度
品川AP 35.630793 139.73786
品川駅 35.630152 139.74044
五反田駅 35.626446 139.723444
実際のSQL
SELECT
sqrt(power((対象緯度-自分の緯度)*111,2)
 +
power((対象経度-自分の経度)* 91,2))
 AS distance
平均で緯度1度あたり111km
平均で経度1度あたり91km
検索例
半径●●メートルの中の登録店を調べる
自分を中心とした円に
含まれているか
実際のSQL
SELECT
店名,
sqrt(power((お店.緯度 - 自分の緯度) * 111, 2)
+ power((お店.経度 - 自分の経度) * 91, 2)) AS 距離(km)
FROM お店
WHERE
circle(point(お店.緯度*91.0,お店.経度*111.0), 円の半径)
@ circle(point(自分の緯度*91.0,自分の経度*111.0), 円の半径)
店名 距離(km)
品川AP 0.01
品川駅 0.4
型を極める
まとめ
• 列挙(enum)型
• ネットワーク・アドレス型
• 範囲型
• 幾何データ型
•
型を極める
まとめ
• 列挙(enum)型
• ネットワーク・アドレス型
• 範囲型
• 幾何データ型
• 適切な型にデータを入れる
型を極める
まとめ
• 列挙(enum)型
• ネットワーク・アドレス型
• 範囲型
• 幾何データ型
• 適切な型にデータを入れる
• 型を使い不正なデータを無くす
型を極める
まとめ
• 列挙(enum)型
• ネットワーク・アドレス型
• 範囲型
• 幾何データ型
• 適切な型にデータを入れる
• 型を使い不正なデータを無くす
• 特別な検索も可能になる
あじぇんだ
1 自己紹介
2 ランキングを作る
3 可変なプロパティを扱う
4 型を極める
5 まとめ
まとめ
まとめ
Webは日々複雑になっている
まとめ
Webは日々複雑になっている
↓
取り扱うデータも増えている
まとめ
運用が始まるとデータは変えれない
まとめ
運用が始まるとデータは変えれない
↓
どんなにコードが綺麗でもデータ構造
がダメだとリファクタリングは難しい
まとめ
SQLや型を使ってデータを守る
まとめ
SQLや型を使ってデータを守る
↓
運用をシンプルにする
まとめ
データの寿命はコードより長い
ご静聴ありがとうございました。

Web エンジニアが postgre sql を選ぶ 3 つの理由