PostgreSQLアンチパターン

Preview:

Citation preview

PostgreSQLアンチパターン

What is it?

RDBの限界を感じた事ありませんか?

What is it?

PostgreSQLは銀の弾丸ではありません

What is it?

アンチパターン

What is it?

アンチパターン ↓

良かれと思ってした事が後々に苦しみを生む

What is it?

アンチパターンを知る

What is it?

アンチパターンを知る ↓

同じ過ちを繰り返さない

What is it?

愚者は経験に学び、賢者は歴史に学ぶ

      オットー・フォン・ビスマルク

What is it?

What is it?

SQLアンチパターンは厳選された失敗集

What is it?

SQLアンチパターンは厳選された失敗集

↓ DBのノウハウが詰まった名著

What is it?

しかしMySQLがベース

What is it?

今日はPostgreSQLの失敗例をご紹介します

What is it?

SQLアンチパターン本の内容

とは全く関係ありません

What is it?

俺の屍を越えて行け

あじぇんだ

1 自己紹介 2 とりあえず削除フラグ 3 困ったときの配列型とJSON型 4 マテビューの功罪 5 まとめ

あじぇんだ

1 自己紹介 2 とりあえず削除フラグ 3 困ったときの配列型とJSON型 4 マテビューの功罪 5 まとめ

自己紹介名前:曽根 壮大(そね たけとも) 年齢:31歳(三人の子供がいます) 職業:Webエンジニア

所属:日本PostgreSQLユーザ会

   中国支部 支部長

  技術的にはLL系言語とかRDBが好きです

中国地方DB勉強会

https://dbstudychugoku.github.io/

あじぇんだ

1 自己紹介 2 とりあえず削除フラグ 3 困ったときの配列型とJSON型 4 マテビューの功罪 5 まとめ

とりあえず削除フラグ

http://www.slideshare.net/t_wada/ronsakucasual

とりあえず削除フラグ

こんなカラム名を

作ったことありませんか?

とりあえず削除フラグ

hoge_delete_flag

とりあえず削除フラグ

会員削除フラグ… 顧客削除フラグ…

hogeデータ削除フラグ…

とりあえず削除フラグ

論理削除と言う名の闇

とりあえず削除フラグ

とりあえず削除フラグ

論理削除はなぜダメなのか?

とりあえず削除フラグ

論理削除の理由

とりあえず削除フラグ

• エンドユーザから見えなくしたいがデータは消したくない • 削除したデータを検索したい • データを消さずにログに残したい • 誤った操作をなかったことにしたい、直ぐに元に戻したい

とりあえず削除フラグ

そして生まれるDELETE_FLAG

とりあえず削除フラグ

論理削除のデメリット

とりあえず削除フラグ

• 積み重なるWHERE delete_flag = 0 • Unique制約が使えなくなる • 複雑な表示条件(Query)の原因

とりあえず削除フラグ

• 積み重なるWHERE delete_flag = 0 • Unique制約が使えなくなる • 複雑な表示条件(Query)の原因

積み重なるWHERE句

-- ブログを取ってきたい場合 SELECT * FROM blog WHERE blog.delete_flag = 0

積み重なるWHERE句

-- ブログを取ってきたい場合 SELECT * FROM blog WHERE blog.delete_flag = 0

ブログの削除フラグを見る

積み重なるWHERE句

-- ブログを取ってきたい場合 SELECT * FROM blog WHERE blog.delete_flag = 0

ブログの削除フラグを見る

更新者アカウントが削除されてたら?

積み重なるWHERE句

-- 有効なアカウントのブログを取ってきたい場合 SELECT * FROM blog INNER JOIN users ON users.id = blog.user_id AND users.delete_flag = 0 WHERE blog.delete_flag = 0

積み重なるWHERE句

-- 有効なアカウントのブログを取ってきたい場合 SELECT * FROM blog INNER JOIN users ON users.id = blog.user_id AND users.delete_flag = 0 WHERE blog.delete_flag = 0

ユーザの削除フラグを見る

積み重なるWHERE句

-- 有効なアカウントのブログを取ってきたい場合 SELECT * FROM blog INNER JOIN users ON users.id = blog.user_id AND users.delete_flag = 0 WHERE blog.delete_flag = 0

ユーザの削除フラグを見る

