Tags vs fields

Kolom identitas vs nilai terukur, beserta tradisi measurement/tag/field.

Daftar isi

Tags dan fields adalah dua peran kolom pada data time-series. Topik ini menjelaskan tradisi measurement, tag, dan field yang berasal dari InfluxDB, alasan tags digunakan sebagai kolom identitas sedangkan fields sebagai nilai terukur, serta dampak kardinalitas pada keduanya. Setelah membaca, kolom pada tabel tidak lagi disusun berdasarkan urutan pengetikan, melainkan berdasarkan peran query yang dilayani.

Tags sebagai identitas, fields sebagai nilai#

Tags adalah kolom yang digunakan untuk menyaring dan mengelompokkan data, sehingga nilainya berulang dengan kardinalitas rendah hingga menengah. Contoh tag yang lazim adalah station_id, lokasi, dan region, sebab nilainya berasal dari himpunan terbatas. Fields adalah kolom yang menyimpan nilai terukur dan menjadi target agregasi, seperti suhu_c, kelembapan, dan tekanan_hpa. Pemisahan ini tidak sekadar konvensi penamaan, melainkan menentukan struktur indeks, yaitu tags diindeks agar penyaringan cepat, sedangkan fields disimpan tanpa indeks tambahan agar penulisan cepat.

Pembedaan tags dan fields berasal dari kebutuhan mesin time-series yang membaca berdasarkan identitas dan mengagregasi berdasarkan nilai. Sebuah query “suhu rata-rata STA01 kemarin” memfilter tag station_id='STA01' dan mengagregasi field suhu_c. Bila station_id tidak diindeks, query memerlukan pemindaian penuh. Bila suhu_c diindeks, penulisan menjadi lambat tanpa manfaat, sebab field hampir tidak pernah disaring. Konvensi ini menjadikan indeks tumbuh seiring banyaknya kombinasi tag, bukan seiring banyaknya nilai field.

Tradisi measurement, tag, dan field#

InfluxDB memperkenalkan istilah measurement, tag, dan field sebagai bagian dari line protocol. Sebuah measurement adalah wadah logis untuk deret yang berbagi skema, misalnya cuaca. Tag adalah pasangan kunci-nilai yang ditulis setelah nama measurement dan sebelum spasi pertama, sedangkan field ditulis setelahnya. Pemisahan ini tercermin pada sintaks cuaca,station_id=STA01,lokasi=atap suhu_c=24.1,kelembapan=62.0 1730419200000000000. TimescaleDB tidak memiliki pemisahan bawaan, tetapi konvensi yang sama diterapkan melalui indeks pada kolom identitas dan ketiadaan indeks pada kolom nilai.

Konvensi ini bukan sekadar tradisi. Indeks pada tags memungkinkan query WHERE station_id='STA01' menemukan baris dalam waktu konstan terhadap ukuran tabel, sedangkan agregasi AVG(suhu_c) GROUP BY station_id membaca field tanpa beban indeks tambahan. Pada skala produksi, pemisahan yang benar menentukan apakah query dashboard berjalan dalam hitungan milidetik atau detik. Karena itu, sebelum SQL ditulis, setiap kolom sebaiknya sudah diberi label peran, yaitu tag, field, atau timestamp.

Konvensi yang sama berlaku lintas mesin, meskipun dengan nama berbeda. Prometheus menyebut tag sebagai label dan field sebagai sample value, tetapi pemisahan identitas dan nilai tetap sama. ClickHouse menyebut tag sebagai kolom di sorting key dan field sebagai kolom di luar sorting key. TimescaleDB tidak memiliki istilah bawaan, tetapi indeks pada kolom identitas menjalankan fungsi yang sama dengan indeks tag InfluxDB. Persamaan ini menunjukkan bahwa pemisahan tag dan field adalah kebutuhan struktural basis data time-series, bukan kekhasan satu produk.

Demonstrasikan pemisahan dengan pandas#

Skrip berikut membangun sebuah measurement bernama cuaca dengan tag station_id dan lokasi, serta field suhu_c dan kelembapan. Skrip menunjukkan tiga pola query yang khas, yaitu filter berdasarkan tag, agregasi field per kelompok tag, dan perhitungan kardinalitas tiap kolom. Tujuannya mengaitkan konvensi penamaan dengan operasi tabel yang nyata, sehingga peran tag dan field terlihat bukan sekadar label.

python tags_fields.py
"""Demonstrasi tradisi measurement, tag, dan field pada data time-series.

Skrip membangun sebuah 'measurement' cuaca dengan tag (kolom identitas)
dan field (kolom nilai terukur), lalu menunjukkan pola query yang khas,
yaitu filter berdasarkan tag dan agregasi atas field. Skrip juga
menghitung kardinalitas tiap kolom untuk menunjukkan mengapa tag
berdimensi tinggi menjadi beban indeks.
"""
from __future__ import annotations

import numpy as np
import pandas as pd

CAP_WAKTU = pd.to_datetime([
    "2026-11-01 00:00:00",
    "2026-11-01 00:15:00",
    "2026-11-01 00:30:00",
    "2026-11-01 00:45:00",
])


def bangun_measurement() -> pd.DataFrame:
    rng = np.random.default_rng(42)
    rows = []
    for station in ["STA01", "STA02", "STA03"]:
        for lokasi in ["atap", "tanah"]:
            for ts in CAP_WAKTU:
                rows.append({
                    "ts": ts,
                    "station_id": station,
                    "lokasi": lokasi,
                    "suhu_c": round(24.0 + rng.normal(0, 0.5), 2),
                    "kelembapan": round(60.0 + rng.normal(0, 3.0), 2),
                })
    return pd.DataFrame(rows)


