Avoid Over-Indexing #

Setelah memahami berbagai teknik optimasi query dan kapan index diperlukan, ada sisi lain yang sama pentingnya: terlalu banyak index juga berbahaya. Index yang tidak perlu bukan sekadar tidak berguna — ia secara aktif merugikan sistem. Setiap INSERT, UPDATE, dan DELETE harus memperbarui semua index yang ada di tabel tersebut. Tabel dengan delapan index berarti setiap write operation memperbarui delapan struktur B-Tree sekaligus. Di sistem dengan ribuan write per detik, ini bisa menjadi bottleneck yang lebih besar dari query read yang lambat. Over-indexing adalah masalah yang tumbuh perlahan: satu index ditambahkan untuk menyelesaikan satu masalah query, lalu satu lagi, lalu satu lagi — sampai suatu hari write performance anjlok dan tidak ada yang tahu kenapa. Artikel ini membahas biaya nyata setiap index, cara menemukan index yang tidak terpakai dan redundant, proses menghapus index dengan aman, dan bagaimana menjaga jumlah index tetap proporsional dengan kebutuhan nyata.

Biaya Nyata Setiap Index Tambahan #

Banyak developer memandang index sebagai “read optimization” dan mengabaikan dampaknya ke write. Padahal setiap index adalah cost tetap untuk setiap operasi write.

Overhead per Write Operation #

-- Tabel orders dengan schema berikut (situasi over-indexing yang nyata):
CREATE TABLE orders (
    id          BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    user_id     BIGINT UNSIGNED NOT NULL,
    tenant_id   VARCHAR(36) NOT NULL,
    status      VARCHAR(20) NOT NULL,
    total       DECIMAL(15,2) NOT NULL,
    created_at  TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at  TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted_at  TIMESTAMP NULL,

    PRIMARY KEY (id),                                           -- index 1
    INDEX idx_orders_user_id (user_id),                        -- index 2
    INDEX idx_orders_tenant_id (tenant_id),                    -- index 3
    INDEX idx_orders_status (status),                          -- index 4
    INDEX idx_orders_created_at (created_at),                  -- index 5
    INDEX idx_orders_user_status (user_id, status),            -- index 6
    INDEX idx_orders_tenant_status (tenant_id, status),        -- index 7
    INDEX idx_orders_tenant_created (tenant_id, created_at),   -- index 8
    INDEX idx_orders_deleted_at (deleted_at)                   -- index 9
);
-- Total: 9 index di satu tabel

Biaya setiap INSERT INTO orders:

Operasi yang terjadi saat satu INSERT ke tabel di atas:
──────────────────────────────────────────────────────────────
  1. Tulis baris ke data page (clustered index / heap)
  2. Update B-Tree PRIMARY KEY
  3. Update B-Tree idx_orders_user_id
  4. Update B-Tree idx_orders_tenant_id
  5. Update B-Tree idx_orders_status
  6. Update B-Tree idx_orders_created_at
  7. Update B-Tree idx_orders_user_status
  8. Update B-Tree idx_orders_tenant_status
  9. Update B-Tree idx_orders_tenant_created
  10. Update B-Tree idx_orders_deleted_at
──────────────────────────────────────────────────────────────
  10 operasi B-Tree untuk 1 baris data.
  Masing-masing B-Tree update bisa memerlukan:
    - Navigasi pohon (O(log n))
    - Potensial page split jika halaman penuh
    - Tulis ke WAL/redo log

  Pada 10.000 INSERT/detik:
    → 100.000 B-Tree update/detik
    → vs 20.000 B-Tree update/detik jika hanya 2 index

  Perbedaan: 5× lebih banyak write I/O hanya dari index
──────────────────────────────────────────────────────────────

Mengukur Dampak Index ke Write Performance #

-- MySQL: pantau statistik handler untuk melihat index write overhead
SHOW GLOBAL STATUS LIKE 'Handler_write';
-- Handler_write meningkat setiap ada write ke tabel atau index

-- Cara lebih detail: lihat innodb_metrics
SELECT name, count
FROM information_schema.innodb_metrics
WHERE name IN (
    'index_page_writes',
    'index_page_splits',
    'buffer_pool_pages_dirty'
)
ORDER BY name;