所属が削除されてたら?

積み重なるWHERE句

-- 有効な所属の有効なアカウントのブログを取ってきたい場合 SELECT * FROM blog INNER JOIN users ON users.id = blog.user_id AND users.delete_flag = 0 INNER JOIN customers ON customers.id = users.customer_id AND customers.delete_flag = 0 WHERE blog.delete_flag = 0

積み重なるWHERE句

-- 有効な所属の有効なアカウントのブログを取ってきたい場合 SELECT * FROM blog INNER JOIN users ON users.id = blog.user_id AND users.delete_flag = 0 INNER JOIN customers ON customers.id = users.customer_id AND customers.delete_flag = 0 WHERE blog.delete_flag = 0

JOINがどんどん積み重なる

積み重なるWHERE句

-- 有効な所属の有効なアカウントのブログを取ってきたい場合 SELECT * FROM blog INNER JOIN users ON users.id = blog.user_id AND users.delete_flag = 0 INNER JOIN customers ON customers.id = users.customer_id AND customers.delete_flag = 0 WHERE blog.delete_flag = 0

JOINがどんどん積み重なる

Queryがボトルネックに

とりあえず削除フラグ

Queryの複雑度に対する対策

とりあえず削除フラグ

Queryの複雑度に対する対策 ↓

有効なデータのViewを作る

とりあえず削除フラグ

Queryの複雑度に対する対策 ↓

有効なデータのViewを作るQueryの高速化にはならない

とりあえず削除フラグ

全てのINDEXのセカンダリに

DELETE_FLAGを入れる必要がある

とりあえず削除フラグ

全てのINDEXのセカンダリに

DELETE_FLAGを入れる必要がある

INDEXの肥大化よりも そもそもINDEXを効かせる 複数のQueryを書くのが大変

とりあえず削除フラグ

PostgreSQLには

マテリアライズド・ビューがある

とりあえず削除フラグ

マテビューで有効データViewを作る

とりあえず削除フラグ

マテビューで有効データViewを作る ↓

高速でQueryもシンプル

とりあえず削除フラグ

マテビューで有効データViewを作る ↓

高速でQueryもシンプルSELECT * FROM active_blog

とりあえず削除フラグ

それ、シンプルになるの参照だけ

とりあえず削除フラグ

それ、シンプルになるの参照だけ ↓

更新系で生まれる悪夢

とりあえず削除フラグ

更新したらマテビューを更新

とりあえず削除フラグ

更新したらマテビューを更新 ↓

customersでもusersでもblogでも 更新があればマテビューも更新

とりあえず削除フラグ

更新したらマテビューを更新 ↓

customersでもusersでもblogでも 更新があればマテビューも更新

頻発する REFRESH MATERIALIZED VIEW

とりあえず削除フラグ

実際にはdeleteされないので データは増え続ける

とりあえず削除フラグ

実際にはdeleteされないので データは増え続ける

↓ マテビューの更新コストが肥大

とりあえず削除フラグ

実際にはdeleteされないので データは増え続ける

↓ マテビューの更新コストが肥大

そして第三章の問題へ…

とりあえず削除フラグ

• 積み重なるWHERE delete_flag = 0 • Unique制約が使えなくなる • 複雑な表示条件(Query)の原因

とりあえず削除フラグ

• 積み重なるWHERE delete_flag = 0 • Unique制約が使えなくなる • 複雑な表示条件(Query)の原因

つまり外部Key制約も死ぬ

とりあえず削除フラグ

id name age deleted

1 soudai 30 1

2 soudai 30 0

3 sone 20 0

4 taketomo 31 0

とりあえず削除フラグ

id name age deleted

1 soudai 30 1

2 soudai 30 0

3 sone 20 0

4 taketomo 31 0

とりあえず削除フラグ

id name age deleted

1 soudai 30 1

2 soudai 30 0

3 sone 20 0

4 taketomo 31 0

同じユーザが2名いるのでUnique制約できない

とりあえず削除フラグ

id name age deleted

1 soudai 30 1

2 soudai 30 0

3 sone 20 0

4 taketomo 31 0

同じユーザが2名いるのでUnique制約できない

もちろん外部キー制約もできない

とりあえず削除フラグ

id name age deleted

1 soudai 30 1

2 soudai 30 0