def main() -> None:
    df = bangun_measurement()
    print("measurement 'cuaca' (tag = station_id, lokasi; field = suhu_c, kelembapan):")
    print(df.head(8).to_string(index=False))
    print()
    print(f"  total baris: {len(df)}")
    print()

    f = df[df["station_id"] == "STA01"]
    print("filter tag station_id='STA01':")
    print(f.head(4).to_string(index=False))
    print()
    print(f"  baris yang lolos: {len(f)}")
    print()

    agg = df.groupby(["station_id", "lokasi"])[["suhu_c", "kelembapan"]].mean().round(2)
    print("agregasi field suhu_c dan kelembapan per kelompok tag:")
    print(agg.to_string())
    print()

    print("kardinalitas tiap kolom:")
    n = len(df)
    for col in df.columns:
        c = df[col].nunique()
        print(f"  {col}: {c} unik dari {n} baris ({c / n * 100:.1f}%)")


if __name__ == "__main__":
    main()
text Keluaran
measurement 'cuaca' (tag = station_id, lokasi; field = suhu_c, kelembapan):
                 ts station_id lokasi  suhu_c  kelembapan
2026-11-01 00:00:00      STA01   atap   24.15       56.88
2026-11-01 00:15:00      STA01   atap   24.38       62.82
2026-11-01 00:30:00      STA01   atap   23.02       56.09
2026-11-01 00:45:00      STA01   atap   24.06       59.05
2026-11-01 00:00:00      STA01  tanah   23.99       57.44
2026-11-01 00:15:00      STA01  tanah   24.44       62.33
2026-11-01 00:30:00      STA01  tanah   24.03       63.38
2026-11-01 00:45:00      STA01  tanah   24.23       57.42

  total baris: 24

filter tag station_id='STA01':
                 ts station_id lokasi  suhu_c  kelembapan
2026-11-01 00:00:00      STA01   atap   24.15       56.88
2026-11-01 00:15:00      STA01   atap   24.38       62.82
2026-11-01 00:30:00      STA01   atap   23.02       56.09
2026-11-01 00:45:00      STA01   atap   24.06       59.05

  baris yang lolos: 8

agregasi field suhu_c dan kelembapan per kelompok tag:
                   suhu_c  kelembapan
station_id lokasi                    
STA01      atap     23.90       58.71
           tanah    24.17       60.14
STA02      atap     24.28       58.62
           tanah    24.34       60.03
STA03      atap     23.90       60.10
           tanah    24.14       60.92

kardinalitas tiap kolom:
  ts: 4 unik dari 24 baris (16.7%)
  station_id: 3 unik dari 24 baris (12.5%)
  lokasi: 2 unik dari 24 baris (8.3%)
  suhu_c: 21 unik dari 24 baris (87.5%)
  kelembapan: 24 unik dari 24 baris (100.0%)

Keluaran di atas memperlihatkan tiga hal. Pertama, filter station_id='STA01' memangkas 24 baris menjadi 8 baris, sebab tag tersebut berperan sebagai identitas. Kedua, agregasi AVG(suhu_c) per kelompok tag menghasilkan enam baris, yaitu kombinasi tiga stasiun dan dua lokasi. Ketiga, kardinalitas memperlihatkan perbedaan peran, yaitu tag station_id hanya memiliki 3 nilai unik dan lokasi hanya 2, sedangkan field suhu_c dan kelembapan hampir seluruhnya unik. Indeks pada tags berukuran kecil dan stabil, sedangkan indeks pada fields akan tumbuh sebanding jumlah baris.

Hasil agregasi juga memperlihatkan konsekuensi lain dari pemilihan tag. Pengelompokan (station_id, lokasi) menghasilkan enam baris rerata, yaitu cukup untuk dashboard per stasiun per lokasi, tetapi tidak cukup untuk pertanyaan seperti “suhu pada menit ke-13”. Pertanyaan dengan resolusi yang lebih halus memerlukan kembali ke data mentah, sebab tag tidak menyimpan resolusi waktu. Karena itu, jumlah tag sebaiknya secukupnya, yaitu cukup untuk melayani kelompok pertanyaan yang paling sering, tanpa membengkak menjadi kombinatorial.

Kesalahan umum#

  • Menjadikan ID transaksi atau ID pengguna sebagai tag. Nilai tersebut unik per titik, sehingga kardinalitas tag setara dengan jumlah baris dan indeks membengkak. Tag sebaiknya memiliki kardinalitas jauh lebih rendah dari jumlah baris.
  • Mengindeks seluruh kolom dengan asumsi “lebih cepat”. Indeks pada field memberatkan penulisan tanpa manfaat query, sebab field hampir tidak pernah disaring.
  • Menggabungkan identitas dan nilai pada satu kolom, misalnya station_id_suhu=STA01-24.1. Kolom demikian tidak bisa difilter sebagai tag maupun diagregasi sebagai field, sehingga tidak melayani salah satu peran.

Ringkasan#

  • Tags adalah kolom identitas yang disaring dan dikelompokkan, sedangkan fields adalah kolom nilai terukur yang diagregasi.
  • Tradisi measurement, tag, dan field berasal dari InfluxDB, tetapi konvensinya berlaku umum pada basis data time-series apa pun.
  • Indeks dibangun pada tags, bukan pada fields, sehingga kardinalitas tag menentukan ukuran indeks dan kelincahan penulisan.

Referensi#