🍣

(データベース)viewの使い所

2022/05/30に公開

どうも、株式会社プラハCEOの松原です

先日プラハチャレンジのメンターセッションの一環で「RDBのviewっていつ使うんですの?」と聞かれたので、実際に自分が遭遇した場面を振り返りながらまとめてみました。今回はマテリアライズドビューは想定していないので、パフォーマンス以外の観点に絞って考えてみます。

セキュリティの一環として

実装ミスによる情報漏洩

UserテーブルにnameやageやemailやaddressやtelephoneNumberなど、公開して差し支えない情報から機密性の高い情報まで何も区別されず突っ込まれているとします(そこまで酷いケースは稀だと思いますが説明の簡単のため...)

そしてUser一覧画面では、そんなUserテーブルに対してSELECT name, imageUrl FROM Userみたいなクエリで名前とプロフィール画像を取得しているとしましょう。

ある日「追加でageも表示しなきゃいけないから、実装しておいて。急ぎで!」と言われたエンジニアがトコトコやってきてクエリをこんなふうに書き換えました:

SELECT * FROM User

「とりあえずUserテーブルのデータを全部APIから返しておいて、後はフロントエンドで必要な情報だけ取れば良いんでしょ。ほれageっと...」

重大インシデントの発生です。 登録している全Userの秘匿情報が公開されてしまうガバガバAPIが生まれたことで眠れない炎上対応デイズの幕が開きます。

対策

そもそも機密性の高いカラムを一般ユーザ用のアプリケーションが参照する必要がないケースは多々あります。例えばマッチングアプリには会員が住所や免許証を登録する必要がありますが、これは異性に見せるためではなく運営会社が管理画面で審査を行うためです。参照しなければいけないのは管理用アプリケーションだけで、一般ユーザ用のアプリケーションがそれらを参照する必要はありません。

こんな時は公開情報と機密性の高い情報を別のテーブルに分割する方法が使えます。例えばUserPublicInfoテーブルとUserConfidentialInfoテーブルに分割して、UserConfidentialInfoに対してはSELECTする権限を一般ユーザ用アプリケーションに与えなければ間違って機密性の高い情報を返す心配はありません。前述のエンジニアが同じミスを繰り返したとしても情報が漏洩するのは管理画面を使っている社内関係者に限定されます。

ビューではなくテーブル分割でも事足りるかもしれませんが、2つのテーブル間で一部の情報を共有する必要が生じたらテーブルを分割するよりビューを作成するほうが便利かもしれません。

仮に先ほどのマッチングアプリに

「UserConfidentialInfoに格納されたaddressのうち先頭5文字だけは異性ユーザに表示したい」

みたいなユースケースが生まれたとします(住所を文字列として保存することは滅多にないので例としてはちょっとイマイチなのですが...もっと良い例が思い付いたら更新しておきます)

テーブルを分割しているとUserが住所を更新した時にUserConfidentialInfoとUserPublicInfoテーブルを両方更新する必要がありますが、ビューなら更新するのはUserConfidentialInfoだけで事足ります。UserPublicInfoビューはUserConfidentialInfoテーブルの情報から導出しているだけですから

これぐらい簡単な例だとテーブル分割とビューの違いはイマイチ実感しづらいかもしれませんが、Single Source of Truthを維持しておかないとデータの不一致が生じたときにどちらが真実か判断できず困るので、可能な限り事実は一箇所にまとめるに越したことはありません。

また、最初からテーブルを分割していればよいのですが、後から「やっぱりテーブル分割しよう」となると既存テーブルを二つのテーブルに分割してデータを入れ直す(UserConfidential -> UserPublic)作業が発生します。「やっぱり切り戻そう」と考えた時も同様ですね。サービスの稼働停止時間、それに伴う対策時間が発生しそうです。

一方ビューならデータの入れ直しは不要です。追加したらすぐ使い始められるので、後から対策を施すのであればビューの方が手軽な事が多いイメージを持っています。

悪意を持った開発者

その会社に所属するエンジニアなら誰でもUserテーブルに対してSELECTを実行できるようになっていると、悪意を持ったエンジニアが一人でも入社したら全ての情報を取得できます。最も単純な対策としてはUserテーブルに対するSELECT権限を一部のエンジニアにしか与えなければ良いのですが、これだとUserテーブルを調査する必要が生じた時に一部のエンジニアしか対応にあたれない不便を強いられます。良い折衷案が欲しいところですね。

対策

元々のテーブルをUserビューとUserCredentialInfoテーブルに分割した方法がここでも役立ちます。UserCredentialInfoテーブルに対するSELECT権限はマネージャー以上に限定する、みたいな運用方法ですね。