3 sone 20 0

4 taketomo 31 0

サロゲートキーで外部キー制約した場合は 関連したデータは別人扱いになる

とりあえず削除フラグ

id name age deleted

1 soudai 30 1

2 soudai 30 0

3 sone 20 0

4 taketomo 31 0

サロゲートキーで外部キー制約した場合は 関連したデータは別人扱いになる

ナチュラルキーの場合は死ぬ

とりあえず削除フラグ

PostgreSQLには

Unique制約にWHERE句がある

Unique制約が使えなくなる

-- deletedが0の時にUnique制約をかける CREATE UNIQUE INDEX user_idx ON users (name) WHERE deleted = 0;

とりあえず削除フラグ

id name age deleted

1 soudai 30 1

2 soudai 30 0

3 sone 20 0

4 taketomo 31 0

とりあえず削除フラグ

id name age deleted

1 soudai 30 1

2 soudai 30 0

3 sone 20 0

4 taketomo 31 0

とりあえず削除フラグ

id name age deleted

1 soudai 30 1

2 soudai 30 0

3 sone 20 0

4 taketomo 31 0

とりあえず削除フラグ

id name age deleted

1 soudai 30 1

2 soudai 30 0

3 sone 20 0

4 taketomo 31 0

deletedが1なのでUnique制約対象外

とりあえず削除フラグ

id name age deleted

1 soudai 30 1

2 soudai 30 0

3 sone 20 0

4 taketomo 31 0

nameでUnique制約をかけれる

とりあえず削除フラグ

id name age deleted

1 soudai 30 1

2 soudai 30 0

3 sone 20 0

4 taketomo 31 0

nameでUnique制約をかけれる

しかし外部キー制約は死ぬ

とりあえず削除フラグ

Unique制約にWHERE句 ↓

結局は外部キー制約は死ぬ

とりあえず削除フラグ

外部キー制約が死ぬ

とりあえず削除フラグ

外部キー制約が死ぬ ↓

テーブルの関連性の担保が死ぬ

とりあえず削除フラグ

データの不整合を防げない

とりあえず削除フラグ

データの不整合を防げない ↓

バグの温床になる

とりあえず削除フラグ

• 積み重なるWHERE delete_flag = 0 • Unique制約が使えなくなる • 複雑な表示条件(Query)の原因

とりあえず削除フラグ

正しいデータを

表示させる条件が多い

積み重なるWHERE句

-- 有効な所属の有効なアカウントのブログを取ってきたい場合 SELECT * FROM blog INNER JOIN users ON users.id = blog.user_id AND users.delete_flag = 0 INNER JOIN customers ON customers.id = users.customer_id AND customers.delete_flag = 0 WHERE blog.delete_flag = 0

表示させるために 3つのテーブルが関係している

とりあえず削除フラグ

1つのデータを取るために

関係するTABLEが多すぎる

とりあえず削除フラグ

「あのデータ取るには○○と☓☓と△△をJOINしてWHEREしてSELECT

句にASで別名を付けて…」 みたいな事が頻発する

とりあえず削除フラグ

仕様変更の時の影響範囲が広い

とりあえず削除フラグ

仕様変更の時の影響範囲が広い ↓

つまりバグの温床になる

とりあえず削除フラグ

論理削除は使わないほうが良い

とりあえず削除フラグ

論理削除は使わないほうが良い ↓

TABLEに状態を持たせない

とりあえず削除フラグ

論理削除は使わないほうが良い ↓

TABLEに状態を持たせない

リレーショナルモデルで表現する

とりあえず削除フラグ

処方箋

とりあえず削除フラグ

有効なデータのみを残す

とりあえず削除フラグ

有効なデータのみを残す ↓

例えば削除済みTABLEを作る

とりあえず削除フラグ

deleted_usersusers

とりあえず削除フラグ

deleted_usersusers

soudai

sone

taketomo

とりあえず削除フラグ

deleted_usersusers

soudai

sone

taketomo

削除(DELETE文)

とりあえず削除フラグ

deleted_usersusers

soudai

sone

taketomo

soudai

トリガーで移す

とりあえず削除フラグ

deleted_usersusers

sone

taketomo

soudai

テーブルには実際のデータのみが残る

とりあえず削除フラグ

注意点

とりあえず削除フラグ

