Chunking dan partisi

Partisi waktu dan ruang, serta alasan query rentang menjadi lebih cepat tanpa usaha tambahan.

Daftar isi

Chunking adalah pemecahan satu hypertable menjadi banyak partisi fisik berdasarkan rentang waktu, dan bila ditambah dimensi ruang, setiap chunk waktu dipecah lagi berdasarkan nilai kolom tertentu. Topik ini menjelaskan partisi waktu dan partisi ruang secara terpisah, lalu menunjukkan bagaimana keduanya bekerja sama saat query rentang memuat predikat pada kedua dimensi. Setelah membaca topik ini, alasan query rentang menjadi lebih cepat tanpa usaha tambahan dari sisi penulis query menjadi jelas, dan keputusan untuk menambah dimensi ruang tidak lagi diambil begitu saja.

Partisi waktu: chunk demi rentang#

Partisi waktu adalah dimensi wajib pada setiap hypertable. Saat create_hypertable dipanggil, ekstensi membuat partisi pertama dan menyiapkan jalur chunk dispatch yang mengarahkan baris baru ke chunk yang sesuai berdasarkan nilai kolom waktu. Setiap chunk menyimpan rentang waktu berurutan, tanpa tumpang-tindih, dan biasanya tanpa celah, sehingga katalog chunk dapat digunakan sebagai peta cepat untuk menentukan chunk mana yang menyentuh sebuah rentang query.

Partisi waktu memberi dua manfaat yang saling berkaitan. Pertama, retensi menjadi operasi cepat, sebab menghapus chunk lama hanya berarti melepaskan seluruh tabel anak, bukan memindai baris demi baris. Kedua, query rentang waktu dapat mengeliminasi sebagian besar chunk dari pemindaian. Bila query meminta data antara tanggal 11 hingga 15 November pada hypertable yang menyimpan 28 hari, planner cukup memeriksa katalog dan menyentuh chunk yang rentangnya tumpang-tindih dengan rentang query, sedangkan chunk lain diabaikan tanpa pemindaian indeks.

Partisi ruang: add_dimension#

Partisi ruang adalah dimensi tambahan yang memecah setiap chunk waktu berdasarkan nilai kolom non-waktu, biasanya identitas seri seperti sensor_id atau host. Partisi ini ditambahkan setelah create_hypertable dengan fungsi add_dimension, dan tujuannya bukan sekadar eliminasi chunk pada query yang memuat filter kolom itu, melainkan juga paralelisme. Pada hypertable dengan dimensi ruang, planner dapat menjalankan pemindaian chunk secara paralel lintas worker, sehingga query agregat pada banyak seri dapat memanfaatkan beberapa CPU sekaligus.

Pemilihan kolom untuk dimensi ruang memerlukan kehati-hatian. Kolom dengan kardinalitas terlalu tinggi, misalnya ID transaksi unik, menyebabkan jumlah chunk meledak, sebab setiap nilai baru memicu pembuatan chunk terpisah. Kolom dengan kardinalitas terlalu rendah, misalnya boolean, tidak memberi eliminasi chunk yang berarti. Dokumentasi resmi TimescaleDB menyarankan kolom dengan kardinalitas sedang, misalnya sensor_id pada kluster dengan puluhan hingga ratusan sensor. Perintah berikut tidak dijalankan di sandbox karena memerlukan TimescaleDB aktif, sehingga hanya menjadi referensi sintaksis.

postgres Penambahan dimensi ruang pada hypertable
-- hypertable 'suhu_sensor' sudah dibuat sebelumnya
SELECT add_dimension('suhu_sensor', 'sensor_id', number_partitions => 3);

-- setelah ini, setiap chunk waktu dipecah lagi menjadi 3 chunk ruang
-- sehingga total chunk = (chunk waktu) x (chunk ruang)
text Keluaran
add_dimension
(68,public,suhu_sensor,sensor_id,t)

Eliminasi chunk oleh planner#

Eliminasi chunk adalah mekanisme yang membuat query rentang menjadi cepat tanpa usaha tambahan dari penulis query. Saat planner menerima sebuah query SELECT, ia memeriksa predikat pada kolom waktu dan kolom dimensi ruang, lalu mencocokkannya dengan katalog chunk. Chunk yang rentangnya tidak tumpang-tindih dengan predikat waktu dieliminasi, demikian juga chunk yang nilainya pada dimensi ruang tidak cocok dengan predikat filter. Hasilnya, query yang secara logis menyentuh seluruh tabel fisik hanya memindai sebagian kecil chunk, sering kali satu atau dua dari puluhan.

