Hypertables

Cara membuatnya, chunking waktu otomatis, dan bagian yang tetap Postgres biasa.

Daftar isi

Hypertable adalah abstraksi tabel logis yang dipartisi otomatis menjadi banyak chunk fisik oleh TimescaleDB, sehingga query SQL tetap menyebut satu nama tabel padahal penyimpanannya tersebar atas banyak tabel anak. Topik ini menjelaskan cara membuat hypertable dengan create_hypertable, parameter chunk_time_interval, serta bagian yang tetap menjadi Postgres biasa. Setelah membaca topik ini, sebuah tabel biasa dapat diubah menjadi hypertable tanpa mengubah SQL aplikasi yang sudah ada, dan pemilihan chunk_time_interval tidak lagi diambil secara spekulatif.

Tabel biasa, lalu hypertable#

Pembuatan hypertable dimulai dari sebuah tabel Postgres biasa. Skema kolom, tipe data, dan constraint didefinisikan dengan CREATE TABLE standar, persis seperti tabel operasional. Yang membedakan adalah langkah berikutnya, yaitu pemanggilan fungsi create_hypertable yang mendaftarkan tabel tersebut ke katalog ekstensi dan menyalakan chunk dispatch. Setelah langkah itu, TimescaleDB mengelola partisi waktu secara otomatis, sementara query tetap menyebut nama tabel logis tanpa menyentuh chunk secara langsung.

Tabel biasa yang tidak diubah menjadi hypertable tetap berfungsi, tetapi tidak mendapat manfaat partisi otomatis, eliminasi chunk, dan kompresi kolumnar. Pada skala kecil, perbedaan ini tidak terasa, tetapi pada tabel dengan jutaan hingga miliaran baris, ketiadaan partisi waktu menjadi sumber lambatnya retensi serta membengkaknya ukuran indeks. Karena itu, keputusan untuk mengubah tabel menjadi hypertable sebaiknya diambil saat skema dibuat, bukan setelah tabel tumbuh besar.

create_hypertable: satu pemanggilan#

Fungsi create_hypertable menerima dua argumen wajib, yaitu referensi tabel dan nama kolom waktu. Argumen tambahan mengatur chunk_time_interval, jumlah partisi ruang, serta apakah tabel yang sudah berisi baris segera dipartisi. Pemanggilan ini mengembalikan metadata hypertable, dan efeknya segera terlihat pada system catalog _timescaledb_catalog.hypertable. Setelah pemanggilan, semua INSERT baru otomatis diarahkan ke chunk yang sesuai berdasarkan nilai kolom waktu, sedangkan baris lama yang sudah ada pada tabel biasa tetap dapat dimigrasikan dengan argumen migrate_data => true, meskipun operasi ini memerlukan waktu yang bergantung pada jumlah baris.

Argumen if_not_exists => true membuat pemanggilan bersifat idempoten, sehingga skrip inisialisasi basis data dapat dijalankan ulang tanpa kesalahan. Praktik ini umum digunakan pada alur deployment otomatis, sebab skrip inisialisasi sering dijalankan beberapa kali pada lingkungan pengembangan. Argumen chunk_time_interval wajib dipilih sejak awal, sebab perubahan ukuran chunk setelah hypertable tumbuh hanya berlaku untuk chunk baru, sedangkan chunk lama tetap menggunakan ukuran sebelumnya. Karena itu, keputusan tentang chunk_time_interval sebaiknya diambil bersamaan dengan keputusan tentang skema tabel, bukan ditunda setelah data mengalir.

Perintah berikut memperlihatkan urutan lengkap, dari CREATE TABLE hingga pemanggilan create_hypertable. Perintah ini tidak dijalankan di sandbox karena memerlukan server Postgres dengan TimescaleDB aktif, sehingga hanya ditampilkan sebagai referensi sintaksis.

postgres Pembuatan hypertable dengan chunk 7 hari
-- tabel biasa dengan kolom waktu 'ts' sebagai poros
CREATE TABLE suhu_sensor (
    ts          TIMESTAMPTZ NOT NULL,
    sensor_id   TEXT        NOT NULL,
    suhu_c      DOUBLE PRECISION NOT NULL
);

-- mendaftarkan tabel sebagai hypertable; chunk tiap 7 hari
SELECT create_hypertable(
    'suhu_sensor',
    'ts',
    chunk_time_interval => INTERVAL '7 days'
);

