Membangun diagram Entity Relationship (ERD) otomatis dalam pipeline CI/CD sering kali terbentur masalah performa saat database memiliki lebih dari 1000 tabel. Pendekatan standar yang memanfaatkan information_schema dan tool rendering berbasis Node.js/Puppeteer memicu runtime timeout dan konsumsi memori tinggi di worker CI.

Bottleneck Ekstraksi Metadata: information_schema vs pg_catalog

Standar ANSI SQL information_schema dirancang untuk portabilitas, bukan performa. Di PostgreSQL, view seperti information_schema.columns dan information_schema.key_column_usage membungkus puluhan subquery, type casting, evaluasi hak akses (has_table_privilege), dan join berlapis ke tabel internal.

Ketika catalog mengevaluasi ribuan relasi secara rekursif, optimizer database menghasilkan nested loop yang mahal. Untuk mengekstrak relasi tabel, foreign key, dan primary key secara efisien, kueri harus diarahkan langsung ke system catalog: pg_catalog.pg_class, pg_catalog.pg_constraint, dan pg_catalog.pg_attribute.

Kueri Lambat via information_schema

Pendekatan naif menggunakan view standar berikut membutuhkan pemindaian hak akses per-baris yang memperlambat database terdistribusi atau instance produksi dengan IOPS terbatas:

SELECT 
    tc.table_schema, tc.table_name, kcu.column_name,
    ccu.table_name AS foreign_table_name,
    ccu.column_name AS foreign_column_name
FROM information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage AS kcu
    ON tc.constraint_name = kcu.constraint_name
JOIN information_schema.constraint_column_usage AS ccu
    ON ccu.constraint_name = tc.constraint_name
WHERE tc.constraint_type = 'FOREIGN KEY';

Kueri Alternatif: Direct Catalog Scan

Query langsung ke PostgreSQL catalog memotong overhead evaluasi permission dan join kompleks. Kueri berikut mengekstrak seluruh relasi foreign key pada skema tertentu dengan efisiensi tinggi:

SELECT
    src_ns.nspname AS schema_name,
    src_tbl.relname AS source_table,
    src_attr.attname AS source_column,
    tgt_ns.nspname AS target_schema,
    tgt_tbl.relname AS target_table,
    tgt_attr.attname AS target_column
FROM pg_catalog.pg_constraint c
JOIN pg_catalog.pg_class src_tbl ON src_tbl.oid = c.conrelid
JOIN pg_catalog.pg_namespace src_ns ON src_ns.oid = src_tbl.relnamespace
JOIN pg_catalog.pg_class tgt_tbl ON tgt_tbl.oid = c.confrelid
JOIN pg_catalog.pg_namespace tgt_ns ON tgt_ns.oid = tgt_tbl.relnamespace
CROSS JOIN LATERAL generate_subscripts(c.conkey, 1) s(i)
JOIN pg_catalog.pg_attribute src_attr 
    ON src_attr.attrelid = c.conrelid AND src_attr.attnum = c.conkey[s.i]
JOIN pg_catalog.pg_attribute tgt_attr 
    ON tgt_attr.attrelid = c.confrelid AND tgt_attr.attnum = c.confkey[s.i]
WHERE c.contype = 'f'
  AND src_ns.nspname = 'public';

Operasi LATERAL generate_subscripts menangani composite foreign key secara native berbasis indeks array atribut, menghilangkan kebutuhan scanning teks nama konstrain.

Indexing dan Metadata Partitioning

Meskipun catalog internal terindeks, querying skema raksasa sering terbentur pembacaan tabel partisi yang tidak relevan. Buat filter eksplisit untuk mengabaikan partisi anak (child partitions) agar graf relasi tetap ringkas:

-- Hanya ambil tabel utama/partisi induk
WHERE src_tbl.relkind IN ('r', 'p') -- regular table & partitioned table
  AND NOT src_tbl.relispartition