Mekanisme ini bekerja tanpa hint atau sintaksis khusus. Penulis query tetap menulis WHERE ts BETWEEN ... AND sensor_id = '...', dan planner mengubahnya menjadi pemindaian chunk yang efisien. Karena itu, keberadaan indeks B-tree pada kolom waktu dan kolom dimensi ruang tetap dianjurkan, sebab setelah chunk dipilih, indeks lokal pada chunk itu yang mempercepat pencarian baris di dalamnya. Eliminasi chunk hanya menentukan chunk mana yang dipindai, sedangkan indeks menentukan bagaimana baris di dalam chunk dipindai.

Pemilihan chunk untuk query rentang#

Untuk memperlihatkan eliminasi chunk secara konkret, skrip Python berikut membangun katalog 15 chunk, yaitu lima chunk waktu tujuh hari yang masing-masing dipecah menjadi tiga chunk ruang berdasarkan sensor_id. Skrip kemudian menjalankan query hipotetis pada rentang 11 hingga 15 November dengan filter sensor_id = 'S02', dan mencatat berapa chunk yang benar-benar dipindai. Skrip tidak menjalankan SQL apa pun, melainkan memetakan logika eliminasi chunk ke dalam kode Python biasa.

python pemilihan_chunk.py
"""Simulasi pemilihan chunk untuk query rentang dengan partisi ruang.

Skrip ini tidak menjalankan SQL. Tujuannya memperlihatkan bagaimana
chunk waktu dan ruang dieliminasi oleh planner saat query memuat predikat
pada kolom waktu dan kolom partisi ruang, sehingga sebagian besar chunk
tidak dipindai sama sekali.
"""
from __future__ import annotations

from datetime import datetime, timedelta, timezone

AWAL = datetime(2026, 11, 1, tzinfo=timezone.utc)
JUMLAH_HARI = 35
CHUNK_HARI = 7
SENSORS = ["S01", "S02", "S03"]


def bangun_katalog() -> list[dict]:
    katalog: list[dict] = []
    cid = 0
    kursor = AWAL
    while kursor < AWAL + timedelta(days=JUMLAH_HARI):
        akhir = kursor + timedelta(days=CHUNK_HARI)
        for s in SENSORS:
            katalog.append({
                "chunk_id": cid,
                "range_start": kursor,
                "range_end": akhir,
                "sensor": s,
            })
            cid += 1
        kursor = akhir
    return katalog


def tumpang_tindih(s1, e1, s2, e2) -> bool:
    return s1 < e2 and s2 < e1


def main() -> None:
    katalog = bangun_katalog()
    print(f"hypertable: suhu_sensor")
    print(f"partisi waktu: {CHUNK_HARI} hari, partisi ruang: sensor_id")
    print(f"total chunk: {len(katalog)} "
          f"({len(katalog)//len(SENSORS)} waktu x {len(SENSORS)} ruang)")
    print()

    q_mulai = AWAL + timedelta(days=10)
    q_akhir = AWAL + timedelta(days=14)
    q_sensor = "S02"
    print(f"query: SELECT * FROM suhu_sensor")
    print(f"       WHERE ts BETWEEN '{q_mulai.date()}' AND '{q_akhir.date()}'")
    print(f"         AND sensor_id = '{q_sensor}'")
    print()

    dipindai = []
    elim_waktu = 0
    elim_ruang = 0
    for c in katalog:
        if c["sensor"] != q_sensor:
            elim_ruang += 1
            continue
        if not tumpang_tindih(c["range_start"], c["range_end"], q_mulai, q_akhir):
            elim_waktu += 1
            continue
        dipindai.append(c)

    print(f"chunk dipindai: {len(dipindai)} dari {len(katalog)}")
    for c in dipindai:
        print(f"  - chunk_id={c['chunk_id']} "
              f"range=[{c['range_start'].date()} -> {c['range_end'].date()}) "
              f"sensor={c['sensor']}")
    print(f"chunk dieliminasi predikat ruang (sensor_id): {elim_ruang}")
    print(f"chunk dieliminasi predikat waktu (ts): {elim_waktu}")
    print(f"rasio pemindaian: {len(dipindai)}/{len(katalog)} "
          f"= {len(dipindai)/len(katalog):.1%}")


