極北データモデリングスマートフォン用の表示で見る極北データモデリングこの広告は、90日以上更新していないブログに表示しています。それは論理削除の問題なのかモデリング削除フラグ(というか「論理削除を削除フラグだけで実装すること」)批判は何度も見てきたがPostgreSQLアンチパターンこれ見るともはや論理削除自体が闇扱いになってしまったようだ。闇だろうと何だろうと論理削除(というドリルが提供する穴)は要件の実装に必要なので、このへんの議論はあまりまじめに追ってこなかった。いつだったか「削除フラグはバグの温床だからやめろ」という主張を読んでおぉなるほどと思い、ではどうやって論理削除を実装するのかなと思って続きを読むと「『ほんとに削除したデータが必要ですか?』とユーザに確認して、物理削除に変えさせてもらう」と書いてあってズコーとなったことがあるが、要件削っていいなら実装上のどんな問題も消えるわけで、何かもう別世界の議論で自分の仕事には関係ないと思っていた。これだけ繰り返し批判されているからには、論理削除を正しく使う方法なり条件なりを明らかにしておかないと不安だ。最近話題になった上記プレゼン資料(以下「資料」)を導きの糸にして、論理削除の何が問題なのかを理解したい。資料は論理削除の3つの問題点を指摘している。パーティショニング「削除フラグはクエリを複雑にする」という指摘には、以前から「生きているデータだけ切り出すビューを作ればいいんじゃないの」と思っていたが、資料のP-45以降にそれではダメだと書いてあった。それらのビュー同士を結合するクエリはパフォーマンスに難がある(=いらないデータを物理削除したテーブルを参照するより、遅い)パフォーマンス問題はインデックスやマテリアライズドビューで解決できない論理削除データの件数は増え続けるので、問題は悪化する一方になるとのことだが。商用DBであればパーティショニングがサポートされているので、削除フラグのON/OFFでパーティションを割ったテーブルに、生きているデータだけ切り出すビューをかぶせれば、指摘されている問題は解決する。死んだデータが0件から10億件に増えても、生きているデータだけを抽出/結合する速さは変わらない。PostgreSQLも(かなり変わった形で)パーティショニングをサポートしているので、同じことができるような気がするが、資料では検討されていない。PostgreSQLには「パーティションをまたがるUNIQUE制約が作れない」という制約があるらしいので、後段の議論との整合性で論外とされたのかもしれないし、全然関係ないかもしれない。「クエリが複雑になり、パフォーマンスも下がる」というのは、自由にパーティションを切れないDBMSに言えることであって、論理削除自体の問題ではない。当該資料はPostgreSQLのアンチパターンなので「削除フラグはダメだ」という結論でよいかもしれないが、一般論と誤解してはならない。Null-Key本来、論理削除でキー重複が発生することはない。論理削除とUNIQUE制約は普通に両立する。論理削除の概念を「データの指す対象が削除(=社員なら退職、ユーザなら退会、商品なら廃版、...)されたら、削除フラグを立てること」だけでなく「データの更新履歴をマスタテーブル本体に累積し、過去の履歴には削除フラグを立てること」にまで拡張すると、キー重複が発生し、UNIQUE制約との兼ね合いが問題になる。論理削除と更新履歴は分けて考えなくてはならないと思うがそれは置いて、資料の議論に乗っかるなら、更新履歴とUNIQUE制約/外部キー制約の両立にはNull-Keyの技法を使えばよい。←ヌル・キーの2列を用意し、まず両方に同じユーザ名を設定する。データが最新版から過去の履歴に変わるタイミングで、name_NKにNULLを設定する*1。Unique制約・外部キー制約(たぶん遅延制約にする必要がある)はname_NKについて設定し、主キーは無し(か、name列+何か)にする。PostgreSQLをはじめ、大半の実装ではNULLの重複はUNIQUE制約にひっかからないので、これで問題を回避できる。Null-Keyなんて聞いたことないしそんな妙なことしたくねえなぁと思うかもしれないが、これは20年以上前から*2使われている由緒正しいテクニックなので、経験ではなく歴史に学ぶ賢者ならむげにできないはずだ。俺は使ったことないけど。?ここは資料だけ見ても意味がわからなかった。プレゼン聴かないで「1つのデータを取るために関係するtableが多すぎる」原因が論理削除である理由がわかった方は居ますか。削除済みTABLEを作る」これが処方箋になるのはどういう場合だろうか。「論理削除はUNIQUE制約が使えないから駄目だ」というからには、処方箋ではUNIQUE制約が機能しなくてはならない。削除済みテーブルに削除データを移動したら、元テーブルにUNIQUE制約は残っていても、その制約は半分死んでいる。deleted_usersに移動した退会ユーザのユーザID(=name列)の値を、usersに再度挿入されることを拒否できないから。これで問題が起きないのは、UNIQUE制約の付いたキー値を使い回せる場合だけだろう。資料の例はブログシステムなので問題なさそうだ。ユーザID=xxxxが退会したら、ブログエントリも全部削除テーブルに移動し、次にユーザID=xxxxが新規登録されたら、まったくの別人として扱えばよい。これに対して、キー値の使い回しができない場合は、「有効なデータのみを残す」アプローチは処方箋にならない。仮に「論理削除はすべて悪だ」と信じて商品マスタの削除フラグを廃止し、廃版商品は削除済み商品テーブルに移したとする。廃版商品の商品コードは、商品マスタ本体のUNIQUE制約では拒否できない。結果、廃版商品と同じ商品コードの新商品を作ることができてしまう。受注明細(本体であれ、削除済みテーブルであれ)に載っているその商品コードは、新商品を指すのか、売切れ直前の廃版商品を指すのか、もはや知る方法がない。まとめ論理削除を削除フラグだけで実装するのはいろいろ問題があるが、それは論理削除の実装方法の問題であって、論理削除自体の問題ではないような。それは論理削除の問題なのかTweet広告を非表示にするもっと読むコメントを書く2014-08-27主キーが「圧倒的多数の関数従属性が成立しないことを保証する」ものならば、受注ヘッダに顧客名を置いてはならないのだろうかモデリング渡辺先生がミステリアスな記事をupしている。http://watanabek.cocolog-nifty.com/blog/2014/08/post-b7d8.htmlつまり主キー設計というものは、「いくつかの関数従属性が成立することを分析する仕事」というよりは、むしろ「圧倒的多数の関数従属性が成立しないことを保証する仕事」である。その責任の重大さがおわかりだろうか。情報処理試験なんかに出てくる主キー設計手順では、テーブル上の複数の候補キーから主キーを選択するのではなかったか。その他の属性」という関数従属性がテーブル内にあるのは普通のことではないのか。元記事のセミナーの例に倣って、こんな受注ヘッダを想定する。顧客番号)→顧客名が成り立つだろう。顧客番号)→顧客名も成り立ってしまう(元記事における"{a,d}→c"のケース)。この関数従属性が「禁じられている」と考えるなら、何らかの手当(顧客名か受注日時を外出しにするとか?)をしなくてはならないが、向こうの流派ではいったいどうするのだろうか。いう関数従属性は成り立っていないと考えている。顧客名を受注ヘッダ上に置いており、「必要なら顧客マスタから拾ってくればよいデータ」ではないと考えている。成り立つと思っている。これは我々の業界でいうマスタ/トランザクションの違いであり、オントロジーでいうendurant/perdurantの違いだ。イベント)は特定の時刻に固定されており、その属性は時刻を指定しなくては値を特定できない。して渡辺先生もこんなことわかってて書いていて、元記事の「主キー」を「候補キー」と読み替えればいいだけの話なのだろうか。見る正月にこれを第2部まで読んだ感想を書こうと思って、何ともう2月後半になってしまった。考えさせられたことを忘れてしまう前に感想文を書きます。(DBばっかりいじっててコードを書かない)俺みたいなのから見たオブジェクト指向設計の特徴に、「実体(エンティティ)の存在は認めても、関係(リレーションシップ)の存在をなかなか認めない」、つまり関係を極力クラスとして立てない、というのがある。社員)ことを好むのだ。エリック・エヴァンスの考えるDDD*1もまた、この特徴を受け継いでいるように見える。DDDのモデルDDDがモデルから関係を排除している例を見てみましょう。前掲書P-70に、銀行の口座振替のシーケンス図が載っている。debitメソッド)が、口座オブジェクト自身に取り付けられていることがわかる。このシーケンス図では、2つの口座の残高を操作するメソッドは見えているが、「何月何日に、a口座からb口座に、いくら送金した」というトランザクションデータを生成するタイミングは描かれていない。金銭取引オブジェクトなどを省略した」という意味のことが書いてあるので、どこかで生成するつもりなのだろう。左端の「資金振替サービス」のtransferToメソッドの中で、ログみたいな処理の副産物として、トランザクションを生成するのではないか。いずれにしても、このモデルにおいてはトランザクションは単なるデータであって、能動的なオブジェクトとして処理を駆動していない。アナリシスパターンのモデル入出金ロジックを口座に取り付ける以外にも、オブジェクト指向設計的にアリなモデルは存在する。P-113には、ファウラーお勧めのモデルとして、トランザクションが口座(同書の表現では「勘定」となっている)の明細(同じく「エントリ」)を生成する例が描かれている。話に関係ある部分だけ切り出すと、こうだ:DDD本とは操作の方向が逆になっていることがわかる。ファウラーのモデルでは、口座自身ではなく、口座と口座の関係であるトランザクションが振替処理を主管する。DDDのモデルなら「資金振替サービス」のレイヤにあたる何かに生成されたトランザクションオブジェクトが、2つの口座の明細を生成するのだ。}といったデータ項目を持つ永続化対象であり、DDDの分類で言えばサービスではなくエンティティに相当する。ファウラーのモデルでは、口座オブジェクトは入金・出金ロジックを持たず、いわば「裏口から」勝手に明細を追加される受身の存在だ*2。ファットモデルの原因DDD本とアナリシスパターン、それぞれのモデルを採用した場合の結果はどうなるだろうか。集約(Aggregates)を重視するDDDの立場から見れば、口座明細は口座の後ろに隠れているべきものであって、口座を経由せずに誰かが勝手に口座明細を作成するのは問題あり、ということになるのかもしれない。DDDは、集約を迂回して必要なオブジェクトだけをつかむ書き方には冷淡だ。前掲書P-148にこんな風に書いてある。クライアントコードがデータベースを直接使用していると、開発者は、集約のようなモデルの機能や、オブジェクトのカプセル化さえも迂回し、必要なデータを直接取り出して操作したくなってしまう。...開発者は、欲しいオブジェクトを何でも直接つかんでしまおうという気になる。この点についてはDDDのモデルの方が優れているのかもしれない。言って集約というアイデアは、実践上どこまで貫徹できるのか怪しいと思う。ファウラーのモデルが優れているのは、口座オブジェクトがより純粋・シンプルなところだ。逆にDDDの口座オブジェクトはよりファットだ。振替処理というただ1つのユースケースを実装するために、口座には入金・出金ロジックと、たぶん口座明細を生成するロジックが追加されている。ファウラーのモデルでは、これら全てが口座間の関係であるトランザクションオブジェクトに吸収され、口座自身の「純度」が保たれている*3。「口座が入出金ロジックを持っているのはまったく自然な設計であって、何の問題もない」と考える人もいるかもしれない。俺も自然な設計だとは思う。特定のユースケースを処理するためのメソッドやメンバ変数の追加を無条件に許すと、口座のようなシステムの中核クラスはどんどん膨れ上がり、いわゆるファットモデルが誕生する。DDDを実践する場合、「自然な設計*4」を優先して、エンティティ間の関係を積極的にクラス化しなかったら、システムの規模拡大に伴ってファットモデルが発生するのは避けられないと思う。*1:DDDにもいろいろあるんだろうけど、以下単にDDDと表記する*2:この図で口座の残高属性が更新されていないのは、「残高はエントリを集計して出す導出項目」という解釈だからだろう。結果、振替処理に口座はまったく参加しない*3:私はT字形ER手法の考え方に従い、エンティティが他のエンティティとの関係を内部に取り込んでいることを「純度の低下」と考えている。社員クラスに「所属部門」という名前の、部門クラスのメンバ変数があったら、それは所属という関係を内部に取り込んでヨゴレていると見なす。モデリング営業のアプローチ方法にA,Bがあるとして(例えば「礼状は手書きで出す」「礼状は印刷して出す」とか)、Aの成約率が10%でBのそれが5%なら、「Aしかやらない」というのは悪くない選択だろう。我々の仕事は営業とは違っている。ある量産品の製造方法にA,Bがあるとして、Aの良品率が98%でBのそれが95%なら、Aで製造するのが正しいだろう。我々の仕事は量産品の製造とは違っている。システム開発における失敗プロジェクトは、営業における失注や工場における不良品のようなものではない。事業を行う上での必要経費ではない。A,Bどちらの開発技法を採用すべきかを、プロジェクトの成功率で決めることはできない。仮に自然キーを採用したプロジェクトの成功率が5割で、サロゲートキーを使った場合のそれが9割だったとして、「すべてのシステムにサロゲートキーを使う」という判断は間違っている。1割のプロジェクトが確実に失敗するからだ。その1割に自然キーがフィットしていたとすれば、完全な判断ミスをしたことになる。この1割の存在を云々することはサロゲートキーに対する批判・攻撃ではない。それはサロゲートキーという技法を安全に使うために絶対必要な何かだ。みんな大好きクレイトン・クリステンセンがこのあたりのことを繰り返し書いている*1。クリステンセンは「現実を観察して理論を作ったら、次はその理論に対する反例を探せ」と言う。自分の理論を支持する事例などいくら集めても意味がないのだ。理論が失敗する事例を積極的に探して、失敗する条件を明らかにすることで初めて理論は改善される。「こうすれば、結果は何%の確率でこうなる。結果には...という傾向が見られる」という相関関係の記述から、「〜という条件が揃ったところででこうすれば、結果は必ずこうなる」という「条件付き因果関係の言明」に理論が進化する、のだそうだ。後者の理論があって初めて、我々は(全体の傾向ではなく)個々の事例の結果を予測することができるようになる。これから書くことはサロゲートキーへの批判ではない必要なのは、技法が通用する時・しない時の、正確な場合分けだ。これは、その技法を使っている人にはできないことだと思う。「自然キーはいつ変わるかわからないから、すべてのテーブルにサロゲートキーを振るべきだ」と信じている人は、世の中に「(正当な理由をもって)サロゲートキーを振ることが不可能なシステム」があることに気付いていないだろう。内側から限界は見えないのだ。よって、ある技法の限界を探るには、その技法を使っていない人の協力が必要になる。ところで現状の俺は完全な自然キー派だ。いま運用担当しているお客さん(2社)のシステムは、1つは本番DB内のテーブル数が900本、もう一つは2000本以上あるが、サロゲートキーを付加しているテーブルは1つもない。サロゲートキー派の人には地獄のような状態に見えると思うが、これらはいわゆる情報系のシステムであって、基幹系からデータの供給を受けて動いているので、自前でサロゲートキーを振ることが不可能*2なのだから仕方がない。近日中に、そういう立場からサロゲートキーの適用範囲を云々するつもりだが、これは既述の通り自然キー派によるサロゲートキーへの批判・攻撃ではない。