-- insert tetap ditulis seperti tabel biasa
INSERT INTO suhu_sensor VALUES
    ('2026-11-01 00:00:00+00', 'S01', 24.1),
    ('2026-11-01 00:01:00+00', 'S01', 24.2);
text Keluaran
create_hypertable
(73,public,suhu_sensor,t)

chunk_time_interval: menentukan ukuran chunk#

Parameter chunk_time_interval menentukan rentang waktu yang dicakup oleh satu chunk. Pemilihan nilai memengaruhi keseimbangan antara banyak chunk kecil dan sedikit chunk besar. Chunk yang terlalu kecil menyebabkan banyak tabel anak di katalog, sehingga planner menanggung biaya pemindaian metadata yang tinggi. Chunk yang terlalu besar menyebabkan satu chunk menyimpan terlalu banyak baris, sehingga eliminasi chunk menjadi kurang efektif dan kompresi tertunda.

Panduan dokumentasi resmi TimescaleDB menyebutkan aturan praktis, yaitu ukuran chunk sebaiknya memuat sekitar 25% memori yang dialokasikan untuk shared buffer pada beban umum. Sebagai contoh, bila shared_buffers diatur ke 4 GB, maka chunk yang menyimpan data mentah sebaiknya berukuran sekitar 1 GB setelah dikompresi. Untuk telemetri dengan satu titik per menit pada satu sensor, INTERVAL '7 days' menghasilkan sekitar 10.000 baris per chunk, yang merupakan nominal wajar untuk pemantauan satu host. Bila laju ingest jauh lebih tinggi, misalnya seribu titik per detik, interval yang sama menghasilkan lebih dari 600 juta baris per chunk, sehingga interval perlu diperkecil.

Katalog chunk pada hypertable#

Setelah hypertable dibuat, TimescaleDB memelihara katalog chunk pada skema internal _timescaledb_catalog.chunk. Setiap chunk memiliki chunk_id, rentang waktu, dan referensi ke tabel fisik bernama _hyper_<hypertable_id>_<chunk_id>_chunk. Skrip Python berikut tidak menjalankan SQL, tetapi mensimulasikan katalog itu pada 28 hari telemetri dengan interval 7 hari, sehingga hubungan antara tabel logis dan tabel fisik menjadi nyata.

python katalog_hypertable.py
"""Mensimulasikan katalog chunk hypertable TimescaleDB.

Skrip ini tidak menjalankan SQL. Tujuannya memperlihatkan bagaimana sebuah
hypertable logis tunggal dipecah menjadi banyak chunk berdasarkan rentang
waktu, sehingga tabel logis tetap terlihat satu bagi pembaca SQL, sedangkan
penyimpanan fisik terbagi atas banyak tabel anak.
"""
from __future__ import annotations

from datetime import datetime, timedelta, timezone

AWAL = datetime(2026, 11, 1, tzinfo=timezone.utc)
JUMLAH_HARI = 28
CHUNK_HARI = 7
TITIK_PER_HARI = 1_440  # 1 titik per menit


def rentang_chunk() -> list[tuple[int, datetime, datetime, int]]:
    katalog: list[tuple[int, datetime, datetime, int]] = []
    idx = 0
    kursor = AWAL
    while kursor < AWAL + timedelta(days=JUMLAH_HARI):
        akhir = min(kursor + timedelta(days=CHUNK_HARI), AWAL + timedelta(days=JUMLAH_HARI))
        hari = (akhir - kursor).days
        baris = hari * TITIK_PER_HARI
        katalog.append((idx, kursor, akhir, baris))
        idx += 1
        kursor = akhir
    return katalog


def main() -> None:
    katalog = rentang_chunk()
    print(f"hypertable: suhu_sensor (chunk_time_interval = '{CHUNK_HARI} days')")
    print(f"rentang total: {AWAL.date()} -> {(AWAL + timedelta(days=JUMLAH_HARI)).date()}")
    print(f"jumlah chunk: {len(katalog)}")
    print()
    print(f"{'chunk_id':>9} {'range_start':>26} {'range_end':>26} {'baris':>8}")
    print("-" * 78)
    for cid, mulai, akhir, baris in katalog:
        print(f"{cid:>9} {str(mulai):>26} {str(akhir):>26} {baris:>8}")
    print()
    total = sum(b for _, _, _, b in katalog)
    print(f"total baris logis (jumlah baris semua chunk): {total}")
    print("baris satu chunk = baris tabel logis pada rentang itu;")
    print("tabel logis dan tabel fisik sama-sama menyimpan seluruh data.")