-- Sebelum dan sesudah menghapus index yang tidak terpakai,
-- bandingkan nilai ini untuk melihat dampak nyatanya

Menemukan Index yang Tidak Pernah Dipakai #

Sebelum bisa membersihkan index, perlu tahu mana yang tidak berguna. Kedua database utama menyediakan cara untuk melacak penggunaan index.

Di MySQL #

-- MySQL 8.0+: sys schema punya view yang berguna
-- Cek index yang tidak pernah dipakai sejak server restart
SELECT
    t.table_schema,
    t.table_name,
    s.index_name,
    s.column_name,
    s.seq_in_index,
    s.cardinality,
    io.count_star AS times_used
FROM information_schema.statistics s
JOIN information_schema.tables t
    ON t.table_schema = s.table_schema
    AND t.table_name = s.table_name
LEFT JOIN performance_schema.table_io_waits_summary_by_index_usage io
    ON io.object_schema = s.table_schema
    AND io.object_name = s.table_name
    AND io.index_name = s.index_name
WHERE t.table_schema NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys')
  AND io.count_star = 0              -- belum pernah dipakai
  AND s.index_name != 'PRIMARY'      -- kecualikan primary key
ORDER BY t.table_name, s.index_name;

-- Catatan: data ini reset setiap server restart
-- Pastikan server sudah berjalan minimal 1-2 minggu sebelum audit
-- untuk memastikan semua pola query sudah terwakili
-- Alternatif: gunakan sys.schema_unused_indexes (MySQL 5.7+)
SELECT *
FROM sys.schema_unused_indexes
WHERE object_schema NOT IN ('mysql', 'sys')
ORDER BY object_schema, object_name;

Di PostgreSQL #

-- PostgreSQL: pg_stat_user_indexes menyimpan statistik penggunaan
SELECT
    schemaname,
    tablename,
    indexname,
    idx_scan,           -- berapa kali index dipakai sejak tracking dimulai
    idx_tup_read,       -- berapa baris dibaca via index
    idx_tup_fetch,      -- berapa baris fetched via index
    pg_size_pretty(pg_relation_size(indexrelid)) AS index_size
FROM pg_stat_user_indexes
WHERE idx_scan = 0                    -- tidak pernah dipakai
  AND schemaname = 'public'
ORDER BY pg_relation_size(indexrelid) DESC;  -- urutkan dari yang paling besar

-- Index yang tidak pernah dipakai dan ukurannya besar = kandidat hapus utama
-- Contoh output yang mengkhawatirkan:
-- idx_orders_deleted_at  | 0 scans | 45 MB   ← bayar 45MB storage untuk 0 manfaat
-- idx_orders_status      | 0 scans | 120 MB  ← mungkin tercakup oleh composite index

Menemukan Index Redundant #

Index redundant adalah index yang tidak pernah dipilih optimizer karena ada index lain yang lebih baik — biasanya karena ada composite index yang prefix-nya sudah mencakup single index tersebut.

-- Contoh redundant index yang umum:
-- orders.user_id punya dua index:
INDEX idx_orders_user_id (user_id)                  -- index A
INDEX idx_orders_user_status (user_id, status)       -- index B

-- Index A redundant karena:
-- Setiap query yang bisa pakai idx_orders_user_id (WHERE user_id = ?)
-- juga bisa pakai idx_orders_user_status (yang punya user_id sebagai prefix)
-- dan malah lebih optimal karena juga mencakup status
-- Optimizer hampir selalu akan pilih index B
-- Index A hanya menambah overhead write tanpa manfaat read

-- MySQL: cari redundant index
SELECT
    r.table_schema,
    r.table_name,
    r.index_name AS redundant_index,
    r.column_name AS redundant_columns,
    d.index_name AS dominant_index,
    d.column_name AS dominant_columns
FROM information_schema.statistics r
JOIN information_schema.statistics d
    ON r.table_schema = d.table_schema
    AND r.table_name  = d.table_name
    AND r.index_name  != d.index_name
    AND r.seq_in_index = d.seq_in_index
    AND r.column_name  = d.column_name
WHERE r.seq_in_index = 1  -- bandingkan kolom pertama
  AND r.index_name != 'PRIMARY'
ORDER BY r.table_name, r.index_name;