if __name__ == "__main__":
    main()
text Keluaran
hypertable: suhu_sensor
partisi waktu: 7 hari, partisi ruang: sensor_id
total chunk: 15 (5 waktu x 3 ruang)

query: SELECT * FROM suhu_sensor
       WHERE ts BETWEEN '2026-11-11' AND '2026-11-15'
         AND sensor_id = 'S02'

chunk dipindai: 1 dari 15
  - chunk_id=4 range=[2026-11-08 -> 2026-11-15) sensor=S02
chunk dieliminasi predikat ruang (sensor_id): 10
chunk dieliminasi predikat waktu (ts): 4
rasio pemindaian: 1/15 = 6.7%

Keluaran di atas memperlihatkan inti eliminasi chunk. Dari 15 chunk yang tersedia, hanya satu yang dipindai, sedangkan sepuluh chunk dieliminasi oleh predikat ruang dan empat chunk dieliminasi oleh predikat waktu. Rasio pemindaian 6,7 persen berarti 93,3 persen chunk tidak disentuh sama sekali, sehingga query rentang yang melibatkan jutaan baris tetap cepat. Diagram berikut memvisualisasikan grid 5 kali 3 itu, dengan satu chunk berwarna biru yang mewakili chunk yang dipindai.

Grid chunk waktu x ruang (15 chunk)5 chunk waktu (7 hari) x 3 partisi ruang (sensor_id)01-08 Nov08-15 Nov15-22 Nov22-29 Nov29 Nov-06 DesS01S02S03chunk_id=4Query: ts BETWEEN '11 Nov' AND '15 Nov' AND sensor_id = 'S02'Planner hanya memindai 1 dari 15 chunk (chunk biru).Chunk lain dieliminasi oleh predikat waktu dan predikat ruang.
Grid 5 chunk waktu kali 3 chunk ruang, dengan satu chunk biru yang dipindai untuk query rentang 11 hingga 15 November pada sensor S02

Mengapa tidak perlu usaha tambahan dari penulis query#

Eliminasi chunk terjadi di lapisan planner, sehingga penulis query tidak perlu menyebut nama chunk secara eksplisit. Kueri SELECT * FROM suhu_sensor WHERE ts BETWEEN ... AND sensor_id = '...' ditulis persis seperti pada tabel biasa, dan planner yang memutuskan chunk mana yang dipindai. Hal ini berarti aplikasi yang sudah menulis SQL untuk tabel biasa tidak perlu diubah ketika tabel itu diubah menjadi hypertable, sepanjang kolom waktu dan kolom dimensi ruang tetap digunakan dalam predikat.

Implikasinya cukup luas. Bila pola query dominan adalah rentang waktu dengan filter identitas seri, maka pemilihan kolom untuk chunk_time_interval dan add_dimension sudah cukup untuk mempercepat sebagian besar query tanpa penulisan ulang aplikasi. Hal ini juga menjelaskan mengapa hypertable disebut sebagai tabel logis, sebab dari sudut pandang penulis SQL, tabel tetap satu, sedangkan pemecahan fisik menjadi urusan ekstensi.

Kesalahan umum#

  • Memilih kolom kardinalitas tinggi sebagai dimensi ruang, misalnya ID transaksi unik. Akibatnya setiap nilai baru memicu pembuatan chunk, sehingga katalog membengkak dan planner menjadi lambat.
  • Menulis query tanpa predikat waktu, lalu heran query tetap lambat. Eliminasi chunk bekerja berdasarkan predikat waktu, sehingga query tanpa predikat ts tetap memindai seluruh chunk.
  • Menambahkan dimensi ruang terlalu dini pada hypertabel kecil. Paralelisme lintas chunk hanya memberi manfaat bila data cukup besar, sedangkan overhead katalog selalu ada.

Ringkasan#

  • Partisi waktu adalah dimensi wajib pada hypertable, memberi retensi cepat dan eliminasi chunk pada query rentang.
  • Partisi ruang ditambahkan dengan add_dimension pada kolom kardinalitas sedang, dan memberi manfaat ganda, yaitu eliminasi chunk pada filter dan paralelisme lintas worker.
  • Eliminasi chunk bekerja di lapisan planner, sehingga penulis query tidak perlu mengubah SQL yang sudah ditulis untuk tabel biasa.

Referensi#