注意点 ↓

トリガーは仕様変更に弱い

とりあえず削除フラグ

注意点 ↓

トリガーは仕様変更に弱い

例えばALTER文でカラムを増やすと トリガーが死ぬ

とりあえず削除フラグ

ケースバイケースで手法は選ぶこと

とりあえず削除フラグ

ケースバイケースで手法は選ぶこと ↓

論理削除は使わない

とりあえず削除フラグ

関連したテーブルは?

とりあえず削除フラグ

関連したテーブルは? ↓

必要なら削除済みTABLEを作る 不要ならDELETEする

とりあえず削除フラグ

関連したテーブルは? ↓

必要なら削除済みTABLEを作る 不要ならDELETEする

外部キー制約が使えればCASCADEも出来る

とりあえず削除フラグ

関連したテーブルは? ↓

必要なら削除済みTABLEを作る 不要ならDELETEする

外部キー制約が使えればCASCADEも出来る

事実のみを保存する

とりあえず削除フラグ

•ユーザの状態(statusカラム) •課金状態(与信、請求済み) •カート情報(カゴの中、購入済み)

類似例

とりあえず削除フラグ

DBに状態を持たせるのは危ない

とりあえず削除フラグ

DBに状態を持たせるのは危ない ↓

DBには事実のみを保存する

あじぇんだ

1 自己紹介 2 とりあえず削除フラグ 3 困ったときの配列型とJSON型 4 マテビューの功罪 5 まとめ

困ったときの配列型とJSON型

PostgreSQLは型が豊富

困ったときの配列型とJSON型

配列型やJSON型を使うと

困ったときの配列型とJSON型

配列型やJSON型を使うと ↓

1:Nを1つのカラムで表現出来る

困ったときの配列型とJSON型

id title body

1 初めてのポスグレ …

2pg9.5 beta リリース …

3篠崎愛

「おしのブログ」 …

blog_id tag

1 postgres1 beginner3 魂

blog tag

困ったときの配列型とJSON型

id title body

1 初めてのポスグレ …

2pg9.5 beta リリース …

3篠崎愛

「おしのブログ」 …

blog_id tag

1 postgres1 beginner3 魂

blog tag

1:N

困ったときの配列型とJSON型

id title body tag

1 初めてのポスグレ … [“postgres”, ”beginner”]

2pg9.5 beta リリース … null

3篠崎愛

「おしのブログ」 … [“魂”]

blog(配列型の使用例)

困ったときの配列型とJSON型

テーブルを減らす事が出来る

困ったときの配列型とJSON型

テーブルを減らす事が出来る ↓

しかもINDEXも効く

困ったときの配列型とJSON型

JSON型の場合

困ったときの配列型とJSON型

JSON型の場合 ↓

KeyValueを表現できる

困ったときの配列型とJSON型

id title body properties

1 初めてのポスグレ … {tag:[“postgres”, “beginner”]}

2 pg9.5 beta リリース

… null

3 篠崎愛 「おしのブログ」

… {tag:[“魂”]}

blog(JSON型の使用例)

困ったときの配列型とJSON型

id title body properties

1 初めてのポスグレ … {tag:[“postgres”, “beginner”]}

2 pg9.5 beta リリース

… null

3 篠崎愛 「おしのブログ」

… {tag:[“魂”], event:[“握手会”]}

blog(JSON型の使用例)

困ったときの配列型とJSON型

id title body properties

1 初めてのポスグレ … {tag:[“postgres”, “beginner”]}

2 pg9.5 beta リリース

… null

3 篠崎愛 「おしのブログ」

… {tag:[“魂”], event:[“握手会”]}

blog(JSON型の使用例)

JSON型はドキュメント指向なスキーマレスを表現できる→つまりDB構造を変更すること無く項目を追加できる

とりあえず削除フラグ

注意点 ↓

トリガーは仕様変更に弱い

例えばALTER文でカラムを増やすと トリガーが死ぬ

とりあえず削除フラグ

注意点 ↓

トリガーは仕様変更に弱い

例えばALTER文でカラムを増やすと トリガーが死ぬ

JSON型ならDB構造を変えない →仕様変更時にトリガーが死なない

困ったときの配列型とJSON型

JSON型はデータに柔軟性を与える

困ったときの配列型とJSON型