-- Atau gunakan sys.schema_redundant_indexes (MySQL 5.7+):
SELECT *
FROM sys.schema_redundant_indexes
WHERE table_schema NOT IN ('mysql', 'sys')
ORDER BY table_schema, table_name;
-- PostgreSQL: tidak ada built-in view, tapi bisa query pg_index
SELECT
    a.indexname AS index_a,
    b.indexname AS index_b,
    a.tablename
FROM pg_indexes a
JOIN pg_indexes b
    ON a.tablename = b.tablename
    AND a.indexname != b.indexname
    AND a.indexdef != b.indexdef
    -- Index A redundant jika kolom-kolomnya merupakan prefix dari Index B
    AND b.indexdef LIKE '%' || split_part(a.indexdef, '(', 2)
WHERE a.schemaname = 'public'
  AND b.schemaname = 'public';

-- Untuk analisis yang lebih presisi, gunakan extension pg_stat_statements
-- dan pg_index untuk memeriksa indkey (array kolom per index)

Pola Redundant yang Paling Umum #

-- Pola 1: single index yang prefix-nya sama dengan composite index
INDEX idx_a ON orders (user_id)              -- redundant
INDEX idx_b ON orders (user_id, status)      -- ini yang dipakai

-- Pola 2: composite index yang merupakan subset dari composite index lain
INDEX idx_c ON orders (tenant_id, status)    -- redundant
INDEX idx_d ON orders (tenant_id, status, created_at)  -- ini lebih lengkap

-- Pola 3: index yang kolom urutannya merupakan subset prefix
INDEX idx_e ON orders (user_id, status)      -- tetap berguna jika ada query
                                             -- WHERE user_id = ? AND status = ?
                                             -- tanpa created_at
INDEX idx_f ON orders (user_id, status, created_at)  -- berguna untuk query
                                                       -- yang juga ORDER BY created_at

-- Catatan: Pola 3 tidak selalu redundant — tergantung query yang ada.
-- Verifikasi dengan melihat actual query sebelum menghapus

Index dengan Selectivity Rendah yang Tidak Berguna #

Selectivity adalah rasio nilai unik terhadap total baris. Index pada kolom dengan sedikit nilai unik (seperti status, gender, boolean) sering kali tidak dipilih optimizer karena full scan lebih efisien.

-- Cek selectivity kolom:
SELECT
    COUNT(DISTINCT status) AS unique_values,
    COUNT(*) AS total_rows,
    COUNT(DISTINCT status) / COUNT(*) AS selectivity
FROM orders;

-- Contoh output:
-- unique_values: 4 (pending, paid, shipped, cancelled)
-- total_rows: 2.000.000
-- selectivity: 0.000002  ← sangat rendah

-- Index di kolom status dengan selectivity rendah:
-- Jika 'paid' ada di 40% baris (800.000 baris):
-- WHERE status = 'paid' dengan index → baca 800.000 baris via index
-- WHERE status = 'paid' tanpa index → full scan 2.000.000 baris
-- Optimizer sering pilih full scan karena overhead index lookup > full scan
-- untuk selectivity rendah!

-- Aturan praktis: index tunggal di kolom dengan < 5% unique values
-- hampir tidak pernah berguna kecuali dikombinasikan dalam composite index
SELECT
    column_name,
    COUNT(DISTINCT column_name) / COUNT(*) AS selectivity
FROM information_schema.columns c
-- query ini pseudo-code, implementasi aktual butuh dynamic SQL per kolom

Proses Audit dan Penghapusan Index yang Aman #

Menghapus index adalah operasi yang tidak bisa di-undo secara instan jika ada masalah (butuh waktu untuk rebuild). Lakukan dengan prosedur yang terstruktur.

Workflow Audit Index #

