[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-performa-postgresql-skala-index-vacuum-optimasi-query":3},{"article":4,"author":54},{"id":5,"category_id":6,"title":7,"slug":8,"excerpt":9,"content_md":10,"content_html":11,"locale":12,"author_id":13,"published":14,"published_at":15,"meta_title":16,"meta_description":17,"focus_keyword":18,"og_image":19,"canonical_url":19,"robots_meta":20,"created_at":15,"updated_at":15,"tags":21,"category_name":24,"related_articles":35},"d2000000-0000-0000-0000-000000000127","a0000000-0000-0000-0000-000000000025","Performa PostgreSQL pada Skala — Index, VACUUM, dan Optimasi Query","performa-postgresql-skala-index-vacuum-optimasi-query","Panduan optimasi performa PostgreSQL: jenis index, strategi VACUUM, teknik optimasi query, konfigurasi tuning, dan monitoring kesehatan database.","## PostgreSQL pada Skala\n\nPostgreSQL bisa menangani tabel dengan miliaran baris — tetapi hanya jika dikonfigurasi dan dimaintain dengan benar. Artikel ini membahas teknik-teknik kunci untuk menjaga performa PostgreSQL pada skala besar.\n\n## Jenis Index dan Kapan Menggunakannya\n\n### B-Tree (Default)\n```sql\nCREATE INDEX idx_articles_published_at ON articles(published_at DESC);\n```\nCocok untuk: equality, range, sorting. Ini adalah index default dan paling serbaguna.\n\n### GIN (Generalized Inverted Index)\n```sql\nCREATE INDEX idx_articles_tags ON articles USING gin(tags);\nCREATE INDEX idx_articles_content ON articles USING gin(to_tsvector('indonesian', content));\n```\nCocok untuk: full-text search, JSONB, array. Lambat untuk build\u002Fupdate, cepat untuk query.\n\n### GiST\n```sql\nCREATE INDEX idx_locations_coords ON locations USING gist(coordinates);\n```\nCocok untuk: spatial data, range type, nearest-neighbor search.\n\n### Partial Index\n```sql\nCREATE INDEX idx_articles_published ON articles(published_at DESC)\n    WHERE published = true;\n```\nHanya meng-index baris yang memenuhi kondisi. Ukuran index jauh lebih kecil.\n\n### Covering Index (INCLUDE)\n```sql\nCREATE INDEX idx_articles_lookup ON articles(locale, slug)\n    INCLUDE (title, excerpt);\n```\nMenghindari \"index-only scan\" harus kembali ke heap untuk kolom tambahan.\n\n## VACUUM: Kunci Performa Jangka Panjang\n\nPostgreSQL menggunakan MVCC (Multi-Version Concurrency Control). Setiap UPDATE membuat versi baru baris; versi lama menjadi \"dead tuple\". VACUUM membersihkan dead tuple ini.\n\n### Autovacuum\n```sql\n-- Konfigurasi autovacuum per tabel\nALTER TABLE articles SET (\n    autovacuum_vacuum_threshold = 100,\n    autovacuum_vacuum_scale_factor = 0.05,  -- 5% dead tuples\n    autovacuum_analyze_threshold = 50,\n    autovacuum_analyze_scale_factor = 0.02\n);\n```\n\n### Monitor Dead Tuples\n```sql\nSELECT\n    schemaname,\n    relname,\n    n_dead_tup,\n    n_live_tup,\n    round(n_dead_tup::numeric \u002F GREATEST(n_live_tup, 1) * 100, 2) AS dead_pct,\n    last_vacuum,\n    last_autovacuum\nFROM pg_stat_user_tables\nORDER BY n_dead_tup DESC\nLIMIT 10;\n```\n\nJika dead_pct > 20%, autovacuum tidak berjalan cukup cepat.\n\n## Optimasi Query\n\n### EXPLAIN ANALYZE\n```sql\nEXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)\nSELECT a.*, c.name\nFROM articles a\nJOIN categories c ON c.id = a.category_id\nWHERE a.locale = 'id' AND a.published = true\nORDER BY a.published_at DESC\nLIMIT 20;\n```\n\nPerhatikan:\n- **Seq Scan** pada tabel besar = missing index\n- **Nested Loop** dengan banyak iterasi = pertimbangkan Hash Join\n- **Buffers: shared hit** = data dari cache (baik)\n- **Buffers: shared read** = data dari disk (potensi bottleneck)\n\n### Common Table Expressions (CTE)\n```sql\n-- PostgreSQL 12+ mengoptimasi CTE secara otomatis\nWITH recent_articles AS (\n    SELECT * FROM articles\n    WHERE locale = 'id' AND published = true\n    ORDER BY published_at DESC\n    LIMIT 20\n)\nSELECT ra.*, array_agg(t.name) AS tag_names\nFROM recent_articles ra\nJOIN article_tags at ON at.article_id = ra.id\nJOIN tags t ON t.id = at.tag_id\nGROUP BY ra.id;\n```\n\n### Keyset Pagination (vs OFFSET)\n```sql\n-- BURUK: OFFSET melambat seiring halaman bertambah\nSELECT * FROM articles ORDER BY published_at DESC OFFSET 10000 LIMIT 20;\n\n-- BAIK: Keyset pagination — konstan\nSELECT * FROM articles\nWHERE published_at \u003C $1  -- last_seen_published_at\nORDER BY published_at DESC\nLIMIT 20;\n```\n\n## Konfigurasi Tuning\n\n```ini\n# postgresql.conf\nshared_buffers = 4GB          # 25% RAM\neffective_cache_size = 12GB   # 75% RAM\nwork_mem = 64MB               # Per-query sort\u002Fhash memory\nmaintenance_work_mem = 1GB    # VACUUM, CREATE INDEX\nwal_buffers = 64MB\nmax_connections = 200\nrandom_page_cost = 1.1        # SSD (default 4.0 untuk HDD)\neffective_io_concurrency = 200 # SSD\n```\n\n## Monitoring\n\n```sql\n-- Query paling lambat\nSELECT\n    calls,\n    round(total_exec_time::numeric, 2) AS total_ms,\n    round(mean_exec_time::numeric, 2) AS avg_ms,\n    query\nFROM pg_stat_statements\nORDER BY total_exec_time DESC\nLIMIT 10;\n```\n\n## Kesimpulan\n\nPerforma PostgreSQL pada skala memerlukan tiga pilar: index yang tepat, VACUUM yang terjaga, dan query yang teroptimasi. Monitoring aktif dengan pg_stat_statements dan pg_stat_user_tables memberikan visibilitas ke masalah sebelum pengguna merasakannya.","\u003Ch2 id=\"postgresql-pada-skala\">PostgreSQL pada Skala\u003C\u002Fh2>\n\u003Cp>PostgreSQL bisa menangani tabel dengan miliaran baris — tetapi hanya jika dikonfigurasi dan dimaintain dengan benar. Artikel ini membahas teknik-teknik kunci untuk menjaga performa PostgreSQL pada skala besar.\u003C\u002Fp>\n\u003Ch2 id=\"jenis-index-dan-kapan-menggunakannya\">Jenis Index dan Kapan Menggunakannya\u003C\u002Fh2>\n\u003Ch3>B-Tree (Default)\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-sql\">CREATE INDEX idx_articles_published_at ON articles(published_at DESC);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Cocok untuk: equality, range, sorting. Ini adalah index default dan paling serbaguna.\u003C\u002Fp>\n\u003Ch3>GIN (Generalized Inverted Index)\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-sql\">CREATE INDEX idx_articles_tags ON articles USING gin(tags);\nCREATE INDEX idx_articles_content ON articles USING gin(to_tsvector('indonesian', content));\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Cocok untuk: full-text search, JSONB, array. Lambat untuk build\u002Fupdate, cepat untuk query.\u003C\u002Fp>\n\u003Ch3>GiST\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-sql\">CREATE INDEX idx_locations_coords ON locations USING gist(coordinates);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Cocok untuk: spatial data, range type, nearest-neighbor search.\u003C\u002Fp>\n\u003Ch3>Partial Index\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-sql\">CREATE INDEX idx_articles_published ON articles(published_at DESC)\n    WHERE published = true;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Hanya meng-index baris yang memenuhi kondisi. Ukuran index jauh lebih kecil.\u003C\u002Fp>\n\u003Ch3>Covering Index (INCLUDE)\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-sql\">CREATE INDEX idx_articles_lookup ON articles(locale, slug)\n    INCLUDE (title, excerpt);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Menghindari “index-only scan” harus kembali ke heap untuk kolom tambahan.\u003C\u002Fp>\n\u003Ch2 id=\"vacuum-kunci-performa-jangka-panjang\">VACUUM: Kunci Performa Jangka Panjang\u003C\u002Fh2>\n\u003Cp>PostgreSQL menggunakan MVCC (Multi-Version Concurrency Control). Setiap UPDATE membuat versi baru baris; versi lama menjadi “dead tuple”. VACUUM membersihkan dead tuple ini.\u003C\u002Fp>\n\u003Ch3>Autovacuum\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-sql\">-- Konfigurasi autovacuum per tabel\nALTER TABLE articles SET (\n    autovacuum_vacuum_threshold = 100,\n    autovacuum_vacuum_scale_factor = 0.05,  -- 5% dead tuples\n    autovacuum_analyze_threshold = 50,\n    autovacuum_analyze_scale_factor = 0.02\n);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Monitor Dead Tuples\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-sql\">SELECT\n    schemaname,\n    relname,\n    n_dead_tup,\n    n_live_tup,\n    round(n_dead_tup::numeric \u002F GREATEST(n_live_tup, 1) * 100, 2) AS dead_pct,\n    last_vacuum,\n    last_autovacuum\nFROM pg_stat_user_tables\nORDER BY n_dead_tup DESC\nLIMIT 10;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Jika dead_pct &gt; 20%, autovacuum tidak berjalan cukup cepat.\u003C\u002Fp>\n\u003Ch2 id=\"optimasi-query\">Optimasi Query\u003C\u002Fh2>\n\u003Ch3>EXPLAIN ANALYZE\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-sql\">EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)\nSELECT a.*, c.name\nFROM articles a\nJOIN categories c ON c.id = a.category_id\nWHERE a.locale = 'id' AND a.published = true\nORDER BY a.published_at DESC\nLIMIT 20;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Perhatikan:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Seq Scan\u003C\u002Fstrong> pada tabel besar = missing index\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Nested Loop\u003C\u002Fstrong> dengan banyak iterasi = pertimbangkan Hash Join\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Buffers: shared hit\u003C\u002Fstrong> = data dari cache (baik)\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Buffers: shared read\u003C\u002Fstrong> = data dari disk (potensi bottleneck)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3>Common Table Expressions (CTE)\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-sql\">-- PostgreSQL 12+ mengoptimasi CTE secara otomatis\nWITH recent_articles AS (\n    SELECT * FROM articles\n    WHERE locale = 'id' AND published = true\n    ORDER BY published_at DESC\n    LIMIT 20\n)\nSELECT ra.*, array_agg(t.name) AS tag_names\nFROM recent_articles ra\nJOIN article_tags at ON at.article_id = ra.id\nJOIN tags t ON t.id = at.tag_id\nGROUP BY ra.id;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Keyset Pagination (vs OFFSET)\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-sql\">-- BURUK: OFFSET melambat seiring halaman bertambah\nSELECT * FROM articles ORDER BY published_at DESC OFFSET 10000 LIMIT 20;\n\n-- BAIK: Keyset pagination — konstan\nSELECT * FROM articles\nWHERE published_at &lt; $1  -- last_seen_published_at\nORDER BY published_at DESC\nLIMIT 20;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"konfigurasi-tuning\">Konfigurasi Tuning\u003C\u002Fh2>\n\u003Cpre>\u003Ccode class=\"language-ini\"># postgresql.conf\nshared_buffers = 4GB          # 25% RAM\neffective_cache_size = 12GB   # 75% RAM\nwork_mem = 64MB               # Per-query sort\u002Fhash memory\nmaintenance_work_mem = 1GB    # VACUUM, CREATE INDEX\nwal_buffers = 64MB\nmax_connections = 200\nrandom_page_cost = 1.1        # SSD (default 4.0 untuk HDD)\neffective_io_concurrency = 200 # SSD\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"monitoring\">Monitoring\u003C\u002Fh2>\n\u003Cpre>\u003Ccode class=\"language-sql\">-- Query paling lambat\nSELECT\n    calls,\n    round(total_exec_time::numeric, 2) AS total_ms,\n    round(mean_exec_time::numeric, 2) AS avg_ms,\n    query\nFROM pg_stat_statements\nORDER BY total_exec_time DESC\nLIMIT 10;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"kesimpulan\">Kesimpulan\u003C\u002Fh2>\n\u003Cp>Performa PostgreSQL pada skala memerlukan tiga pilar: index yang tepat, VACUUM yang terjaga, dan query yang teroptimasi. Monitoring aktif dengan pg_stat_statements dan pg_stat_user_tables memberikan visibilitas ke masalah sebelum pengguna merasakannya.\u003C\u002Fp>\n","id","b0000000-0000-0000-0000-000000000001",true,"2026-03-28T10:44:25.163657Z","Performa PostgreSQL — Index, VACUUM, dan Optimasi Query","Optimasi PostgreSQL: jenis index, strategi VACUUM, EXPLAIN ANALYZE, keyset pagination, dan konfigurasi tuning.","performa PostgreSQL",null,"index, follow",[22,27,31],{"id":23,"name":24,"slug":25,"created_at":26},"c0000000-0000-0000-0000-000000000012","DevOps","devops","2026-03-28T10:44:21.513630Z",{"id":28,"name":29,"slug":30,"created_at":26},"c0000000-0000-0000-0000-000000000022","Performance","performance",{"id":32,"name":33,"slug":34,"created_at":26},"c0000000-0000-0000-0000-000000000005","PostgreSQL","postgresql",[36,42,48],{"id":37,"title":38,"slug":39,"excerpt":40,"locale":12,"category_name":24,"published_at":41},"d0000000-0000-0000-0000-000000000644","Platform Engineering Memakan DevOps: Membangun Internal Developer Platform di 2026","platform-engineering-memakan-devops-membangun-idp-2026","80% organisasi engineering besar kini memiliki tim platform khusus, naik dari 45% di 2024. Internal developer platform — portal self-service, infrastruktur yang sudah disetujui, guardrail otomatis — telah menjadi cara standar untuk menghadirkan DevOps secara besar-besaran.","2026-03-28T10:44:47.476351Z",{"id":43,"title":44,"slug":45,"excerpt":46,"locale":12,"category_name":24,"published_at":47},"d0000000-0000-0000-0000-000000000643","Observabilitas Tanpa Instrumentasi: Bagaimana eBPF Menggantikan Armada Sidecar","observabilitas-tanpa-instrumentasi-ebpf-menggantikan-armada-sidecar","67% tim Kubernetes kini menggunakan alat observabilitas berbasis eBPF, naik dari 29% di 2024. Dengan memindahkan pengumpulan telemetri ke kernel, eBPF menghilangkan kontainer sidecar, memangkas penggunaan RAM sebesar 84%, dan memberikan overhead CPU di bawah 1%.","2026-03-28T10:44:47.469045Z",{"id":49,"title":50,"slug":51,"excerpt":52,"locale":12,"category_name":24,"published_at":53},"d0000000-0000-0000-0000-000000000642","WASI 0.3 dan Kematian Cold Start: Wasm Sisi Server di Produksi","wasi-0-3-kematian-cold-start-wasm-sisi-server-di-produksi","WASI 0.3 dirilis pada Februari 2026 dengan async I\u002FO native, tipe stream, dan dukungan socket penuh. WebAssembly sisi server kini menghadirkan cold start dalam hitungan mikrodetik, dan setiap penyedia cloud besar menawarkan Wasm serverless.","2026-03-28T10:44:47.445780Z",{"id":13,"name":55,"slug":56,"bio":57,"photo_url":19,"linkedin":19,"role":58,"created_at":59,"updated_at":59},"Open Soft Team","open-soft-team","The engineering team at Open Soft, building premium software solutions from Bali, Indonesia.","Engineering Team","2026-03-28T08:31:22.226811Z"]