if __name__ == "__main__":
    main()
text Keluaran
hypertable: suhu_sensor (chunk_time_interval = '7 days')
rentang total: 2026-11-01 -> 2026-11-29
jumlah chunk: 4

 chunk_id                range_start                  range_end    baris
------------------------------------------------------------------------------
        0  2026-11-01 00:00:00+00:00  2026-11-08 00:00:00+00:00    10080
        1  2026-11-08 00:00:00+00:00  2026-11-15 00:00:00+00:00    10080
        2  2026-11-15 00:00:00+00:00  2026-11-22 00:00:00+00:00    10080
        3  2026-11-22 00:00:00+00:00  2026-11-29 00:00:00+00:00    10080

total baris logis (jumlah baris semua chunk): 40320
baris satu chunk = baris tabel logis pada rentang itu;
tabel logis dan tabel fisik sama-sama menyimpan seluruh data.

Keluaran di atas memperlihatkan dua hal. Pertama, satu tabel logis suhu_sensor dipecah menjadi empat chunk fisik, masing-masing mencakup tujuh hari dengan sekitar 10.000 baris. Kedua, jumlah baris logis sama dengan jumlah baris seluruh chunk, sehingga query SELECT count(*) FROM suhu_sensor tetap mengembalikan angka yang benar. Diagram berikut meringkas hubungan tersebut secara visual.

Hypertable logissuhu_sensor (tabel logis)ts, sensor_id, suhu_cselect * from suhu_sensorChunk fisik (4 partisi waktu)_hyper_1_0_chunk01 Nov -> 08 Nov_hyper_1_1_chunk08 Nov -> 15 Nov_hyper_1_2_chunk15 Nov -> 22 Nov_hyper_1_3_chunk22 Nov -> 29 Novcreate_hypertablememetakanSatu nama tabel, satu set kolom,satu kueri SELECT. Penyimpananterbagi atas 4 chunk fisik.
Hypertable logis tunggal dipetakan ke empat chunk fisik berdasarkan rentang waktu tujuh hari

Bagian yang tetap Postgres biasa#

Meskipun penyimpanan dipecah menjadi chunk, sebagian besar aspek tabel tetap menjadi Postgres biasa. Kolom, tipe data, dan constraint seperti NOT NULL atau CHECK tetap didefinisikan pada tabel logis dan berlaku pada semua chunk. Indeks yang dibuat dengan CREATE INDEX pada tabel logis secara otomatis diterapkan pada setiap chunk, sehingga indeks B-tree pada (sensor_id, ts) tetap tersedia tanpa duplikasi manual. Trigger, foreign key, dan row-level security juga tetap berlaku apa adanya.

Bagian yang berubah terbatas pada mekanisme internal. Perintah INSERT diarahkan oleh chunk dispatch hook ke chunk yang sesuai, sedangkan perintah DELETE dan UPDATE menerima rute yang sama. Query SELECT diproses oleh planner yang sudah dipasang hook TimescaleDB, sehingga predikat waktu dapat mengeliminasi chunk. Karena perubahan ini terjadi di lapisan planner dan executor, aplikasi yang menggunakan driver standar tidak perlu mengubah satu baris pun SQL-nya untuk berinteraksi dengan hypertable.

Kesalahan umum#

  • Memilih chunk_time_interval yang terlalu kecil, misalnya satu jam pada telemetri rendah. Akibatnya katalog chunk membengkak dan planner menanggung biaya pemindaian metadata yang tinggi.
  • Membuat indeks pada tabel biasa sebelum create_hypertable dan menganggap indeks otomatis menyebar. Indeks memang tetap berlaku, tetapi chunk baru yang tercipta setelahnya membutuhkan konsistensi yang harus diverifikasi.
  • Mencoba memasukkan baris dengan kolom waktu NULL pada hypertable. Kolom waktu wajib non-NULL, sebab chunk dispatch tidak dapat menentukan chunk tujuan tanpa nilai waktu.

Ringkasan#

  • Hypertable dibuat dari tabel Postgres biasa dengan satu pemanggilan create_hypertable, dan query SQL tetap menyebut nama tabel logis.
  • Parameter chunk_time_interval menentukan ukuran chunk, dengan pedoman ukuran chunk sekitar 25% dari shared_buffers setelah dikompresi.
  • Skema, indeks, trigger, dan constraint tetap menjadi Postgres biasa, sedangkan perubahan internal terbatas pada planner, executor, dan chunk dispatch.

Referensi#