Proses audit index yang aman:
──────────────────────────────────────────────────────────────
  1. IDENTIFIKASI KANDIDAT (selama 2-4 minggu)
     → Kumpulkan data dari sys.schema_unused_indexes (MySQL)
       atau pg_stat_user_indexes (PostgreSQL)
     → Pastikan periode monitoring mencakup semua pola traffic
       (termasuk akhir bulan, laporan periodik, dll.)

  2. VERIFIKASI PER KANDIDAT
     → Untuk setiap index kandidat, cari query yang memakai kolom tersebut
     → Jalankan EXPLAIN untuk query tersebut TANPA index (gunakan IGNORE INDEX)
     → Verifikasi apakah ada index lain yang bisa mengambil alih

  3. STAGING FIRST
     → Drop index di staging environment terlebih dahulu
     → Monitor performa selama 1-2 hari dengan traffic replica
     → Jika tidak ada regresi, lanjut ke production

  4. PRODUCTION DROP
     → Pilih waktu low-traffic
     → Drop index (untuk tabel besar: perlu beberapa detik-menit)
     → Monitor query latency dan slow query log selama 24 jam

  5. ROLLBACK PLAN
     → Simpan DDL untuk re-create index jika ada masalah
     → Estimasi waktu rebuild (ALTER TABLE bisa lama untuk tabel besar)
──────────────────────────────────────────────────────────────

Cara Aman: Disable Sebelum Drop di MySQL 8+ #

MySQL 8.0 memperkenalkan fitur “invisible index” — index bisa disembunyikan dari optimizer tanpa dihapus, sehingga bisa diuji dulu dampaknya:

-- Langkah 1: buat index invisible (optimizer tidak memakainya, tapi masih diupdate)
ALTER TABLE orders ALTER INDEX idx_orders_status INVISIBLE;

-- Langkah 2: monitor selama 1-2 hari
-- Jika tidak ada query yang regresi → index memang tidak dibutuhkan

-- Langkah 3: jika aman, hapus permanen
DROP INDEX idx_orders_status ON orders;

-- Langkah 3 alternatif: jika ada masalah, kembalikan ke visible
ALTER TABLE orders ALTER INDEX idx_orders_status VISIBLE;

-- Fitur ini sangat berguna karena:
-- - Index tetap up-to-date (tidak ada re-build jika dikembalikan)
-- - Dampak ke write performance bisa diuji terlebih dahulu
-- - Rollback instan jika ada masalah
Invisible index tersedia di MySQL 8.0+ dan MariaDB 10.6+. Di PostgreSQL, belum ada fitur equivalen bawaan, tapi kamu bisa menggunakan pg_hint_plan extension untuk memaksa planner mengabaikan index tertentu sementara waktu untuk pengujian.

Studi Kasus: Membersihkan Over-Indexing di Tabel Orders #

Berikut contoh nyata proses identifikasi dan pembersihan, menggunakan tabel orders dari contoh di atas.

-- Kondisi awal: 9 index di tabel orders
-- Setelah 3 minggu monitoring, data penggunaan:

-- sys.schema_unused_indexes menunjukkan:
-- idx_orders_user_id    → 0 kali dipakai
-- idx_orders_tenant_id  → 0 kali dipakai
-- idx_orders_status     → 0 kali dipakai
-- idx_orders_created_at → 3 kali dipakai (query laporan bulanan)
-- idx_orders_deleted_at → 0 kali dipakai

-- sys.schema_redundant_indexes menunjukkan:
-- idx_orders_user_id   → redundant, tercakup oleh idx_orders_user_status
-- idx_orders_tenant_id → redundant, tercakup oleh idx_orders_tenant_status

-- Analisis lebih lanjut untuk yang tidak terpakai:
-- idx_orders_status: semua query pakai composite index
--   (tenant_id, status) atau (user_id, status)
-- idx_orders_deleted_at: semua query yang filter deleted_at IS NULL
--   menggunakan composite index lain yang juga mencakup kondisi ini

-- Keputusan:
-- Hapus: idx_orders_user_id (redundant)
-- Hapus: idx_orders_tenant_id (redundant)
-- Hapus: idx_orders_status (tidak terpakai, tercakup composite)
-- Hapus: idx_orders_deleted_at (tidak terpakai)
-- Pertahankan: idx_orders_created_at (dipakai laporan bulanan)
-- Pertahankan: idx_orders_user_status (dipakai query utama)
-- Pertahankan: idx_orders_tenant_status (dipakai query utama)
-- Pertahankan: idx_orders_tenant_created (dipakai query listing)

-- Dari 9 index → 5 index (hapus 4 yang tidak berguna)

