Skip to content

Field Turunan Saya Basi Lagi, Tiga Hari Setelah Di-Backfill

Adityo Guni Waluyo

Commit backfill next_review basi lagi tiga hari kemudian. Solusinya: field turunan dihitung saat dibaca, nilai manual tetap dipertahankan.

Ringkasan

Gue ngabisin waktu backfill manual field next_review, eh besoknya datanya basi cuma gara-gara satu berkas baru. Ternyata itu field turunan yang bisa dihitung sendiri dari last_updated plus 90 hari, jadi mending derive pas dibaca, gak perlu disimpen. SQLite sama PostgreSQL aja udah setuju lewat generated column dan view; nyimpen cuma worth it kalau ada override manual atau bacaannya berat banget.

Pagi itu saya jalankan scan indeks riset. Satu perintah, output JSON berisi status tiap topik. Di situ ada field next_review, tanggal yang menandai kapan sebuah arsip riset layak dibuka lagi. Tiga hari sebelumnya saya habis mem-backfill field itu secara manual untuk 13 topik lama. Senang rasanya melihat data rapi. Semua nilai itu muncul dari satu skrip scan yang membaca folder riset lalu menulis ulang indeks.

Ternyata topik yang baru selesai riset punya next_review kosong. Data yang barusan saya rapikan langsung basi, hanya karena ada satu berkas baru masuk indeks.

Backfill Itu Bukan Solusi, Cuma Pelaporan

Dua commit lalu saya sempat yakin backfill adalah solusi tuntas. Commit 3b9d8fb menambahkan next_review = tanggal riset + 90 hari untuk 13 topik lama. Mandul, iya. Tapi begitu ada satu berkas riset baru, 27 baris data itu kedaluwarsa sebagian.

Di situlah saya sadar soal yang keliru bukan datanya, tapi keputusan menyimpannya. next_review itu field turunan: nilainya bisa dihitung dari last_updated plus 90 hari. Nyimpen hasil perhitungan yang bisa berubah itu nambah satu kelas pekerjaan rumah baru. Setiap kali sumber berubah, data turunannya harus di-sync lagi. Derive aja saat dibaca, satu kelas pekerjaan itu langsung hilang.

Solusinya ada di commit 93b12a2. Fungsi scan() di skrip indeks riset sekarang menghitung next_review sendiri kalo nilainya kosong.

Nilai manual yang pernah saya tulis tetep dipertahankan, karena logikanya cuma ngisi kalo kosong. Selftest menutup dua jalur itu: field manual nggak ketimpa, field kosong keisi otomatis.

Database Udah Memilih Hal yang Sama Lebih Dulu

Pola ini bukan ciptaan saya. SQLite punya generated column, kolom yang isinya fungsi dari kolom lain di baris yang sama [1]. Nilainya bisa dibaca tapi nggak bisa ditulis langsung. Ada dua mode: VIRTUAL dihitung saat dibaca, STORED dihitung saat baris ditulis. STORED makan ruang di file database, VIRTUAL makan sedikit siklus CPU saat membaca.

PostgreSQL menyebut generated column sebagai "apa arti kolom bagi tabel" [2]. Versi virtualnya mirip view biasa, versi storednya mirip materialized view. Dan view PostgreSQL nggak pernah nyimpen hasilnya: query-nya dijalankan setiap kali view itu direferensikan [3]. Database modern udah bilang lebih dulu: turunkan saat dibaca, simpan hanya kalo ada alasan.

Satu pengecualian yang perlu dibedakan: column default. Nilai default dievaluasi sekali saat insert dan nggak boleh merujuk kolom lain [2]. Jadi default bukan field turunan. Ia nilai awal, bukan cerminan kolom lain yang harus terus sinkron.

Kapan Menyimpan Tetap Masuk Akal

Ada dua situasi yang membuat penyimpanan tetap masuk akal, dan keduanya jujur soal trade-off-nya.

Pertama, override manual. Di indeks riset saya, sebagian topik emang sengaja ditandai biar review-nya lebih cepat atau lebih lambat dari formula 90 hari. Makanya kondisi "isi cuma kalo kosong" penting: formula mengisi kekosongan, keputusan manusia yang menimpa formula. Ini model yang saya pakai.

Kedua, kinerja baca yang padat. Generated column VIRTUAL dihitung ulang tiap kali dibaca [1]. Untuk ekspresi yang berat dan sering dibaca, itu buang-buang CPU. SQLite tetap mengizinkan generated column ikut di-index: yang memakai kolom STORED jadi index biasa, yang memakai VIRTUAL jadi expression index. Jadi kalau pembacaannya super sering dan ekspresinya mahal, menyimpan hasil di STORED lalu meng-index-nya adalah keputusan teknis yang sah, bukan pelanggaran pola.

Yang saya hindari justru zona abu-abu: nyimpen field turunan tanpa override dan tanpa alasan kinerja, cuma karena keliatan lebih gampang saat itu. Di situ biaya sinkronisasi menunggu di kemudian hari.

Jadi kalo nanti kamu nemu field yang nilainya selalu bisa dihitung dari kolom lain, tahan dulu sebelum nambahin kolom penyimpannya. Tulis satu fungsi derive di scan atau pembaca datanya. Sisain penyimpanan cuma buat nilai manual atau beban baca yang benar-benar kerasa.

Sumber

  1. SQLite, "Generated Columns" — sqlite.org, diakses 2026-09-17
  2. PostgreSQL, "Dokumentasi: 5.4. Generated Columns" — postgresql.org, diakses 2026-09-17
  3. PostgreSQL, "Dokumentasi: CREATE VIEW" — postgresql.org, diakses 2026-09-17

Artikel terkait