JSON型はデータに柔軟性を与える ↓

それは整合性を犠牲にする

困ったときの配列型とJSON型

配列型もJSON型も

データを守る機能が弱い

困ったときの配列型とJSON型

外部キー制約→使えない CHECK制約→使えない

困ったときの配列型とJSON型

外部キー制約→使えない CHECK制約→使えない

該当のカラムが 紐づくテーブルを関連付けなれない

困ったときの配列型とJSON型

外部キー制約→使えない CHECK制約→使えない

該当のカラムに 入るデータを制限出来ない

困ったときの配列型とJSON型

UPDATE文に弱い

困ったときの配列型とJSON型

UPDATE文に弱い ↓

特定の要素のみの更新が難しい

困ったときの配列型とJSON型

仕様が曖昧な時にJSON型

困ったときの配列型とJSON型

仕様が曖昧な時にJSON型 ↓

仕様変更には強いので有効だが 運用時にデータが守れない

困ったときの配列型とJSON型

処方箋

困ったときの配列型とJSON型

ちゃんと正規化する

困ったときの配列型とJSON型

ちゃんと正規化する ↓

面倒でも正規化すれば 運用フェーズで必ず救われる

困ったときの配列型とJSON型

ちゃんと正規化する ↓

面倒でも正規化すれば 運用フェーズで必ず救われる

逆説的にリソースが許されるなら 困ってから正規化の選択肢もアリ

困ったときの配列型とJSON型

使っていい場合

困ったときの配列型とJSON型

• UPDATEが無い(INSERTのみ) • データを守る必要が無い(制約を使わない) • データを関連付けない(JOINしない) • UPDETE時は対象カラムをまるっと更新 • 集約関数等の対象にならない

困ったときの配列型とJSON型

•ブログのTags •可変なプロパティ •外部のWebAPIの結果

具体例

困ったときの配列型とJSON型

•ブログのTags •可変なプロパティ •外部のWebAPIの結果

具体例例えばServerのOS情報などは ディストリビューションの違いを 気にしなくても良くなる

困ったときの配列型とJSON型

•ブログのTags •可変なプロパティ •外部のWebAPIの結果

具体例

いつ仕様変更が来るかわからない →その変更に柔軟に対応できる

困ったときの配列型とJSON型

ご利用は計画的に

困ったときの配列型とJSON型

ご利用は計画的に ↓

基本的に正規化しましょう

あじぇんだ

1 自己紹介 2 とりあえず削除フラグ 3 困ったときの配列型とJSON型 4 マテビューの功罪 5 まとめ

マテビューの功罪

マテビューはPostgreSQL 9.3から

マテビューの功罪

•複雑なテーブルをシンプルに •遅いクエリを高速に •INDEXチューニングも可能

メリット

マテビューの功罪

•表領域を使う •リフレッシュが必要 •リフレッシュのコストが高い

デメリット

マテビューの功罪

•表領域を使う •リフレッシュが必要 •リフレッシュのコストが高い

デメリット

マテビューの功罪

•表領域を使う •リフレッシュが必要 •リフレッシュのコストが高い

デメリット実体を持つのでデータファイルが作成される 作った分だけディスクスペースを圧迫する

マテビューの功罪

•表領域を使う •リフレッシュが必要 •リフレッシュのコストが高い

デメリット実体を持つのでデータファイルが作成される 作った分だけディスクスペースを圧迫する

参照元のテーブルの増加と合わせてマテビューが増えれば指数的にディスクスペースの使用量が増える

マテビューの功罪

•表領域を使う •リフレッシュが必要 •リフレッシュのコストが高い

デメリット

マテビューの功罪

集計済み表課金log

計算結果をマテビューにする

マテビューの功罪

集計済み表課金log

課金1

計算結果をマテビューにする

課金2

マテビューの功罪

集計済み表課金log

課金1

計算結果をマテビューにする

課金2

リフレッシュ

マテビューの功罪

集計済み表課金log

課金1 集計結果

計算結果をマテビューにする

課金2

リフレッシュ

マテビューの功罪

集計済み表課金log

課金1 集計結果

計算結果をマテビューにする

課金2

集計結果を参照するので高速でシンプル!!

マテビューの功罪

集計済み表課金log

課金1 集計結果

計算結果をマテビューにする