UserCredentialInfoに起因する障害だったら結局マネージャーを駆り出す必要があるので万能ではありませんが、全てのエンジニアがあらゆるテーブルのデータを見れるガバガバ状態、あるいは何を調査するにしても許可やマネージャーの立ち合いが必要になるガチガチ状態の折衷案としてビューを見かける事が多いイメージです。

エイリアスとして

めちゃくちゃ不雑なJOINを書いたことはありませんか。大変ですよね。もっと大変なのはそれを読む人です。「え、このJOINは何と何を結合してんの!?」と混乱します。

対策

めちゃくちゃ複雑、かつ使い回すことの多いクエリならVIEWとして定義することで意味が伝わりやすくなります。

RIGHT OUTER JOIN (SELECT * FROM Products WHERE Products.createdAt < (SELECT shippmentDate FROM Shippments JOIN (SELECT id FROM ShippingCompanies LIMIT CASE WHERE WHILE IF FOR UPDATE OH NO HELP ME.... ))

みたいなクエリをウンウン唸りながら読み解いていくより

JOIN Sales2021.productId

と書かれていた方が、全く会計の知識がないエンジニアがビューの中身すら見なくても「あぁ2021年度に売上計上された商品のidとJOINしてるのか」と読み解けそうですよね(発生主義とか現金主義とか、会計上同年度として扱われる売上の判定ルールは結構複雑なのでクエリも複雑になりやすい)

クエリの複雑性が認識されることなく実行されるデメリットもあるので注意が必要ですが、自分はデータベースのスキーマを見ればアプリケーションの意図や挙動が想像しやすい方が好きなので、結構使います。

以前Private methodを使うことでコードレビューを楽にする記事を書いた時も「抽象度を上げた名前を処理につけることで可読性が向上する」ということを伝えたのですが、それと同じようなことをデータベースで実施するイメージです。

(蛇足)
ちなみに前職時代に負債だらけのサービスを企画職として担当した際、エンジニアからJOINやサブクエリをこれでもかと詰め込んだ300行近いクエリを(しかもセキュリティの関係?でA3用紙にプリントされた状態で)渡されて「レビューお願いします!」と依頼されたことがあります。千年前の遺跡に残された文字を解読する方がまだ簡単だったと思います。1週間ぐらい考えるフリをしてから「LGTM」とだけ返しました。 エンジニアは「動いてよかったです!」って言ってたけど、正直あのクエリが動いたように見えることの方が怖い。

リファクタリングのツールとして

これは偏見ですが、コードが汚いアプリケーションは大体データベースも汚いです。DBだけ綺麗でアプリケーションが汚いケースは見た事がありません。なので大体データベースもセットでリファクタリングしなければいけないのですが、データベースに触れるほど怖い作業はありませんよね。コードのリファクタリングと一緒にDBの設計も変更したら「誰も存在を認識していなかったアプリケーションが動かなくなった」みたいなケースも多々あります。

対策

仮に元のアプリケーションコードが「Data」と呼ばれる何でもアリなばっちぃテーブルを参照していたら、DataからProductView、OrderView、ShipmentViewなどを切り出して、リファクタリングしたコードは元のDataテーブルではなく分割されたViewを参照するようにします。 元のDataテーブルは一切手を加えていないので、仕様書には残されていない野良アプリケーションがDataテーブルを参照していたとしても壊す心配はありません。

あとは本番環境でしばらく稼働させて問題がないことを確認しつつ、Dataテーブルを参照しているアプリケーションが完璧に洗い出せた自信が持てたタイミングでProduct、Order、Shippmentビューをテーブルに分割すれば良いわけです。もし必要であれば。

コードもデータベースも併せて一気に変えるのは辛い時は、最初から不可逆な変更を加えるのではなく、ビューという脱着容易な機構を用いて段階的にリファクタリングを進めると、少しアプローチしやすくなるのでオススメです。

検索の代替として

複数のテーブルを横断して検索しつつページネーションを実装したい時Elasticsearchのような全文検索機構を用いる事がありますが、仮説検証中は開発した機能をすぐに削除することも多いので、そこまでの手間をかけたくない事があります。

対策

複数のテーブルから検索対象のカラムを文字列に変換してビューのカラムにまとめる事があります。例えばテーブルA,B,CのカラムをそれぞれconcatしてSearchビューのbodyカラムなどにまとめておけば、Searchビューのbodyカラムに対してLIKE検索をすることで比較的簡単に横断検索機能を実装できます。

ただ、当然LIKE検索なので性能は期待できません。恒久的な機能であれば適切な実装方法ではありませんが「ちょっとユーザー向けに検索機能をリリースして反響みたいな」なんて時には重宝します。

まとめ

総括すると、自分が実際に実務で直面したビューの用途としては

  • セキュリティの一環として
  • エイリアスとして
  • リファクタリングのツールとして
  • 簡易的な検索として

が挙げられますが、他にも色々ありそうなのでお気づきの方がいたら教えてくださいませ!

PrAha

Discussion