Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】How to Ghost Your Data: Implementing Soft Deletes in Prisma

2025年09月18日に「Dev.to」が公開したITニュース「How to Ghost Your Data: Implementing Soft Deletes in Prisma」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

Prismaでソフトデリートを実装する方法を解説。データを完全に削除せず、削除済みの目印を付けてデータベースに残すことで、誤削除からの復元や監査に役立つ。Prismaの機能で自動的に非表示化し、関連データやユニーク制約、性能、集計時の注意点も学ぶ。

ITニュース解説

システムエンジニアがアプリケーション開発において、データベース上のデータを完全に削除せず、論理的に削除済みとして扱う「ソフトデリート」という手法は、誤ってデータを削除してしまったユーザーが後から簡単に復元できるようにするために用いられる。また、誰が、いつ、どのデータを削除したかという「監査証跡」を残す目的や、関連するデータが突然消失することで発生するエラーを防ぎ、データの整合性を維持するためにも重要となる。

ソフトデリートの基本的な仕組みは、データベースの各テーブルに「deleted_at」のような特別なカラムを追加することから始まる。このカラムは、そのレコードが論理的に削除された日時を記録するために使用され、通常は空(null)に設定される。ユーザーがアプリケーションを通じてデータを削除する指示を出すと、データベースから物理的にレコードを削除するのではなく、この「deleted_at」カラムに現在のタイムスタンプを記録する。これにより、データはデータベースに残り続けるが、アプリケーションは通常、このカラムが空でないレコードを無視し、ユーザーには見えないようにフィルタリングする。

PrismaのようなORM(Object-Relational Mapper)を使用する場合、このソフトデリートの仕組みを効果的に実装できる。具体的には、Prismaのスキーマファイル(schema.prisma)で、対象となるモデル(例えばUserやPost)にdeleted_at DateTime? @db.Timestamptzというカラムを追加する。このカラムは、タイムスタンプ型であり、nullを許容する設定だ。

次に、Prismaクライアントの「拡張機能」($extends)を活用して、ソフトデリートのロジックをPrismaの標準的な操作に組み込む。これにより、開発者が個別に削除済みチェックのコードを書く手間を省き、ソフトデリートをアプリケーション全体のデフォルトの挙動にできる。拡張機能では、findUniquefindFirstfindManyといったデータ取得系のクエリが実行される前に、自動的にdeleted_atカラムがnullであるレコードのみを対象とする条件を追加する。例えば、queryArgs.where = { ...queryArgs.where, deleted_at: null };といった形で、既存の検索条件に自動的に「削除されていない」という条件を付加する。

また、delete操作もこの拡張機能で上書きされる。通常のdeleteメソッドが呼ばれた場合でも、データベースから物理的にレコードを削除するのではなく、そのレコードのdeleted_atカラムに現在時刻を更新する処理に置き換わる。これにより、アプリケーションから削除指示が出ても、データは安全にデータベースに残される。本当にデータを物理的に削除したい場合は、別途hardDeleteのようなカスタムメソッドを用意し、それを明示的に呼び出す仕組みを設ける。

ソフトデリートされたデータを元に戻す「復元」機能も重要だ。これもカスタムメソッドとして実装され、対象レコードのdeleted_atカラムを再びnullに戻すことで実現する。特定の管理画面などでは、ソフトデリートされたレコードも含めてデータを検索したい場合もあるため、findFirstWithDeletedのようなヘルパーメソッドを拡張機能に追加することで、柔軟なデータアクセスが可能になる。