Jika skema database Anda menggunakan arsitektur multitenancy dengan ribuan skema terpisah, batasi analisis hanya pada target aktif menggunakan filter namespace yang terdefinisi jelas daripada melakukan wildcard scanning.

Implementasi Headless CLI via Rust Tanpa Chromium

Penggunaan Mermaid CLI (@mermaid-js/mermaid-cli) membutuhkan instalasi Node.js dan eksekusi headless Chromium di dalam pipeline CI/CD. Hal ini menambah footprint image Docker hingga lebih dari 500MB dan memakan ratusan megabyte RAM hanya untuk merender SVG/teks.

Alternatif yang jauh lebih efisien adalah menulis CLI ringan berbasis Rust (seperti crate merman atau generator berbasis AST internal). Binary Rust mandiri dapat querying database, mengonversi struktur ke format Mermaid atau DOT, lalu mengekspor teks tersebut langsung.

Contoh CLI Rust: Querying Metadata ke Sintaks Mermaid

Berikut implementasi minimal menggunakan sqlx untuk mengekstrak data dari PostgreSQL secara streaming dan mencetaknya langsung ke output Mermaid ERD:

use sqlx::postgres::PgPoolOptions;
use sqlx::Row;
use std::env;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let db_url = env::var("DATABASE_URL").expect("DATABASE_URL wajib diisi");
    let pool = PgPoolOptions::new()
        .max_connections(2)
        .connect(&db_url)
        .await?;

    let query = r#"
        SELECT
            src_tbl.relname AS src_table,
            tgt_tbl.relname AS tgt_table
        FROM pg_catalog.pg_constraint c
        JOIN pg_catalog.pg_class src_tbl ON src_tbl.oid = c.conrelid
        JOIN pg_catalog.pg_class tgt_tbl ON tgt_tbl.oid = c.confrelid
        JOIN pg_catalog.pg_namespace src_ns ON src_ns.oid = src_tbl.relnamespace
        WHERE c.contype = 'f' AND src_ns.nspname = 'public'
        GROUP BY src_tbl.relname, tgt_tbl.relname;
    "#;

    let rows = sqlx::query(query).fetch_all(&pool).await?;

    println!("erDiagram");
    for row in rows {
        let src: String = row.get("src_table");
        let tgt: String = row.get("tgt_table");
        println!("    {} }}|..|{{ {} : references", src, tgt);
    }

    Ok(())
}

Pipeline CI/CD: Eksekusi Efisien Tanpa Node.js

Simpan binary Rust hasil kompilasi atau jalankan via stage caching. Integrasikan langsung ke dalam workflow GitHub Actions atau GitLab CI untuk memperbarui dokumentasi skema secara terjadwal:

steps:
  - uses: actions/checkout@v4
  - name: Run Schema Indexer CLI
    env:
      DATABASE_URL: ${{ secrets.READONLY_DB_URL }}
    run: |
      ./bin/schema-erd-generator > docs/diagram.mmd
  - name: Commit Updated ERD
    run: |
      git config user.name 'github-actions[bot]'
      git config user.email 'github-actions[bot]@users.noreply.github.com'
      git add docs/diagram.mmd
      git diff-index --quiet HEAD || git commit -m 'chore: update ERD metadata' && git push

Trade-offs dan Kesimpulan

Memotong ketergantungan pada information_schema dan runtime Node.js memberikan peningkatan performa signifikan pada repositori besar:

  • Waktu Eksekusi: Query berbasis catalog berjalan dalam hitungan milidetik, bahkan pada database dengan ribuan tabel.
  • Kebutuhan Pipeline: Image CI/CD tetap ramping karena tidak memerlukan dependensi browser headless.
  • Kelemahan: Query langsung ke pg_catalog terikat pada PostgreSQL engine dan memerlukan penyesuaian khusus jika ingin diimplementasikan pada database lain seperti MySQL (mysql.innodb_tables).