-- Proses dengan invisible index dulu:
ALTER TABLE orders ALTER INDEX idx_orders_user_id INVISIBLE;
ALTER TABLE orders ALTER INDEX idx_orders_tenant_id INVISIBLE;
ALTER TABLE orders ALTER INDEX idx_orders_status INVISIBLE;
ALTER TABLE orders ALTER INDEX idx_orders_deleted_at INVISIBLE;
-- Monitor 2 hari...
-- Tidak ada regresi → drop permanen:
DROP INDEX idx_orders_user_id ON orders;
DROP INDEX idx_orders_tenant_id ON orders;
DROP INDEX idx_orders_status ON orders;
DROP INDEX idx_orders_deleted_at ON orders;

Dampak yang diharapkan setelah pembersihan:

Sebelum: 9 index → Sesudah: 5 index

Dampak ke write performance:
  INSERT: update 9 B-Tree → update 5 B-Tree (~44% lebih sedikit)
  UPDATE status: update semua index yang mengandung status
    Sebelum: 3 index (idx_status, idx_user_status, idx_tenant_status)
    Sesudah: 2 index (idx_user_status, idx_tenant_status)
  Throughput INSERT meningkat signifikan di tabel write-heavy

Dampak ke storage:
  Ukuran index yang dihapus: ~200MB (tergantung jumlah data)
  Backup lebih kecil, restore lebih cepat
  Replication traffic berkurang

Prinsip Mendesain Index yang Proporsional #

Aturan Jumlah Index per Tabel #

Tidak ada angka pasti, tapi ada panduan umum berdasarkan karakteristik tabel:

Panduan jumlah index berdasarkan workload:
──────────────────────────────────────────────────────────────
  Read-heavy table (lookup table, catalog, referensi):
    Bisa toleransi lebih banyak index
    5-10 index masih acceptable jika semua terpakai

  Balanced table (order, produk, user):
    3-6 index biasanya cukup
    Lebih dari 8 index perlu justifikasi kuat

  Write-heavy table (event log, audit trail, metrics):
    Minimal mungkin — 2-3 index ideal
    Setiap index harus terbukti memberikan manfaat besar

  Prinsip: semakin tinggi write rate, semakin ketat seleksi index
──────────────────────────────────────────────────────────────

Pertanyaan yang Harus Dijawab Sebelum Menambah Index #

Sebelum CREATE INDEX, jawab pertanyaan ini:

Justifikasi index yang sehat:
──────────────────────────────────────────────────────────────
  1. Query apa yang diselamatkan oleh index ini?
     → Bisa sebut query yang konkret, bukan asumsi?

  2. Seberapa sering query ini dijalankan?
     → Query yang dipanggil 10x/menit layak dapat index
     → Query yang dipanggil 1x/bulan (laporan) mungkin tidak

  3. Apakah ada composite index yang sudah mencakup ini?
     → Jika ya, index baru ini redundant

  4. Apa trade-off ke write performance?
     → Di tabel write-heavy, apakah manfaat read > biaya write?

  5. Apakah EXPLAIN tanpa index ini menunjukkan performa yang benar-benar tidak acceptable?
     → Atau hanya "mungkin lebih cepat"?

  Jika tidak bisa menjawab pertanyaan 1 dan 2 dengan konkret,
  jangan tambahkan index tersebut.
──────────────────────────────────────────────────────────────

Anti-Pattern yang Harus Dihindari #

-- ✗ Anti-pattern 1: index semua kolom "supaya aman"
CREATE INDEX idx_col1 ON orders (user_id);
CREATE INDEX idx_col2 ON orders (status);
CREATE INDEX idx_col3 ON orders (created_at);
CREATE INDEX idx_col4 ON orders (updated_at);
CREATE INDEX idx_col5 ON orders (tenant_id);
-- ✓ Solusi: analisis query nyata, buat composite index yang tepat

-- ✗ Anti-pattern 2: index sebelum ada query
-- "Nanti pasti ada query yang butuh ini"
CREATE INDEX idx_orders_meta ON orders (metadata_type, source_system);
-- Kolom ini belum pernah ada di satu pun query WHERE
-- ✓ Solusi: tunggu sampai ada kebutuhan nyata

-- ✗ Anti-pattern 3: tidak pernah mengaudit index
-- Tabel dengan 12 index, tapi 5 di antaranya tidak pernah dipakai
-- ✓ Solusi: audit berkala setiap 3-6 bulan