ソフトデリートを実装する上で、最も複雑になるのが「関連データ」の扱いだ。例えば、ユーザーをソフトデリートする場合、そのユーザーが作成した投稿も同時にソフトデリートする必要がある。さらに、ユーザーを復元する際には、そのユーザーに関連付けられていた投稿も復元すべきか、または特定の条件によっては復元すべきではない、といった判断が必要になる。このような関連データの削除・復元処理には、Prismaの「トランザクション」を利用することが推奨される。トランザクションを用いることで、一連のデータベース操作(ユーザーのソフトデリートと関連投稿のソフトデリートなど)を一つのまとまりとして扱い、すべてが成功するか、あるいはすべてが失敗して変更がロールバックされるかを保証し、データの整合性を保つことができる。

トランザクションを利用する際の注意点として、トランザクション内で使用するPrismaクライアントオブジェクトは、前述の拡張機能で追加したカスタムメソッドを持たないという点がある。そのため、トランザクション内のロジックは、拡張機能に依存しない形で記述する必要がある。また、トランザクション処理中にグローバルのPrismaクライアントオブジェクトを使用すると、そのクエリがトランザクションの範囲外で実行されてしまうため、必ずトランザクション内で提供されるクライアントオブジェクトを使うべきだ。

もう一つ重要なのが、deleted_atカラムの管理だ。このカラムは、ソフトデリートの意図しない挙動を防ぐため、アプリケーションコードから直接createupdate操作で触れるべきではない。deleted_atは、Prismaの拡張機能内で定義されたソフトデリート関連のメソッド(例えば、deleteのオーバーライドやrestore)のみを通じて更新されるように厳しく管理することが推奨される。

データベースの「ユニーク制約」もソフトデリートにおいて考慮すべき点だ。例えば、ユーザーのメールアドレスにユニーク制約が設定されている場合、あるユーザーがソフトデリートされたとしても、そのメールアドレスはデータベース上には存在し続けているため、同じメールアドレスで新しいユーザーを登録しようとするとユニーク制約違反のエラーが発生する。これに対する解決策としては、データベースが部分ユニークインデックスをサポートしている場合、deleted_atがnullのレコードにのみユニーク制約を適用する方法がある。あるいは、ソフトデリート時にメールアドレスを一時的に変更する方法や、アプリケーションレベルでソフトデリートされたレコードを考慮して重複チェックを行う方法も考えられる。

パフォーマンス面では、deleted_atカラムにデータベースインデックスを追加することが非常に重要だ。データ量が増加すると、deleted_at IS NULLという条件でデータをフィルタリングするクエリの処理速度が低下する可能性がある。インデックスを設定することで、データベースは全レコードをスキャンすることなく、効率的にアクティブなレコード(deleted_atがnullのレコード)を検索できるようになり、クエリの高速化とデータベースサーバーへの負荷軽減につながる。また、複数の条件で絞り込むことが多い場合は、authorIddeleted_atのような複合インデックスも有効となる。

さらに、countaggregategroupByといった集計・分析系のクエリを扱う際には特に注意が必要だ。これらのクエリはデフォルトではdeleted_atカラムのフィルタリングを自動で行わないため、ソフトデリートされたレコードも含めて集計してしまう。例えば、アクティブなユーザーの総数を取得したい場合でも、明示的にwhere: { deleted_at: null }という条件を追加しないと、ソフトデリートされたユーザーもカウントに含まれてしまう。正確な分析結果を得るためには、常に削除済みレコードを除外する条件を含めるか、Prismaの拡張機能でこれらの集計クエリにも自動フィルタリングを適用する仕組みを実装することが求められる。

結論として、ソフトデリートは誤操作からのデータ保護、監査証跡の確保、関連データ整合性の維持に有効な手段だが、その実装にはデータベーススキーマの設計、Prismaクライアント拡張によるクエリのカスタマイズ、関連データ処理のためのトランザクション管理、ユニーク制約への対応、パフォーマンス向上のためのインデックス設定、そして集計クエリでの明示的なフィルタリングといった多岐にわたる考慮と工夫が必要となる。これらのベストプラクティスを守ることで、データの安全性を高めつつ、アプリケーションの安定した運用を実現できる。

関連コンテンツ

関連IT用語