Soft delete vs hard delete: pahami tradeoff nyata untuk analytics, support, permintaan penghapusan ala GDPR, kompleksitas query, dan pola restore yang aman.

Tombol hapus bisa berarti dua hal sangat berbeda di basis data.
Hard delete menghapus baris. Setelah itu, record hilang kecuali Anda punya backup, log, atau replika yang masih menyimpannya. Sederhana untuk dipahami, tetapi bersifat final.
Soft delete menyimpan baris tetapi menandainya sebagai dihapus, biasanya dengan field seperti deleted_at atau is_deleted. Aplikasi lalu memperlakukan baris bertanda sebagai tidak terlihat. Anda mempertahankan data terkait, merekam riwayat, dan kadang bisa mengembalikan record.
Pilihan ini muncul dalam pekerjaan sehari-hari lebih sering daripada yang orang kira. Ia memengaruhi bagaimana Anda menjawab pertanyaan seperti: “Kenapa pendapatan turun bulan lalu?”, “Bisakah kembalikan proyek yang saya hapus?”, atau “Kami mendapat permintaan penghapusan GDPR — apakah data pribadi benar-benar terhapus?” Juga membentuk arti “dihapus” di UI. Pengguna sering berharap bisa membatalkan sampai mereka tidak bisa lagi.
Aturan praktis singkat:
Contoh: seorang pelanggan menghapus workspace lalu menyadari ada faktur yang diperlukan untuk akuntansi. Dengan soft delete, support dapat merestore (jika aplikasi Anda dirancang untuk restore yang aman). Dengan hard delete, Anda kemungkinan terjebak menjelaskan backup, keterlambatan, atau “tidak bisa.”
Tidak ada pendekatan yang selalu “terbaik.” Pilihan paling sedikit menyakitkan bergantung pada apa yang perlu Anda lindungi: kepercayaan pengguna, akurasi pelaporan, atau kepatuhan privasi.
Pilihan penghapusan cepat terlihat di analytics. Pada hari Anda mulai melacak pengguna aktif, konversi, atau pendapatan, “dihapus” berhenti menjadi status sederhana dan menjadi keputusan pelaporan.
Jika Anda hard delete, banyak metrik tampak bersih karena record yang dihapus hilang dari query. Tapi Anda juga kehilangan konteks: langganan masa lalu, ukuran tim sebelumnya, atau seperti apa funnel bulan lalu. Pelanggan yang dihapus bisa membuat grafik historis berubah saat Anda menjalankan ulang laporan, yang menakutkan untuk tinjauan keuangan dan growth.
Jika Anda soft delete, Anda menjaga riwayat, tetapi bisa secara tidak sengaja membengkakkan angka. Sederhana “COUNT users” mungkin termasuk orang yang sudah pergi. Grafik churn bisa menghitung ganda jika Anda menganggap deleted_at sebagai churn di satu laporan dan mengabaikannya di laporan lain. Bahkan pendapatan bisa berantakan jika faktur tetap ada tetapi akun ditandai dihapus.
Yang cenderung bekerja adalah memilih pola pelaporan yang konsisten dan menaatinya:
Kuncinya adalah dokumentasi agar analis tidak menebak. Tuliskan apa arti “aktif”, apakah pengguna soft-deleted disertakan, dan bagaimana pendapatan diatribusi jika akun kemudian dihapus.
Contoh konkret: sebuah workspace dihapus oleh kesalahan, lalu dipulihkan. Jika dashboard Anda menghitung workspace tanpa filter, Anda akan menunjukkan penurunan dan pemulihan mendadak yang sebenarnya tidak terjadi dalam penggunaan riil. Dengan snapshot, grafik historis tetap stabil sementara tampilan produk masih bisa menyembunyikan workspace yang dihapus.
Sebagian besar tiket support terkait penghapusan terdengar sama: “Saya menghapusnya karena keliru,” atau “Ke mana record saya?” Strategi hapus Anda menentukan apakah support bisa menjawab dalam hitungan menit, atau apakah jawaban jujur satu-satunya adalah “Sudah hilang.”
Dengan soft delete, Anda biasanya bisa memverifikasi apa yang terjadi dan membatalkannya. Dengan hard delete, support sering harus bergantung pada backup (jika ada), dan itu bisa lambat, tidak lengkap, atau mustahil untuk satu item. Karena itu pilihan ini bukan hanya detail basis data. Ia membentuk seberapa “membantu” produk Anda setelah sesuatu salah.
Jika Anda mengharapkan support nyata, tambahkan beberapa field yang menjelaskan event penghapusan:
deleted_at (timestamp)deleted_by (user id atau sistem)delete_reason (opsional, teks singkat)deleted_from_ip atau deleted_from_device (opsional)restored_at dan restored_by (jika Anda mendukung restore)Bahkan tanpa log aktivitas penuh, detail ini membantu support menjawab: siapa yang menghapus, kapan terjadi, dan apakah itu kecelakaan atau pembersihan otomatis.
Hard delete bisa baik untuk data sementara, tetapi untuk record yang terlihat pengguna mereka mengubah apa yang bisa dilakukan support.
Support tidak bisa merestore satu record kecuali Anda membangun recycle bin di tempat lain. Mereka mungkin perlu restore backup penuh, yang bisa memengaruhi data lain. Mereka juga tidak mudah membuktikan apa yang terjadi, yang menyebabkan bolak-balik panjang.
Fitur restore mengubah beban kerja juga. Jika pengguna bisa self-restore dalam jendela waktu, tiket berkurang. Jika restore membutuhkan support melakukan secara manual, tiket mungkin meningkat, tetapi menjadi cepat dan berulang alih-alih investigasi satu-satu.
“Right to be forgotten” biasanya berarti Anda harus berhenti memproses data seseorang dan menghapusnya dari tempat-tempat di mana data itu masih bisa digunakan. Itu tidak selalu berarti Anda harus menghapus setiap agregat historis segera, tetapi itu berarti Anda tidak boleh menyimpan data yang bisa diidentifikasi “hanya untuk berjaga-jaga” jika Anda tidak lagi punya alasan hukum untuk menyimpannya.
Di sinilah soft delete vs hard delete menjadi lebih dari sekadar pilihan produk. Soft delete (mis. men-set deleted_at) sering hanya menyembunyikan record dari aplikasi. Data masih ada di basis data, masih bisa di-query oleh admin, dan sering masih muncul di ekspor, indeks pencarian, dan tabel analytics. Untuk banyak permintaan penghapusan GDPR, itu bukan penghapusan.
Anda masih perlu purge ketika:
Backup dan log adalah bagian yang sering terlupakan. Anda mungkin tidak bisa menghapus satu baris dari backup terenkripsi, tetapi Anda bisa menetapkan aturan: backup kadaluwarsa cepat, dan backup yang dipulihkan harus menerapkan ulang event penghapusan sebelum sistem live. Log harus menghindari menyimpan data pribadi mentah bila memungkinkan, dan punya batas retensi yang jelas.
Kebijakan sederhana dan praktis adalah penghapusan dua langkah:
Jika platform Anda mendukung ekspor kode sumber atau ekspor data, perlakukan file yang diekspor sebagai penyimpanan data juga: tentukan di mana mereka berada, siapa yang dapat mengaksesnya, dan kapan mereka dihapus.
Soft delete terdengar sederhana: tambahkan deleted_at (atau is_deleted) dan sembunyikan baris. Biaya tersembunyi adalah setiap tempat Anda membaca data sekarang harus mengingat flag itu. Terlewat sekali, dan Anda mendapat bug aneh: total menyertakan item yang dihapus, pencarian menampilkan hasil “hantu”, atau pengguna melihat sesuatu yang mereka kira sudah hilang.
Edge case UI dan UX muncul cepat. Bayangkan sebuah tim menghapus proyek bernama “Roadmap” lalu mencoba membuat “Roadmap” baru. Jika basis data Anda punya aturan unik pada nama, pembuatan bisa gagal karena baris yang dihapus masih ada. Pencarian juga bisa membingungkan: jika Anda menyembunyikan item di list tapi tidak di pencarian global, pengguna akan menganggap aplikasi Anda rusak.
Filter soft delete sering terlupa di:
Performa biasanya baik pada awalnya, tetapi kondisi ekstra menambah pekerjaan. Jika sebagian besar baris aktif, memfilter deleted_at IS NULL murah. Jika banyak baris dihapus, basis data harus melewati lebih banyak baris kecuali Anda menambahkan index yang tepat. Dalam kata sederhana: ini seperti mencari dokumen saat ini di laci yang juga berisi banyak dokumen lama.
Area “Arsip” terpisah dapat mengurangi kebingungan. Buat tampilan default hanya menunjukkan record aktif, dan tempatkan item yang dihapus di satu lokasi dengan label jelas dan jendela waktu. Dalam alat yang dibuat cepat (mis. aplikasi internal yang dibuat di Koder.ai), keputusan produk ini sering mencegah lebih banyak tiket support daripada trik query apa pun.
Soft delete bukan satu fitur tunggal. Ini pilihan model data, dan model yang Anda pilih akan membentuk semua yang mengikuti: aturan query, perilaku restore, dan apa arti “dihapus” bagi produk Anda.
deleted_at plus deleted_byPola paling umum adalah timestamp nullable. Saat record dihapus, set deleted_at (dan sering deleted_by ke id pengguna). “Aktif” adalah record yang deleted_at-nya null.
Ini bekerja baik saat Anda butuh restore bersih: restore cukup menghapus deleted_at dan deleted_by. Ini juga memberi sinyal audit sederhana untuk support.
Alih-alih timestamp, beberapa tim memakai field status dengan state jelas seperti active, archived, dan deleted. Ini berguna ketika “archived” adalah status produk yang nyata (tersembunyi dari sebagian besar layar tetapi masih dihitung untuk penagihan, misalnya).
Biayanya adalah aturan. Anda harus mendefinisikan arti tiap state di mana-mana: pencarian, notifikasi, ekspor, dan analytics.
Untuk objek sensitif atau bernilai tinggi, Anda bisa memindahkan baris yang dihapus ke tabel terpisah, atau merekam event di log append-only.
deleted_at, deleted_bystatus dengan state bernamaIni sering dipakai saat restore harus sangat dikontrol, atau saat Anda ingin jejak audit tanpa mencampur data yang dihapus ke query sehari-hari.
Catatan: child record juga butuh aturan jelas. Jika sebuah workspace dihapus, apa yang terjadi pada proyek, file, dan keanggotaan?
archived (bukan dihapus)Pilih satu aturan per relasi, tuliskan, dan jaga konsistensi. Sebagian besar bug “restore salah” muncul karena parent dan child memakai arti deleted yang berbeda.
Tombol restore terdengar sederhana, tetapi bisa diam-diam merusak izin, menghidupkan kembali data lama ke tempat yang salah, atau membingungkan pengguna jika “restored” bukan yang mereka harapkan. Mulailah dengan menuliskan janji tepat yang dibuat produk Anda.
Gunakan urutan kecil dan ketat supaya restore dapat diprediksi dan diaudit.
Jika Anda membangun aplikasi cepat di tool chat-driven seperti Koder.ai, jadikan pengecekan ini bagian dari workflow yang dihasilkan supaya setiap layar dan endpoint mengikuti aturan yang sama.
Kesulitan terbesar dengan soft delete bukan pada penghapusan itu sendiri, tetapi semua tempat yang lupa bahwa record “hilang.” Banyak tim memilih soft delete demi keamanan, lalu secara tidak sengaja menampilkan item yang dihapus di hasil pencarian, badge, atau total. Pengguna cepat menyadarinya ketika dashboard menulis “12 proyek” tetapi hanya muncul 11 di daftar.
Kedua adalah kontrol akses. Jika pengguna, tim, atau workspace di-soft-delete, mereka seharusnya tidak bisa login, memanggil API, atau menerima notifikasi. Ini sering terlewat saat cek login mencari berdasarkan email, menemukan baris, dan tidak memeriksa flag deleted.
Jebakan umum yang membuat tiket support:
Tabrakan unik sangat menyebalkan saat restore. Jika seseorang membuat akun baru dengan email yang sama sementara akun lama di-soft-delete, restore bisa gagal atau menimpa identitas yang salah. Tentukan aturan dari awal: blokir penggunaan ulang sampai purge, izinkan penggunaan ulang tapi larang restore, atau restore dengan identifier baru.
Satu skenario umum: agen support merestore workspace yang di-soft-delete. Workspace kembali, tetapi anggotanya tetap dihapus, dan integrasi mulai menyinkron ulang record lama ke alat mitra. Dari sudut pandang pengguna, restore “setengah berhasil” dan membuat kekacauan baru.
Sebelum merilis fitur restore, buat perilaku-perilaku ini eksplisit:
Tim B2B SaaS punya tombol “Delete workspace”. Suatu Jumat, seorang admin melakukan pembersihan dan menghapus 40 workspace yang tampak tidak aktif. Senin, tiga pelanggan mengeluh proyek mereka hilang dan meminta restore segera.
Tim mengira keputusan sederhana. Ternyata tidak.
Masalah pertama: support tidak bisa merestore apa yang benar-benar dihapus. Jika baris workspace di-hard-delete dan cascade menghapus proyek, file, dan keanggotaan, satu-satunya opsi adalah backup. Itu berarti waktu, risiko, dan jawaban canggung ke pelanggan.
Masalah kedua: analytics terlihat rusak. Dashboard menghitung “active workspaces” dengan query deleted_at IS NULL. Penghapusan tidak sengaja membuat grafik menunjukkan penurunan tiba-tiba. Lebih parah, laporan mingguan membandingkan ke minggu lalu dan menandai lonjakan churn palsu. Data tidak hilang, tetapi dikecualikan di tempat yang salah.
Masalah ketiga: permintaan privasi datang untuk salah satu pengguna yang terdampak. Mereka meminta penghapusan data pribadi. Soft delete murni tidak memadai. Tim butuh rencana untuk mem-purge field pribadi (nama, email, log IP) sambil mempertahankan agregat non-pribadi seperti total penagihan dan nomor faktur.
Masalah keempat: semua bertanya, “Siapa yang klik delete?” Jika tidak ada jejak, support tidak bisa menjelaskan apa yang terjadi.
Pola yang lebih aman adalah memperlakukan penghapusan sebagai event dengan metadata jelas:
deleted_by, deleted_at, dan alasan atau id tiketIni jenis workflow yang sering dibangun cepat di platform seperti Koder.ai, lalu tim sadar bahwa kebijakan hapus perlu didesain sama pentingnya dengan fitur di sekitarnya.
Memilih antara soft delete vs hard delete lebih soal apa yang aplikasi Anda harus jamin setelah record “pergi” daripada preferensi. Tanyakan ini sebelum menulis satu query pun.
Cara sederhana untuk memeriksa keputusan adalah pilih satu insiden realistis dan jalankan skenario. Contoh: seseorang menghapus workspace karena keliru Jumat malam. Senin, support perlu melihat event penghapusan, mengembalikannya dengan aman, dan menghindari membangkitkan kembali data terkait yang harus tetap dihapus. Jika Anda membangun aplikasi di platform seperti Koder.ai, definisikan aturan ini sejak awal supaya backend dan UI yang digenerasi mengikuti satu kebijakan alih-alih menyebarkan pengecualian di seluruh kode.
Pilih pendekatan dengan menulis kebijakan sederhana yang bisa dibagikan dengan tim dan support. Jika tidak tertulis, kebijakan akan berubah, dan pengguna akan merasakan inkonsistensi.
Mulai dengan seperangkat aturan jelas:
Lalu bangun dua jalur yang jelas yang tidak bercampur: jalur “admin restore” untuk kesalahan, dan jalur “privacy purge” untuk penghapusan nyata. Jalur restore harus reversible dan tercatat. Jalur purge bersifat final dan menghapus atau meng-anonimkan semua data terkait yang dapat mengidentifikasi orang, termasuk backup atau ekspor jika kebijakan Anda mengharuskannya.
Tambahkan guardrail supaya data yang dihapus tidak bocor kembali ke produk. Cara termudah adalah memperlakukan “dihapus” sebagai state kelas satu dalam pengujian Anda. Tambahkan checkpoint review untuk setiap query, halaman daftar, pencarian, ekspor, dan job analytics. Aturan praktis: jika layar menampilkan data yang terlihat pengguna, harus ada keputusan eksplisit tentang record yang dihapus (sembunyikan, tampilkan dengan label, atau hanya untuk admin).
Jika Anda berada di tahap awal produk, buat prototipe kedua alur sebelum mengunci skema. Di Koder.ai, Anda dapat menggambarkan kebijakan penghapusan di planning mode, menghasilkan CRUD dasar, dan cepat mencoba skenario restore dan purge, lalu menyesuaikan model data sebelum commit.