-- ✗ Anti-pattern 4: hapus index tanpa periode monitoring
DROP INDEX idx_orders_status ON orders;
-- Langsung hapus tanpa uji dampak → mungkin ada query jarang
-- yang tiba-tiba lambat
-- ✓ Solusi: invisible index dulu, monitor, baru drop

-- ✗ Anti-pattern 5: tidak memperhitungkan query periodik
-- Index idx_orders_created_at tidak dipakai sehari-hari,
-- tapi dipakai oleh laporan bulanan
-- Audit 3 hari → "tidak terpakai" → dihapus → laporan bulanan crash
-- ✓ Solusi: periode audit minimal 4-6 minggu untuk menangkap semua pola

Checklist Audit Index #

IDENTIFIKASI KANDIDAT HAPUS:
  □ Sudah jalankan query sys.schema_unused_indexes atau pg_stat_user_indexes?
  □ Periode monitoring sudah mencakup minimal 4 minggu (termasuk akhir bulan)?
  □ Sudah cek sys.schema_redundant_indexes untuk single index yang redundant?
  □ Sudah cek selectivity kolom untuk index tunggal di kolom bernilai sedikit?

VERIFIKASI SEBELUM HAPUS:
  □ Sudah EXPLAIN query yang relevan dengan IGNORE INDEX untuk verifikasi dampak?
  □ Sudah uji di staging environment terlebih dahulu?
  □ Sudah pakai invisible index (MySQL 8+) untuk uji di production?
  □ Sudah siapkan DDL untuk re-create jika perlu rollback?

EKSEKUSI:
  □ Drop dilakukan di jam low-traffic?
  □ Slow query log aktif dan dipantau selama 24 jam setelah drop?
  □ Ada dashboard yang menampilkan write latency untuk deteksi regresi?

UNTUK INDEX BARU:
  □ Ada query konkret yang membutuhkan index ini?
  □ Query ini sering dijalankan (bukan hanya sesekali)?
  □ Tidak ada composite index yang sudah mencakup kebutuhan ini?
  □ Dampak ke write performance sudah diperhitungkan?

Ringkasan #

  • Setiap index adalah biaya write yang permanen — setiap INSERT, UPDATE, dan DELETE harus memperbarui semua index di tabel. Tabel dengan 9 index memperbarui 9 B-Tree untuk setiap write; dengan 5 index hanya 5. Pada tabel write-heavy, ini perbedaan yang sangat signifikan.
  • Index yang tidak terpakai tetap membebani write — index yang tidak pernah dipilih optimizer tetap diupdate setiap ada write. Ini adalah biaya murni tanpa manfaat apapun. Harus dihapus.
  • Single index yang prefix-nya sama dengan composite index adalah redundantINDEX (user_id) tidak berguna jika sudah ada INDEX (user_id, status) karena optimizer selalu memilih yang lebih lengkap.
  • Index di kolom selectivity rendah sering tidak terpakai — kolom dengan 3-5 nilai unik di jutaan baris hampir tidak pernah menguntungkan di single index. Berguna hanya jika bagian dari composite index yang dimulai dengan kolom selectivity tinggi.
  • Audit minimal setiap 3-6 bulan — index yang ditambahkan satu tahun lalu mungkin sudah tidak relevan karena pola query berubah. Gunakan sys.schema_unused_indexes (MySQL) atau pg_stat_user_indexes (PostgreSQL) secara rutin.
  • Invisible index di MySQL 8+ adalah safety net untuk penghapusan — sembunyikan index dari optimizer selama beberapa hari sebelum menghapusnya. Jika tidak ada regresi, hapus permanen. Jika ada masalah, kembalikan ke visible tanpa rebuild.
  • Periode monitoring harus mencakup semua pola traffic — audit selama 3 hari mungkin melewatkan query laporan bulanan. Minimal 4-6 minggu untuk menangkap semua pola termasuk query periodik.
  • Jumlah index ideal berbanding terbalik dengan write rate — tabel event log atau audit trail yang diisi ribuan kali per detik harus punya index sesedikit mungkin (2-3); tabel lookup statis boleh punya lebih banyak.

← Sebelumnya: Index with Sort
About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact