やっぱり論理削除は駄目だ

f:id:ryoutaku_jo:20190704013817p:plain

【結論】

・論理削除は、ソフトウェアにパフォーマンスや拡張性に問題をもたらすリスクが高い

・考えなしに実装するのではなく、論理削除が本当に必要なのか?という問いは徹底的に追求すべきである

・論理削除は、本当にこれ以外に選択肢が無いという「最終手段」として利用する

【目次】

【本題】

論理削除の実装をやめた話

先日から何度も論理削除に関する試行錯誤を記事にまとめてきましたが、最終的に論理削除は使わないという結論に至りました。

今回は、その経緯をまとめます。

論理削除とは

平たく言えば、データベースから直接データを削除せず、削除フラグで見かけ上はデータが消えている様に振る舞わせる処理です。

データベースにデータは残っているので、削除したデータを一部の権限では見える様にしたり、データの復元が容易になるというメリットがあります。

データベースから直接データを削除する処理は、物理削除と呼ばれます。

論理削除の導入を検討した理由

論理削除の導入を検討した理由は、主に以下の2点です。

・ユーザーが誤って削除操作をしても復元できる様にする

・削除後もユーザーの投稿内容などを確認できる様にする

一度物理削除してしまうと取り返しがつかないので、一先ずデータを残しておく事に決めました。

paranoiaを試す

論理削除を実装するにあたり、一から作るのはコストが掛かりすぎると考え、gemを探しました。

そうすると、GitHubの更新頻度も多く、ダウンロード実績も多いgemでparanoiaというものがあり、そちらを試す事にしました。

ryoutaku-jo.hatenablog.com

一通り実装を終えて、想定通りの動作をしてくれましたが、いくつか懸念点がありました。

ryoutaku-jo.hatenablog.com

ざっくりいうと、パフォーマンスと拡張性が損なわれてしまう可能性が高いことです。

paranoiaは、削除フラグのカラムを論理削除対象のテーブルに設けて、モデルのデフォルトスコープに、その削除フラグの有無を設定することで、データの表示を制御します。

毎回削除フラグを見るクエリが発行されることでパフォーマンスが低下する事と、関連テーブル含めて全てに削除フラグを設ける事による拡張性の低下が懸念されました。

極め付けは、公式がこのgemを新規プロジェクトで使用することを推奨していないことでした。

理由は、paranoiaActiveRecordをオーバーライドすることで論理削除を実現しているので、予期せぬ動作を起こす可能性があるというものです。

ここでparanoiaの使用は断念しました。

別テーブルに保持する方法を検討する

次の方法を模索する中で、ダウンロード実績の多いgemから探すとpaper_trailというものが見つかりました。

これは「別テーブルに削除データを保持してから、物理削除を行う」という方法で論理削除を実現していました。

このメリットは、別テーブルに削除済みのデータを移すことで、未削除のデータの検索などに影響を及ぼさない点です。

同じテーブルに削除済みのデータが残っていると、毎回そのデータを検索対象から除外する必要が出てきますが、この方法だとその必要がありません。

しかしデメリットがあり、それは関連テーブルの同時に削除する場合、関連テーブルのデータ保持が煩雑になるという点です。

例えばRailsであれば、ActiveRecordのオプションであるdependent: :destroyによって、アソシエーション先のレコードも同時削除できます。

そういった同時削除されるデータも論理削除したい場合、ただ親モデルのデータを別テーブルに移すだけでは済まなくなります。

gemに頼らず自分で実装するのであれば、以下の3点の対処方法があると考えています。

・子モデルの削除データを保持するテーブルも別途用意 → DB設計が複雑化

・親子のデータを構造化して(JSONなど)、別テーブルに保存 → 復元時にデータの整合性が取れなくなる危険性あり

・親モデルのデータ有無を、子モデルのスコープに設定する → 一番マシ?

いずれも理想とは程遠いので、この方法も断念しました。

discardを試す

そして次に検討したのは、paranoiaの中でも紹介されていたdiscardというgemです。

こちらもparanoia同様に削除フラグを設けて論理削除を実現するgemです。

paranoiaとの違いは、ActiveRecordにオーバーライドさせないという点です。

これにより予期せぬ挙動に悩まされる心配は無くなるはずなので、良い実装方法だと思ったのですが、やはり断念しました。

理由は、ActiveRecordをオーバーライドさせない故に、dependent: :destroyが設定されている関連テーブルの論理削除については対応していない点です。

だったらgem使わずに、自分で実装するのと大差ないと感じて、やめました。

そもそも論理削除が要るの?

ここあたりで煮詰まり過ぎて、CTOに相談したところ、「そもそも論理削除、要るの?」という話になりました。

間違って削除してしまった場合の保険という意味合いが強かったですが、UIを工夫すれば防げることでもあります。

(例:GItHubリポジトリ削除する際、削除対象のリポジトリ名の入力が必須)

そんなこんなで、論理削除の実装は見送りとなりました!

論理削除は要注意

論理削除については、様々な方が注意を促している内容になります。

www.slideshare.net

blog.kazuhooku.com

qiita.com

絶対悪という訳では無いでしょうが、実装する場合は、細心の注意を払う必要がある内容ですね。

《今日の学習進捗(3年以内に10000時間に向けて)》

ユーザー削除機能について、先日からかなり煮詰まっていたが、本日CTOに相談したら一瞬で解決した・・・ (論理削除ではなく、物理削除で実装する方針に変更して解決)

かなりの稼働をこのタスクに割いてしまった為、今後もより早期に相談する様にしたい。 また今後は、目の前のタスクに直ぐ取り掛かるのではなく、実際に必要とされている仕様が何かを正確に把握し、要件を固めた上で開発に取り組みたい。

現在のプロジェクトの開発という観点では時間を無駄にしたが、今回悩みまくったおかげで、論理削除の実装手法に関する知見がかなり蓄積できたので、今後の業務で活かせる場面があれば活用していきたい。

なお、物理削除で実装することで開発難易度は下がったが、運用面での課題は残る。

管理者側で削除する分には問題は出にくいだろうが、一般ユーザー側での退会処理が物理削除になると、管理者の意図しないところで以下の様な事象が発生することが想定される。

これらの問題への対処案は、いくつか考えているのでビジネスサイドと運用方法を話し合いたい。

学習開始からの期間 :208日
今日までの合計時間:2020h
一日あたりの平均学習時間:9.8h
今日までに到達すべき目標時間:1900h
目標との解離:120h
「10,000時間」まで、

残り・・・「7980時間!」