📝

一時保存の物理データ設計

2022/08/21に公開
  • 世の中には何かを一時保存したい、下書き機能が欲しい、という要件が時たまある
  • 物理的なデータ設計において、考えておきたい点を整理
  • 一時保存されるという事象を発見した時点で、保存対象は「未完成」or「完成」という少なくとも二つの状態を持つことになる
    • それを状態と捉えるか、リソースと捉えるかは、設計次第

ケーススタディ

  • 何かしらの会員登録機能を有するシステムの会員データを例に考える
  • 会員データは以下の項目を持つ
    • 氏名
    • 生年月日
    • 出身地
    • メールアドレス
    • 自己紹介文
  • 本登録する前に一時保存できるようにしたい、という要件がある
    • 自己紹介文を400文字以上入力しないと会員登録できないとか、適当にそんなのをイメージしてください(example なんで細かいおかしさは一旦無視してください)
    • 一時保存というのはブラウザキャッシュに依存しない形でやりたいというのも要件に含まれる
  • 必要な全てのデータをインプットすると、登録できるようになる

RDB での永続化の考え方

単一テーブル継承を使う

  • users みたいなテーブルを作って、そこで一時保存データも本登録済みのデータも全て管理する方法
    • status や flag 的なサムシングでそのレコードの状態が一時保存なのか登録済みなのかを峻別できるようにする必要がある
    • 愚直にやるとありがちなやつ
  • 一時保存したタイミングでデータがアップサートされる

pros

  • テーブルの数が抑えられる
  • 管理(インプット)項目を増やした時に、users テーブルに DDL を実行するだけで良い
  • クエリ書くのが楽、な可能性がある

cons

  • ほとんどの項目が nullable になる
    • 本登録して会員になったタイミング以降は全てのカラムにデータが入っている状態が期待されるが、それを担保するには RDB の制約だけでは満たせず、アプリケーションロジックを書く必要がある
    • rails みたいな mvc and orm で model とテーブルが密結合しながらバリデーションもやるアーキテクチャだとこれが中々どうして厳しい
      • カスタムバリデーター用意したりと、どんどん model が複雑になっていく
      • クラス分けちゃえばいいのかもしれないけど
      • こういった意見は一定わかるけど、時と場合によりそう
      • 昨今のロバストネスの重要性から鑑みて一考の余地がある
        • 昨今のエンタープライズシステムとかだとそもそもイミュータブルデータモデリングで実装するケースもあり、全体のあるいは対象のロバストネスやデータレジリエンシーをどの程度担保したいか、みたいな話もセットでしないとダメなのではという気がする
        • postgres 等で使える check 制約に関しても、check 制約それ自体は model に現れず、ストアドプロシージャ等と一緒で多用するとアプリケーション全体の見通しが悪くなる気がするので使うのは慎重になりたい
        • 状態との組み合わせが増えてくると地獄そう
      • 一時保存に限って考えると、要件はサブクラスの単一テーブル保存とはやや違い、継承というより、不完全なデータを焼き付けておきたい、がやりたいことなので、サブクラスでしか扱わない項目だけでなく、ほとんどの項目を nullable にしないといけなくなって辛いねという
  • 一時保存時に必要なデータと登録済み時に必要なデータを全て一つのテーブルで持つ必要がある(詳しくは後述)
    • テーブルの肥大化

具象テーブル継承を使う

  • 本登録済み会員データを保存するための users テーブルとよく似たスキーマの user_candidates テーブル(名前は適当)を作り、一時保存のデータはそこで管理する手法
    • users には完璧なデータしか入れない
    • この二つは論理的に違うものでクラスも分かれるので、物理設計も分かれる、という考え方

pros

  • nill を極力排除できる
    • users テーブルのカラムには not null 制約を貼れる
    • users レコードを作る段で制約が効くので、ビジネスロジックで扱うユーザーデータにおいて、不正なデータがないことを物理設計単位で保証できる
  • 一時保存時に必要なデータと本登録済み時に必要なデータを分けて保持できる
    • 例えば、生年月日の項目で、入力時には「年」「月」「日」を分けて持っておきたいけど、登録済みなら全て結合した状態でしか使わないという場合、見え方は同じだが、スキーマとしては異なる
      • 一時保存時は3カラム、登録済みは1カラムで良い
        • (実際は raw data を持っておいた方がいいので、この例はあんまりよくないかもしれないけど、要はそれぞれで必要なデータだけを定義できると言いたい)
  • テーブル単位で格納場所が分割されるので、データ量増加によるクエリの性能劣化を遅らせられるかもしれない
    • 例えば一時保存のゴミデータがべらぼうに増えちゃって、登録済みユーザーのデータが欲しいだけなのに select にやたらと時間がかかるとかっていう被害がなくなる

cons

  • 管理(インプット)項目を増やすと、両方のテーブルに同時に DDL を実行する必要が出てくる可能性がある
    • 例えばニックネームの情報も持たせたい、となった時に、usersuser_candidates の両方に DDL を実行する必要がある
  • 一時保存含めた一覧ビューが欲しい、という要件が出てくると、select を2本投げたり、union したりする必要が出てきて、ややカオスみが増す
    • そこの critical みが増して、ビュー用に最適化したテーブルを別途作ったり、と正規化を崩さざるを得ない事態に陥ったり
  • サブクラス分だけテーブルが増えていってしまう
    • 一時保存、本登録済み、の二つしか登場していないが、削除済みを別テーブルに分けたり...とかやってるとどんどんテーブルが増えていく

半構造化データを使う

  • 一時保存と登録済みでテーブルは分けるんだけど、一時保存データの永続化に半構造化データを使う手法
    • 半構造化データは json や xml などを指す
  • 一時保存データなんて取り回すのはアプリケーションのごく一部分だけだし、大してロバストにしなくても困らないよね、というときは選択してみるのも良い
  • 半構造化データを使う場合、要件によっては Redis 等の KVS に保存するという選択肢もある
    • EC サイトのカート情報のようにトランザクションが多いが、最悪消えてもいいようなデータは RDB 使うよりも効率が良さそう

pros

  • 具象テーブル継承との違いは、スキーマの変更に対して強い、という点
    • 管理項目の変更を json のスキーマ変更で吸収できるので、一時保存テーブルに対して DDL を実行しなくてよい
  • 具象テーブル継承の継承元のテーブルが正規化されてたり、has many リレーションのリソースがあったりして全部を継承するのがダルい、みたいな時も array in json で押し込んで管理できる

cons

  • RDB のスキーマ定義の恩恵を受けられないので、堅牢性は落ちる
    • SQL アンチパターンの EAV の章で扱われていた内容

Discussion