Kebijakan retensi

Penghapusan chunk berdasarkan usia, terkait rencana siklus hidup dari modul beban kerja.

Daftar isi

Kebijakan retensi pada TimescaleDB menghapus chunk berdasarkan usia, sehingga data time-series yang nilainya meluruh dapat dibersihkan dari penyimpanan utama tanpa memicu operasi DELETE baris-per-baris yang mahal. Topik ini menjelaskan retensi sebagai operasi pada level partisi waktu, kebijakan add_retention_policy yang mengotomatiskan penghapusan, keterkaitan dengan siklus hidup data dari modul beban kerja, serta konsekuensi pemilihan drop_after terhadap baris yang dihapus per iterasi. Setelah membaca topik ini, sebuah hypertable yang tumbuh tanpa batas dapat dilengkapi kebijakan retensi yang menjaga ukuran disk tetap konstan tanpa mengganggu beban ingest maupun query.

Retensi sebagai penghapusan chunk#

Pada tabel Postgres biasa, penghapusan data lama dilakukan dengan DELETE FROM ... WHERE ts < .... Operasi ini memerlukan pemindaian indeks, penulisan WAL untuk setiap baris yang dihapus, dan vacuum setelahnya untuk mengembalikan ruang yang dibebaskan. Pada tabel berisi jutaan hingga miliaran baris, operasi ini menjadi sangat mahal dan dapat memengaruhi beban concurent, sehingga tidak praktis dijalankan setiap hari. TimescaleDB memperbaiki kelemahan ini dengan memanfaatkan struktur chunk, yaitu partisi waktu yang sudah ada pada setiap hypertable. Penghapusan chunk lama hanya berarti melepaskan seluruh tabel anak dari katalog, bukan memindai baris demi baris.

Karena penghapusan terjadi pada level tabel anak, biaya penghapusan tidak bergantung pada jumlah baris di dalam chunk. Sebuah chunk berisi 10.080 baris tujuh hari telemetri menitan dapat dihapus dalam waktu yang sama dengan chunk berisi satu juta baris. Konsekuensinya, retensi harian atau mingguan dapat berjalan dalam hitungan detik, bahkan pada hypertable berisi miliaran baris. Mekanisme ini juga menjadikan retensi sebagai keputusan skema yang ditetapkan bersamaan dengan chunk_time_interval, sebab ukuran chunk menentukan resolusi retensi yang dapat dicapai.

Kebijakan retensi otomatis#

TimescaleDB menyediakan add_retention_policy untuk mengotomatiskan penghapusan chunk berdasarkan usia. Argumen utama adalah drop_after, yaitu interval waktu yang menentukan kapan sebuah chunk menjadi kandidat penghapusan. Bila drop_after diatur ke INTERVAL '30 days', maka chunk dengan range_end lebih tua dari 30 hari dari waktu sekarang akan dihapus pada iterasi berikutnya. Kebijakan ini terdaftar pada tabel timescaledb_information.jobs sebagai pekerjaan latar belakang, dan jadwalnya diatur oleh schedule_interval yang dapat pula dikustomisasi. Perintah berikut tidak dijalankan di sandbox karena memerlukan Postgres dengan TimescaleDB aktif, sehingga hanya menjadi referensi sintaksis.

postgres Kebijakan retensi 30 hari
SELECT add_retention_policy(
    'suhu_sensor',
    drop_after => INTERVAL '30 days'
);

-- daftar kebijakan retensi yang sudah terdaftar
SELECT job_id, hypertable_name, schedule_interval, config
FROM timescaledb_information.jobs
WHERE proc_name = 'policy_retention';

-- hapus chunk lama secara manual untuk rentang tertentu
SELECT drop_chunks('suhu_sensor', older_than => INTERVAL '30 days');
text Keluaran
add_retention_policy
1013
job_id|hypertable_name|schedule_interval|config
1013|suhu_sensor|1 day|{"drop_after": "30 days", "hypertable_id": 84}
drop_chunks
_timescaledb_internal._hyper_84_159_chunk
_timescaledb_internal._hyper_84_160_chunk
_timescaledb_internal._hyper_84_161_chunk
_timescaledb_internal._hyper_84_162_chunk
_timescaledb_internal._hyper_84_163_chunk

Fungsi drop_chunks dapat digunakan untuk penghapusan manual, misalnya ketika kebijakan baru saja dipasang dan data lama perlu dibersihkan seketika. Kebijakan otomatis melengkapi fungsi ini dengan menjalankan drop_chunks pada jadwal yang ditetapkan, sehingga operasi harian tidak lagi memerlukan intervensi manual.

Hubungan dengan siklus hidup data#

Pemilihan drop_after tidak boleh diambil secara spekulatif, sebab nilai ini menentukan panjang memori operasional yang dipelihara basis data. Pada dashboard pemantauan infrastruktur, retensi 30 hingga 90 hari umumnya mencukupi kebutuhan investigasi insiden, sedangkan pada audit keuangan, retensi lima tahun atau lebih sering menjadi syarat regulasi. Pemilihan drop_after sebaiknya mengikuti kebutuhan domain, bukan kapasitas disk yang tersedia, sebab retensi yang terlalu panjang karena disk masih ada ruang akan membuat indeks tumbuh tanpa hingga dan menurunkan kualitas query.

Siklus hidup data dari modul beban kerja menetapkan empat tahap, yaitu panas, hangat, dingin, dan hilang. Retensi adalah titik transisi dari tahap dingin ke tahap hilang, dan kebijakan drop_after menentukan kapan transisi itu terjadi. Tahap panas hingga dingin ditangani oleh kompresi kolumnar, sedangkan tahap hilang ditangani oleh retensi. Karena itu, kedua kebijakan sebaiknya dipasang bersamaan, dengan compress_after lebih pendek daripada drop_after, sehingga data melewati kompresi sebelum dihapus. Tanpa kompresi, retensi 30 hari pada telemetri menitan dengan 100 sensor tetap menghasilkan puluhan gigabyte per bulan, sedangkan dengan kompresi, volume yang sama dapat turun menjadi beberapa gigabyte.

Simulasi retensi harian#

Untuk memahami bagaimana retensi berbasis chunk berperilaku pada hypertable yang terus menerima data baru, skrip Python berikut memodelkan situasi tersebut. Skrip membangkitkan 90 hari telemetri yang dipartisi menjadi chunk 7 hari, lalu menjalankan iterasi retensi harian selama 30 hari dengan drop_after = 30 hari. Pada setiap iterasi, skrip mencatat jumlah chunk yang dihapus, jumlah baris yang ikut terhapus, dan total baris yang telah dibersihkan. Skrip tidak menjalankan SQL apa pun.

python simulasi_retensi.py
"""Simulasi kebijakan retensi berbasis chunk pada TimescaleDB.

Skrip ini tidak menjalankan SQL. Tujuannya memperlihatkan bagaimana kebijakan
retensi TimescaleDB yang menghapus chunk berdasarkan usia bekerja pada
hypertable yang terus menerima data baru. Skrip membangkitkan 90 hari
telemetri yang dipartisi menjadi chunk 7 hari, lalu menjalankan iterasi
retensi harian selama 30 hari, dengan drop_after = 30 hari. Setiap iterasi
mencatat jumlah chunk yang dihapus dan jumlah baris yang ikut terhapus,
sehingga perbedaan antara retensi berbasis chunk dan retensi baris-per-baris
menjadi nyata.
"""
from __future__ import annotations

from dataclasses import dataclass
from datetime import date, timedelta

AWAL = date(2026, 1, 1)
TOTAL_HARI = 90
CHUNK_HARI = 7
DROP_AFTER_HARI = 30
TITIK_PER_HARI = 1_440
ITERASI_RETENSI = 30


@dataclass
class Chunk:
    chunk_id: int
    range_start: date
    range_end: date
    baris: int


def bangun_katalog(hari_ini: date) -> list[Chunk]:
    katalog: list[Chunk] = []
    cid = 0
    kursor = AWAL
    while kursor < hari_ini:
        akhir = kursor + timedelta(days=CHUNK_HARI)
        if akhir > hari_ini:
            akhir = hari_ini
        baris = (akhir - kursor).days * TITIK_PER_HARI
        katalog.append(Chunk(cid, kursor, akhir, baris))
        cid += 1
        kursor = akhir
    return katalog


def main() -> None:
    hari_mulai_retensi = AWAL + timedelta(days=TOTAL_HARI)
    print(f"hypertable: suhu_sensor (chunk_time_interval = {CHUNK_HARI} hari)")
    print(f"retensi: drop_after = INTERVAL '{DROP_AFTER_HARI} days'")
    print(f"titik per hari: {TITIK_PER_HARI}")
    print(f"mulai retensi: {hari_mulai_retensi} (setelah {TOTAL_HARI} hari ingest)")
    print()

    total_dihapus = 0
    total_chunk_dihapus = 0
    hari_ini = hari_mulai_retensi
    katalog = bangun_katalog(hari_ini)

    print(f"{'tanggal':>12} {'chunk hidup':>12} "
          f"{'chunk drop':>10} {'baris drop':>12} {'total drop':>12}")
    print("-" * 64)

    for _ in range(ITERASI_RETENSI):
        drop_threshold = hari_ini - timedelta(days=DROP_AFTER_HARI)
        drop_chunk = [c for c in katalog if c.range_end <= drop_threshold]
        baris_drop = sum(c.baris for c in drop_chunk)
        total_dihapus += baris_drop
        total_chunk_dihapus += len(drop_chunk)
        katalog = [c for c in katalog if c.range_end > drop_threshold]

        # ingest hari baru: tambah satu chunk bila melampaui batas chunk
        hari_ini = hari_ini + timedelta(days=1)
        if katalog:
            terakhir = katalog[-1]
            if hari_ini >= terakhir.range_end:
                katalog.append(Chunk(
                    chunk_id=terakhir.chunk_id + 1,
                    range_start=terakhir.range_end,
                    range_end=terakhir.range_end + timedelta(days=CHUNK_HARI),
                    baris=CHUNK_HARI * TITIK_PER_HARI,
                ))
            else:
                terakhir.range_end = hari_ini
                terakhir.baris = (terakhir.range_end - terakhir.range_start).days * TITIK_PER_HARI

        print(f"{str(hari_ini - timedelta(days=1)):>12} {len(katalog):>12} "
              f"{len(drop_chunk):>10} {baris_drop:>12} {total_dihapus:>12}")

    print()
    print(f"total chunk dihapus selama {ITERASI_RETENSI} iterasi: {total_chunk_dihapus}")
    print(f"total baris dihapus: {total_dihapus:,}")
    print(f"rasio baris dihapus per iterasi (rata-rata): "
          f"{total_dihapus // ITERASI_RETENSI:,} baris/hari")
    print()
    print("padanan SQL TimescaleDB:")
    print("  SELECT add_retention_policy('suhu_sensor',")
    print(f"    drop_after => INTERVAL '{DROP_AFTER_HARI} days');")


if __name__ == "__main__":
    main()
text Keluaran
hypertable: suhu_sensor (chunk_time_interval = 7 hari)
retensi: drop_after = INTERVAL '30 days'
titik per hari: 1440
mulai retensi: 2026-04-01 (setelah 90 hari ingest)

     tanggal  chunk hidup chunk drop   baris drop   total drop
----------------------------------------------------------------
  2026-04-01            6          8        80640        80640
  2026-04-02            6          0            0        80640
  2026-04-03            7          0            0        80640
  2026-04-04            6          1        10080        90720
  2026-04-05            7          0            0        90720
  2026-04-06            7          0            0        90720
  2026-04-07            8          0            0        90720
  2026-04-08            8          0            0        90720
  2026-04-09            9          0            0        90720
  2026-04-10            9          0            0        90720
  2026-04-11            9          1        10080       100800
  2026-04-12            9          0            0       100800
  2026-04-13           10          0            0       100800
  2026-04-14           10          0            0       100800
  2026-04-15           11          0            0       100800
  2026-04-16           11          0            0       100800
  2026-04-17           12          0            0       100800
  2026-04-18           11          1        10080       110880
  2026-04-19           12          0            0       110880
  2026-04-20           12          0            0       110880
  2026-04-21           13          0            0       110880
  2026-04-22           13          0            0       110880
  2026-04-23           14          0            0       110880
  2026-04-24           14          0            0       110880
  2026-04-25           14          1        10080       120960
  2026-04-26           14          0            0       120960
  2026-04-27           15          0            0       120960
  2026-04-28           15          0            0       120960
  2026-04-29           16          0            0       120960
  2026-04-30           16          0            0       120960

total chunk dihapus selama 30 iterasi: 12
total baris dihapus: 120,960
rasio baris dihapus per iterasi (rata-rata): 4,032 baris/hari

padanan SQL TimescaleDB:
  SELECT add_retention_policy('suhu_sensor',
    drop_after => INTERVAL '30 days');

Keluaran di atas memperlihatkan tiga hal. Pertama, iterasi pertama menghapus delapan chunk sekaligus, sebab data 90 hari yang dikumpulkan sebelum kebijakan dipasang menghasilkan tumpukan chunk yang lebih tua dari 30 hari. Setelah iterasi pertama, penghapusan menjadi jarang, yaitu rata-rata satu chunk setiap tujuh hari, sebab chunk_time_interval = 7 hari dan drop_after = 30 hari membuat satu chunk baru menjadi kandidat penghapusan setiap tujuh hari. Kedua, jumlah baris yang dihapus per iterasi tidak konstan, melainkan bergantung pada apakah sebuah chunk melewati ambang drop_after pada hari itu. Ketiga, rata-rata 4.032 baris per hari adalah angka rata-rata yang menyembunyikan kenyataan bahwa sebagian besar hari tidak ada penghapusan sama sekali, sedangkan beberapa hari tertentu menghapus 10.080 baris sekaligus.

Demo interaktif timeline retensi#

Untuk merasakan bagaimana drop_after mengubah pola penghapusan, jalankan demo berikut. Penggeser mengubah drop_after antara 7 hingga 90 hari, sedangkan tombol mengubah frekuensi iterasi antara harian dan mingguan. Garis vertikal oranye menandai ambang drop_after dari hari ini, sedangkan chunk merah adalah chunk yang akan dihapus pada iterasi berikutnya. Tabel di bawah mencatat iterasi retensi selama 12 siklus.

Kode sumber demo
<!doctype html>
<html lang="id">
<head>
  <meta charset="utf-8" />
  <title>Simulasi kebijakan retensi</title>
  <style>
    body { font-family: system-ui, -apple-system, sans-serif; margin: 16px; color: #1f2937; }
    h2 { font-size: 16px; margin: 0 0 8px 0; font-weight: 600; }
    .kontrol { display: flex; gap: 12px; align-items: center; margin-bottom: 12px; font-size: 13px; }
    .kontrol label { font-weight: 500; }
    .kontrol input[type=range] { width: 200px; }
    .kontrol .nilai { font-variant-numeric: tabular-nums; min-width: 64px; }
    .kontrol button { padding: 4px 10px; font-size: 13px; cursor: pointer; border: 1px solid #d1d5db; background: #fff; border-radius: 4px; }
    .kontrol button.aktif { background: #1e3a8a; color: #fff; border-color: #1e3a8a; }
    .legend { display: flex; gap: 16px; margin-bottom: 8px; font-size: 12px; }
    .legend span { display: inline-flex; align-items: center; gap: 6px; }
    .swatch { width: 14px; height: 14px; border-radius: 2px; }
    svg { width: 100%; height: 220px; background: #fafafa; border: 1px solid #e5e7eb; }
    .axis { font-size: 10px; fill: #6b7280; }
    .grid { stroke: #e5e7eb; stroke-width: 1; }
    .stat { margin-top: 8px; font-size: 12px; color: #374151; }
    table { border-collapse: collapse; margin-top: 8px; font-size: 12px; width: 100%; }
    th, td { border: 1px solid #e5e7eb; padding: 4px 8px; text-align: left; }
    th { background: #f3f4f6; }
  </style>
</head>
<body>
  <h2>Timeline chunk dengan kebijakan retensi</h2>
  <div class="kontrol">
    <label for="dropAfter">drop_after:</label>
    <input id="dropAfter" type="range" min="7" max="90" step="7" value="30" />
    <span class="nilai" id="dropAfterNilai">30 hari</span>
    <button data-freq="1">harian</button>
    <button data-freq="7" class="aktif">mingguan</button>
  </div>
  <div class="legend">
    <span><span class="swatch" style="background:#3b82f6"></span> Chunk hidup</span>
    <span><span class="swatch" style="background:#ef4444"></span> Chunk dihapus retensi</span>
    <span><span class="swatch" style="background:#9ca3af"></span> Chunk belum terisi</span>
  </div>
  <svg id="chart" viewBox="0 0 640 220" preserveAspectRatio="none">
    <line class="grid" x1="0" y1="60" x2="640" y2="60" />
    <line class="grid" x1="0" y1="110" x2="640" y2="110" />
    <line class="grid" x1="0" y1="160" x2="640" y2="160" />
    <text class="axis" x="6" y="14">chunk timeline</text>
    <text class="axis" x="6" y="206">waktu (90 hari, chunk 7 hari)</text>
    <g id="chunks"></g>
    <line id="today" stroke="#111827" stroke-width="2" />
    <text class="axis" id="todayLabel" x="0" y="40">hari ini</text>
  </svg>
  <div class="stat" id="statistik">retensi: 30 hari, frekuensi: mingguan</div>
  <table id="tabel">
    <thead><tr><th>iterasi</th><th>tanggal</th><th>chunk hidup</th><th>chunk drop</th><th>baris drop</th><th>total drop</th></tr></thead>
    <tbody></tbody>
  </table>

  <script>
    const AWAL = new Date(Date.UTC(2026, 0, 1));
    const TOTAL_HARI = 90;
    const CHUNK_HARI = 7;
    const TITIK_PER_HARI = 1440;
    const HARI_MULAI_RETENSI = TOTAL_HARI; // hari-90

    function tambah(tgl, hari) {
      const d = new Date(tgl);
      d.setUTCDate(d.getUTCDate() + hari);
      return d;
    }
    function bedaHari(a, b) {
      return Math.round((b - a) / 86400000);
    }
    function tanggalStr(d) {
      return d.toISOString().slice(0, 10);
    }

    function bangunKatalog(hariIni) {
      const katalog = [];
      let cid = 0;
      let kursor = new Date(AWAL);
      while (kursor < hariIni) {
        let akhir = tambah(kursor, CHUNK_HARI);
        if (akhir > hariIni) akhir = new Date(hariIni);
        const baris = bedaHari(kursor, akhir) * TITIK_PER_HARI;
        katalog.push({ cid, range_start: new Date(kursor), range_end: new Date(akhir), baris });
        cid++;
        kursor = new Date(akhir);
      }
      return katalog;
    }

    function jadwalRetensi(dropAfter, freqHari) {
      const hariMulai = tambah(AWAL, HARI_MULAI_RETENSI);
      let katalog = bangunKatalog(hariMulai);
      const iterasi = [];
      let total = 0;
      let totalChunk = 0;
      const langkahIterasi = 12;
      let hariIni = new Date(hariMulai);
      for (let i = 0; i < langkahIterasi; i++) {
        const threshold = tambah(hariIni, -dropAfter);
        const dropChunk = katalog.filter(c => c.range_end <= threshold);
        const barisDrop = dropChunk.reduce((a, c) => a + c.baris, 0);
        total += barisDrop;
        totalChunk += dropChunk.length;
        katalog = katalog.filter(c => c.range_end > threshold);
        hariIni = tambah(hariIni, freqHari);
        if (katalog.length) {
          const terakhir = katalog[katalog.length - 1];
          if (hariIni >= terakhir.range_end) {
            katalog.push({
              cid: terakhir.cid + 1,
              range_start: new Date(terakhir.range_end),
              range_end: tambah(terakhir.range_end, CHUNK_HARI),
              baris: CHUNK_HARI * TITIK_PER_HARI
            });
          } else {
            terakhir.range_end = new Date(hariIni);
            terakhir.baris = bedaHari(terakhir.range_start, terakhir.range_end) * TITIK_PER_HARI;
          }
        }
        iterasi.push({
          iterasi: i + 1,
          tanggal: tanggalStr(tambah(hariIni, -1)),
          hidup: katalog.length,
          drop: dropChunk.length,
          barisDrop,
          totalDrop: total
        });
      }
      return { iterasi, totalChunk };
    }

    function gambarChart(dropAfter, freqHari) {
      const hariMulai = tambah(AWAL, HARI_MULAI_RETENSI);
      const katalog = bangunKatalog(hariMulai);
      const hariIni = hariMulai;
      const threshold = tambah(hariIni, -dropAfter);

      const W = 640, padX = 24, padY = 30;
      const lebarTotal = W - 2 * padX;
      const perHari = lebarTotal / TOTAL_HARI;

      const g = document.getElementById("chunks");
      g.innerHTML = "";
      katalog.forEach(c => {
        const x0 = padX + bedaHari(AWAL, c.range_start) * perHari;
        const x1 = padX + bedaHari(AWAL, c.range_end) * perHari;
        const dihapus = c.range_end <= threshold;
        const warna = dihapus ? "#ef4444" : "#3b82f6";
        const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
        rect.setAttribute("x", x0.toFixed(1));
        rect.setAttribute("y", (padY + 16).toFixed(1));
        rect.setAttribute("width", Math.max(2, x1 - x0 - 1).toFixed(1));
        rect.setAttribute("height", "60");
        rect.setAttribute("fill", warna);
        rect.setAttribute("opacity", dihapus ? "0.85" : "0.55");
        rect.setAttribute("stroke", dihapus ? "#991b1b" : "#1e40af");
        rect.setAttribute("stroke-width", "1");
        g.appendChild(rect);
        const t = document.createElementNS("http://www.w3.org/2000/svg", "text");
        t.setAttribute("class", "axis");
        t.setAttribute("x", ((x0 + x1) / 2).toFixed(1));
        t.setAttribute("y", (padY + 8).toFixed(1));
        t.setAttribute("text-anchor", "middle");
        t.textContent = "c" + c.cid;
        g.appendChild(t);
      });

      const todayX = padX + TOTAL_HARI * perHari;
      const today = document.getElementById("today");
      today.setAttribute("x1", todayX);
      today.setAttribute("y1", padY);
      today.setAttribute("x2", todayX);
      today.setAttribute("y2", padY + 90);
      const todayLabel = document.getElementById("todayLabel");
      todayLabel.setAttribute("x", (todayX - 32).toFixed(1));
      todayLabel.setAttribute("y", padY - 6);

      const thresholdX = padX + bedaHari(AWAL, threshold) * perHari;
      const thr = document.createElementNS("http://www.w3.org/2000/svg", "line");
      thr.setAttribute("stroke", "#f59e0b");
      thr.setAttribute("stroke-width", "2");
      thr.setAttribute("stroke-dasharray", "5 3");
      thr.setAttribute("x1", thresholdX);
      thr.setAttribute("y1", padY);
      thr.setAttribute("x2", thresholdX);
      thr.setAttribute("y2", padY + 90);
      g.appendChild(thr);
      const thrLabel = document.createElementNS("http://www.w3.org/2000/svg", "text");
      thrLabel.setAttribute("class", "axis");
      thrLabel.setAttribute("x", (thresholdX + 4).toFixed(1));
      thrLabel.setAttribute("y", (padY - 6).toFixed(1));
      thrLabel.textContent = "drop_after -" + dropAfter + " hari";
      g.appendChild(thrLabel);
    }

    function perbarui(dropAfter, freqHari) {
      document.getElementById("dropAfterNilai").textContent = dropAfter + " hari";
      document.getElementById("statistik").textContent =
        "retensi: " + dropAfter + " hari, frekuensi: " + (freqHari === 1 ? "harian" : "mingguan");
      gambarChart(dropAfter, freqHari);
      const { iterasi } = jadwalRetensi(dropAfter, freqHari);
      const tbody = document.querySelector("#tabel tbody");
      tbody.innerHTML = iterasi.map(it =>
        "<tr><td>" + it.iterasi + "</td><td>" + it.tanggal + "</td><td>" +
        it.hidup + "</td><td>" + it.drop + "</td><td>" +
        it.barisDrop.toLocaleString("id-ID") + "</td><td>" +
        it.totalDrop.toLocaleString("id-ID") + "</td></tr>"
      ).join("");
    }

    const slider = document.getElementById("dropAfter");
    slider.addEventListener("input", () => {
      const freq = parseInt(document.querySelector(".kontrol button.aktif").dataset.freq, 10);
      perbarui(parseInt(slider.value, 10), freq);
    });
    document.querySelectorAll(".kontrol button").forEach(btn => {
      btn.addEventListener("click", () => {
        document.querySelectorAll(".kontrol button").forEach(b => b.classList.remove("aktif"));
        btn.classList.add("aktif");
        perbarui(parseInt(slider.value, 10), parseInt(btn.dataset.freq, 10));
      });
    });
    perbarui(30, 7);
  </script>
</body>
</html>

Demo di atas memperlihatkan tradeoff klasik retensi. drop_after yang pendek, misalnya 7 hari, membuat chunk merah mendekati garis hari ini, sehingga memori operasional basis data menjadi sangat singkat. drop_after yang panjang, misalnya 90 hari, membuat seluruh chunk berwarna biru, sehingga tidak ada penghapusan sama sekali pada periode yang ditampilkan. Frekuensi mingguan menghasilkan tabel dengan iterasi yang lebih jarang tetapi dengan penghapusan yang lebih besar per iterasi, sedangkan frekuensi harian menghasilkan iterasi yang lebih sering dengan penghapusan yang lebih kecil per iterasi. Pada TimescaleDB, pilihan frekuensi tidak mengubah total baris yang akhirnya dihapus, tetapi mempengaruhi keteraturan beban pekerjaan latar belakang.

Kesalahan umum#

  • Menghapus data lama dengan DELETE FROM ... WHERE ts < ... pada hypertable besar. Operasi ini memicu pemindaian indeks, penulisan WAL untuk setiap baris, dan vacuum berat, sehingga retensi harian menjadi tidak praktis. Retensi sebaiknya berbasis chunk melalui drop_chunks atau kebijakan add_retention_policy.
  • Memilih drop_after yang lebih pendek daripada chunk_time_interval. Akibatnya chunk yang baru saja dibuat dapat langsung menjadi kandidat penghapusan, sehingga data panas terhapus sebelum sempat dikompresi atau dibaca.
  • Memasang kebijakan retensi tanpa kebijakan kompresi. Tanpa kompresi, ukuran disk tumbuh linier dengan waktu, sehingga retensi 90 hari pada telemetri menitan dengan banyak sensor tetap menghasilkan ukuran disk yang besar.

Ringkasan#

  • Retensi TimescaleDB menghapus chunk berdasarkan usia, sehingga biaya penghapusan tidak bergantung pada jumlah baris di dalam chunk.
  • add_retention_policy mengotomatiskan penghapusan melalui argumen drop_after, dan kebijakan ini terdaftar pada timescaledb_information.jobs.
  • drop_after sebaiknya lebih panjang daripada chunk_time_interval dan lebih panjang daripada compress_after, sehingga data melewati kompresi sebelum dihapus.

Referensi#