課金2

課金3

マテビューの功罪

集計済み表課金log

課金1 集計結果

計算結果をマテビューにする

課金2

課金3

☓更新失敗

マテビューの功罪

集計済み表課金log

課金1 集計結果

計算結果をマテビューにする

課金2

課金3

☓更新失敗

前回の更新結果のままなので不整合

マテビューの功罪

更新が失敗した際の処理が必要

マテビューの功罪

更新が失敗した際の処理が必要 ↓

自動更新の場合に アプリケーションで制御出来ない

マテビューの功罪

更新が失敗した際の処理が必要 ↓

自動更新の場合に アプリケーションで制御出来ない

DBはテストの自動化も大変 未然にバグを検知しにくい

マテビューの功罪

•表領域を使う •リフレッシュが必要 •リフレッシュのコストが高い

デメリット

マテビューの功罪

•表領域を使う •リフレッシュが必要 •リフレッシュのコストが高い

デメリット

PostgreSQLには差分更新は無い →データがクエリの実行速度が遅ければ更新も遅い

マテビューの功罪

•表領域を使う •リフレッシュが必要 •リフレッシュのコストが高い

デメリット

PostgreSQLには差分更新は無い →データがクエリの実行速度が遅ければ更新も遅い

データが大きくなるにつれ、負荷も上がる →特に集約関数を使ったようなクエリは

メモリに乗らなくなった瞬間に急激に遅くなる

マテビューの功罪

•表領域を使う •リフレッシュが必要 •リフレッシュのコストが高い

デメリット

PostgreSQLには差分更新は無い →データがクエリの実行速度が遅ければ更新も遅い

データが大きくなるにつれ、負荷も上がる →特に集約関数を使ったようなクエリは

メモリに乗らなくなった瞬間に急激に遅くなる

更新が遅くなった事に気づきにくい

マテビューの功罪

処方箋

マテビューの功罪

DB状態を監視する

マテビューの功罪

DB状態を監視する ↓

死活監視だけでは無く レコードの増加量やCPU使用率など

マテビューの功罪

DB状態を監視する ↓

死活監視だけでは無く レコードの増加量やCPU使用率など

Zabbixを使っているならpg_monzなどを使う

マテビューの功罪

兎にも角にもマテビューはしない

マテビューの功罪

兎にも角にもマテビューはしない ↓

マテビューを集計してマテビュー のような闇を作らない

マテビューの功罪

マテビューは便利だが銀の弾丸ではない ↓

必要になった時、それは多くの場合 設計に問題を抱えている

マテビューの功罪

マテビューは便利だが銀の弾丸ではない ↓

必要になった時、それは多くの場合 設計に問題を抱えている

マテビューの功罪

マテビューは便利だが銀の弾丸ではない ↓

必要になった時、それは多くの場合 設計に問題を抱えている

根本的な解決策が必要なので マテビューが必要になった時は設計を見直す

マテビューの功罪

使わないに越した事は無い

マテビューの功罪

使わないに越した事は無い ↓

積極的に使うものではない

マテビューの功罪

使わないに越した事は無い ↓

積極的に使うものではない

新機能は使いたくなるけども

あじぇんだ

1 自己紹介 2 とりあえず削除フラグ 3 困ったときの配列型とJSON型 4 マテビューの功罪 5 まとめ

まとめ

PostgreSQLは高機能

まとめ

PostgreSQLは高機能 ↓

設計の不備を 機能でカバー出来てしまう

まとめ

機能に依存し過ぎない

まとめ

機能に依存し過ぎない ↓

シンプルな設計は全てに勝る

まとめ

愚者は経験に学び、賢者は歴史に学ぶ

まとめ

周囲の経験談から学ぶ

まとめ

周囲の経験談から学ぶ ↓

積極的にコミュニティを利用する

参考資料

・公式ドキュメント(日本語) https://www.postgresql.jp/document/9.4/html/postgres-fdw.html

・postgresql-jp Slack(チャットルーム) https://postgresql-hackers-jp.herokuapp.com/

まとめ

データの寿命はコードよりも長い

まとめ

一度作ったDBは消せない

まとめ

一度作ったDBは消せない ↓

設計が大事

まとめ

より良い設計を

一緒に考えて行きましょう

ご静聴ありがとうございました。