Server substitute GNU Guix mendistribusikan biner paket pra-bangun melalui file metadata bernama narinfo. Klien Guix mengirim permintaan HTTP ke endpoint /<store-hash>.narinfo untuk memverifikasi ketersediaan substitute sebelum mengunduh arsip NAR (Nix Archive). Pada repositori dengan jutaan revisi komit dan artefak build, backend SQL yang melayani metadata narinfo sering mengalami latensi tinggi akibat query lookup hash yang lambat, traversal closure dependensi yang tidak terindeks dengan baik, dan pagination yang tidak efisien.

Artikel ini membahas arsitektur skema PostgreSQL performa tinggi untuk metadata narinfo, eliminasi sequential scan melalui partial B-Tree indexing, optimasi relasi dependensi paket, serta penggantian pagination berbasis OFFSET dengan keyset pagination.

Struktur Data Narinfo dan Bottleneck pada Skala Besar

Spesifikasi narinfo Guix memuat atribut krusial seperti StorePath, URL arsip, Kompresi, NarHash (umumnya format nix-base32 atau sha256 murni), NarSize, References (daftar path store dependensi), dan Deriver. Setiap metadata juga memuat signature kriptografis dari kunci publik build farm (seperti Cuirass atau Berlin build farm).

Ada tiga titik bottleneck utama pada layer basis data metadata narinfo:

  • Hash Lookup Degradation: Kolom hash store path sering dicari secara exact-match. Menyimpan hash sebagai string teks tanpa tipe data yang presisi atau tanpa struktur indeks yang tepat memicu konsumsi disk besar dan cache thrashing.
  • Resolusi Closure Dependensi: Resolusi graf dependensi paket (atribut References) membutuhkan pencarian rekursif. Tanpa foreign key yang terindeks dua arah, query closure menghasilkan full table scan.
  • Pagination Degradation: Endpoint listing inventaris paket yang menggunakan pola OFFSET / LIMIT standar memaksa basis data memindai jutaan baris sebelum membuang tuple yang tidak diperlukan.

Perancangan Skema DDL PostgreSQL

Untuk efisiensi penyimpanan, hash 32-karakter atau SHA256 64-karakter hex sebaiknya dinormalisasi. Jika disimpan sebagai representasi biner murni (bytea), ukuran indeks berkurang drastis dibanding tipe data text. Namun, jika menggunakan format heksadesimal standar atau base32 karakter tetap, char(32) atau varchar(64) dengan collation deterministik "C" dapat memangkas overhead sorting lokal.

Skema DDL PostgreSQL berikut mengoptimalkan penyimpanan narinfo, kunci publik signing (keystore), dan relasi dependensi:

-- Tabel Keystore untuk verifikasi signature build farm
CREATE TABLE signing_keys (
    id SERIAL PRIMARY KEY,
    fingerprint VARCHAR(64) NOT NULL UNIQUE,
    public_key_data TEXT NOT NULL,
    is_revoked BOOLEAN NOT NULL DEFAULT FALSE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Tabel utama metadata Narinfo
CREATE TABLE narinfos (
    id BIGSERIAL PRIMARY KEY,
    store_hash VARCHAR(32) NOT NULL, -- Substring hash store path 32-karakter
    store_name VARCHAR(255) NOT NULL, -- Nama paket dan versi, misal: hello-2.12.1
    nar_hash BYTEA NOT NULL,          -- SHA-256 binary (32 bytes)
    nar_size BIGINT NOT NULL,
    file_size BIGINT NOT NULL,
    url VARCHAR(512) NOT NULL,
    compression VARCHAR(16) NOT NULL DEFAULT 'lzip',
    signing_key_id INT REFERENCES signing_keys(id),
    signature BYTEA NOT NULL,
    is_valid BOOLEAN NOT NULL DEFAULT TRUE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    CONSTRAINT uq_store_hash UNIQUE (store_hash)
);

-- Tabel relasi dependensi narinfo (References)
CREATE TABLE narinfo_references (
    narinfo_id BIGINT NOT NULL REFERENCES narinfos(id) ON DELETE CASCADE,
    reference_store_hash VARCHAR(32) NOT NULL,
    PRIMARY KEY (narinfo_id, reference_store_hash)
);

Strategi Indexing: Partial Index dan Indeks Graf Referensi

Klien Guix hanya tertarik pada narinfo yang valid dan belum di-revoke atau corrupt. Jika basis data menyimpan histori build gagal atau artefak usang (soft-deleted / invalid), membangun indeks atas seluruh baris membuang memori shared buffers PostgreSQL.

1. Partial Index untuk Hash Lookup

Daripada mengindeks seluruh entri pada kolom nar_hash atau store_hash, gunakan partial index yang hanya mencakup baris valid:

-- Indeks parsial untuk lookup hash substitusi saat validasi narinfo
CREATE INDEX idx_narinfos_active_lookup
ON narinfos (store_hash)
WHERE is_valid = TRUE;

-- Indeks untuk pencarian integritas nar_hash biner
CREATE INDEX idx_narinfos_active_nar_hash
ON narinfos (nar_hash)
WHERE is_valid = TRUE;

Penggunaan B-Tree pada PostgreSQL diutamakan daripada Hash Index murni. B-Tree di PostgreSQL mendukung write-ahead logging (WAL) yang efisien, ukuran halaman yang padat, dan mampu menangani scanning rentang bila diperlukan pada listing awalan hash.

2. Indeks Dependensi Dua Arah

Untuk menavigasi dependensi secara terbalik (mencari paket mana saja yang mereferensikan library tertentu), tambahkan indeks terbalik pada tabel relasi:

CREATE INDEX idx_narinfo_refs_reverse
ON narinfo_references (reference_store_hash, narinfo_id);

Analisis Eksekusi Query: EXPLAIN ANALYZE

Pengaruh indexing terlihat jelas saat mengeksekusi pencarian substitute hash di tabel dengan volume 5 juta baris.

Kondisi Sebelum Dibuat Indeks Parsial (Sequential Scan)

EXPLAIN ANALYZE
SELECT id, store_hash, store_name, url, nar_size
FROM narinfos
WHERE store_hash = '28qflr708k846w8j9g1vplw33n8f9p9x'
  AND is_valid = TRUE;

-- Hasil Execution Plan:
-- Seq Scan on narinfos  (cost=0.00..128450.00 rows=1 width=85) (actual time=412.312..412.314 rows=1 loops=1)
--   Filter: (is_valid AND ((store_hash)::text = '28qflr708k846w8j9g1vplw33n8f9p9x'::text))
--   Rows Removed by Filter: 4999999
-- Planning Time: 0.118 ms
-- Execution Time: 412.355 ms

Kondisi Sesudah Penambahan Partial B-Tree Index (Index Scan)

-- Hasil Execution Plan:
-- Index Scan using idx_narinfos_active_lookup on narinfos  (cost=0.43..8.45 rows=1 width=85) (actual time=0.038..0.040 rows=1 loops=1)
--   Index Cond: ((store_hash)::text = '28qflr708k846w8j9g1vplw33n8f9p9x'::text)
-- Planning Time: 0.142 ms
-- Execution Time: 0.062 ms

Waktu eksekusi terpangkas dari ~412 ms menjadi ~0.06 ms. Latensi pemrosesan turun drastis karena engine database membaca langsung leaf node index B-Tree tanpa memindai blok-blok disk dari tabel fisik secara menyeluruh.

Eliminasi Offset: Menerapkan Keyset Pagination

Klien sinkronisasi mirror substitute sering memindai katalog narinfo secara bertahap. Pola query umum seperti SELECT * FROM narinfos ORDER BY id LIMIT 50 OFFSET 1000000; memaksa database membaca 1.000.050 baris dan membuang 1.000.000 baris pertama.

Pendekatan keyset pagination (cursor-based pagination) memanfaatkan primary key atau kolom monotonik yang telah terindeks untuk langsung meloncat ke data target.

-- Keyset pagination menggunakan ID terakhir yang diproses
SELECT id, store_hash, store_name, url, nar_size, created_at
FROM narinfos
WHERE id > 1048576
  AND is_valid = TRUE
ORDER BY id ASC
LIMIT 50;

Plan query untuk keyset pagination selalu mempertahankan kompleksitas waktu deterministik O(log N + K) (di mana K adalah ukuran limit), menjaga latensi pagination stabil di bawah 1 ms terlepas dari seberapa dalam cursor telah berjalan.

Rekomendasi Maintenance Database Guix Narinfo

  • Vacuum Tuning: Tabel narinfos sering mengalami update parsial jika status validitas sering diperbarui. Turunkan autovacuum_vacuum_scale_factor menjadi 0.05 pada tabel tersebut agar bloat cepat dibersihkan.
  • Collation Determinism: Tetapkan kolom hash dengan collation eksplisit "C" saat instalasi DDL (misal: VARCHAR(32) COLLATE "C") untuk mencegah PostgreSQL menjalankan sorting berbasis locale lingkungan OS yang memperlambat pemrosesan string hashing.