Membaca Hasil EXPLAIN MySQL: Memahami Execution Plan untuk Optimasi Query

Sebelumnya saya sudah belajar tentang single index dan composite index di MySQL. Tapi pertanyaan selanjutnya: bagaimana cara memastikan index yang sudah dibuat benar-benar dipakai oleh MySQL? Jawabannya ada di perintah EXPLAIN. Hari ini saya mencatat bagaimana membaca hasil EXPLAIN dan menghubungkannya dengan pemahaman tentang index.

EXPLAIN: Jendela ke Otak MySQL

EXPLAIN adalah perintah di MySQL yang menampilkan execution plan — rencana eksekusi yang akan dilakukan MySQL untuk menjalankan sebuah query. Dengan EXPLAIN, kita bisa melihat apakah index sudah terpakai, berapa banyak baris yang harus diperiksa, dan apakah ada bottleneck tersembunyi.

Cara pakainya sangat sederhana — tinggal tambahkan EXPLAIN di depan query:

-- Tambah EXPLAIN di depan SELECT
EXPLAIN SELECT * FROM users WHERE email = 'rudy@example.com';

-- Bisa juga untuk query kompleks
EXPLAIN SELECT u.nama, COUNT(o.id) AS total_order
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE u.status = 'active'
GROUP BY u.id;

Setiap Kolom di EXPLAIN: Apa Artinya?

Hasil EXPLAIN berupa tabel dengan beberapa kolom. Berikut penjelasan masing-masing kolom yang paling penting untuk dipahami:

1. id — Nomor Urut Query

Setiap SELECT dalam query diberi nomor id. Jika dua baris punya id yang sama, mereka dieksekusi bersama (misalnya saat JOIN). Jika id berbeda, mereka dieksekusi secara terpisah — misalnya subquery yang dijalankan untuk setiap baris dari outer query.

2. select_type — Jenis SELECT

select_typeArtiKapan Muncul
SIMPLEQuery tanpa subquery/UNIONSELECT * FROM t WHERE x=1
PRIMARYSELECT terluarOuter query dari subquery/UNION
SUBQUERYSubquery independentDijalankan sekali saja
DEPENDENT SUBQUERYSubquery bergantung outer query⚠️ Dijalankan ulang N× per baris outer
MATERIALIZEDSubquery di-materialize ke tabel sementaraHasilnya di-cache, lalu di-lookup
DERIVEDSubquery di FROM clauseMySQL buat tabel sementara

Yang paling perlu diwaspadai adalah DEPENDENT SUBQUERY — ini artinya subquery dijalankan ulang untuk setiap baris dari tabel induk. Semakin banyak data, semakin lambat.

3. table — Tabel yang Diakses

Nama tabel atau alias yang sedang diakses. Jika tertulis <subqueryN>, berarti itu hasil materialized dari subquery nomor N.

4. type — Cara MySQL Mengambil Baris ⭐

Ini adalah kolom paling penting di EXPLAIN. Kolom type menunjukkan cara MySQL mengambil data dari tabel. Semuanya diurutkan dari yang paling cepat ke yang paling lambat:

typeArtiKecepatan
systemTabel hanya punya 1 baris⚡ Instan
constPrimary/unique key, hasil 1 baris⚡ Instan
eq_refJOIN pakai primary/unique, 1 baris per kombinasi⚡⚡ Sangat cepat
refPakai index biasa, bisa lebih dari 1 baris⚡⚡ Cepat
rangeRange scan (BETWEEN, >, <, IN)✅ Bagus
indexFull index scan (bukan full table)⚠️ Lambat
ALLFull table scan — tanpa index🐌 Terburuk

Rumus cepat: kalau type adalah ALL atau index di tabel yang besar (ribuan baris ke atas), itu tanda ada masalah yang perlu diperbaiki.

5. possible_keys vs key — Index Kandidat vs Index Terpakai

possible_keys menunjukkan index yang bisa dipakai MySQL, sedangkan key menunjukkan index yang benar-benar dipakai. Jika key bernilai NULL, berarti MySQL memilih full table scan karena dianggap lebih cepat.

Ini kembali ke pembahasan single index vs composite index — tidak semua index yang tersedia akan dipakai. MySQL memilih index yang paling efisien berdasarkan selectivitas (seberapa banyak baris yang bisa di-filter).

6. key_len — Panjang Byte Index yang Dipakai

Menunjukkan berapa byte dari index yang benar-benar digunakan. Ini berguna untuk memahami apakah composite index terpakai penuh atau hanya sebagian. Contoh:

-- Composite index: (survei_id, deleted_at, responden_type, responden_id)
-- key_len: 8  → Hanya survei_id yang terpakai (BIGINT = 8 byte)
-- key_len: 13 → survei_id + deleted_at terpakai (8 + 5)
-- key_len: 18 → survei_id + deleted_at + responden_type terpakai

Jika key_len kecil padahal composite index-nya punya banyak kolom, berarti hanya sebagian kolom index yang aktif — bisa jadi karena ada gap di leftmost prefix (kembali lagi ke aturan composite index sebelumnya).

7. ref — Apa yang Dibandingkan dengan Index

Nilai refArti
constDibandingkan dengan nilai konstan (WHERE x = 5)
funcDibandingkan dengan hasil fungsi (WHERE YEAR(tgl) = 2025)
table.columnDibandingkan dengan kolom dari tabel lain (saat JOIN)
NULLTidak ada referensi

8. rows — Estimasi Jumlah Baris yang Diperiksa ⭐

Kolom kedua paling penting setelah type. Menunjukkan berapa baris yang estimasi harus diperiksa MySQL. Semakin kecil, semakin cepat. Jika angkanya mendekati total baris di tabel, berarti filter tidak selektif atau index tidak efektif.

Catatan: angka ini adalah estimasi optimizer, bukan jumlah aktual. Untuk memastikan statistik akurat, jalankan:

ANALYZE TABLE nama_tabel;

9. Extra — Info Tambahan

ExtraArtiPenilaian
Using whereFilter dilakukan setelah MySQL ambil dataNormal
Using indexCovering index — tidak perlu akses tabel utama✅ Bagus
Using temporaryPakai tabel sementara (GROUP BY, DISTINCT)⚠️ Hati-hati
Using filesortSorting tidak pakai index⚠️ Lambat kalau data banyak
Using intersect()MySQL menggabungkan 2 index (index merge)⚠️ Pertimbangkan composite index
Using index conditionIndex Condition Pushdown — filter di engine storage✅ Bagus

Yang perlu diwaspadai: Using temporary + Using filesort bersamaan — ini menunjukkan query yang cukup mahal karena MySQL harus membuat tabel sementara DAN mengurutkannya tanpa bantuan index.

Red Flags: Tanda-tanda Query Bermasalah

Setelah memahami setiap kolom, berikut ringkasan tanda-tanda yang harus diwaspadai saat membaca hasil EXPLAIN:

  • type: ALL di tabel lebih dari 1.000 baris → full table scan, index tidak terpakai
  • select_type: DEPENDENT SUBQUERY → subquery dijalankan ulang N× per baris outer query
  • key: NULL padahal possible_keys ada → MySQL menolak index karena dianggap tidak efisien
  • rows mendekati total baris tabel → filter tidak selektif
  • Extra: Using intersect() → MySQL harus menggabungkan 2 index (index merge), lebih baik pakai composite index tunggal

Studi Kasus: Dari EXPLAIN ke Perbaikan

Mari kita lihat contoh nyata. Misalnya ada query survei yang hasil EXPLAIN-nya menunjukkan masalah:

EXPLAIN SELECT
  s.nama,
  (SELECT COUNT(*)
   FROM v2_survei_pertanyaan p
   WHERE p.survei_id = s.id
     AND p.deleted_at IS NULL
     AND p.is_required = 1) AS pertanyaan_wajib,
  (SELECT COUNT(*)
   FROM v2_survei_respon r
   WHERE r.survei_id = s.id
     AND r.deleted_at IS NULL
     AND r.responden_type = 'tendik'
     AND r.responden_id = 1536) AS pertanyaan_dijawab
FROM v2_survei s
WHERE s.deleted_at IS NULL;

Hasil EXPLAIN-nya:

idselect_typetabletypepossible_keyskeykey_lenrefrowsExtra
1PRIMARYsALLidx_deleted_atNULLNULLNULL48Using where
2DEPENDENT SUBQUERYv2_survei_pertanyaanrefsurvei_id, idx_deleted_atsurvei_id8survei.s.id10Using where
3DEPENDENT SUBQUERYv2_survei_responrefdeleted_at, responden_type, survei_idsurvei_id8survei.s.id26585Using where

Ada tiga masalah yang bisa diidentifikasi:

Masalah 1: Row 3 — 26.585 baris diperiksa per subquery. Dengan 48 survei, total baris yang harus diperiksa: 48 × 26.585 = ~1,27 juta baris. Penyebabnya: index (deleted_at, responden_type, survei_id, responden_id) tidak efisien untuk query yang filter by survei_id dulu — karena survei_id bukan kolom paling kiri di index.

Masalah 2: DEPENDENT SUBQUERY di row 2 dan 3. Kedua subquery di SELECT dijalankan ulang untuk setiap baris dari v2_survei. Ini pola yang harus dihindari.

Masalah 3: Kolom key_len hanya 8. Artinya dari composite index yang tersedia, hanya satu kolom yang terpakai. Sisa kolom index sia-sia.

Perbaikan: Tambah Index yang Benar

Karena query filter by survei_id duluan, survei_id harus jadi kolom pertama di composite index:

-- Urutan kolom mengikuti urutan filter di query:
-- WHERE survei_id = ? AND deleted_at IS NULL
--   AND responden_type = ? AND responden_id = ?
ALTER TABLE v2_survei_respon
  ADD INDEX idx_respon_fix (survei_id, deleted_at, responden_type, responden_id);

Perbaikan Lebih Baik: Rewrite ke JOIN

Selain menambah index, rewrite query dari DEPENDENT SUBQUERY ke JOIN bisa menghilangkan masalah secara total:

SELECT
  s.nama,
  COUNT(DISTINCT IF(
    p.is_required = 1 AND p.deleted_at IS NULL,
    p.id, NULL
  )) AS pertanyaan_wajib,
  COUNT(DISTINCT IF(
    r.responden_type = 'tendik' AND r.responden_id = 1536
    AND r.deleted_at IS NULL,
    r.id, NULL
  )) AS pertanyaan_dijawab
FROM v2_survei s
  INNER JOIN v2_survei_pertanyaan p
    ON p.survei_id = s.id
    AND p.deleted_at IS NULL
    AND p.jenis = 'pertanyaan'
    AND p.is_required = 1
  LEFT JOIN v2_survei_respon r
    ON r.survei_id = s.id
    AND r.deleted_at IS NULL
    AND r.responden_type = 'tendik'
    AND r.responden_id = 1536
    AND r.pertanyaan_id = p.id
WHERE s.deleted_at IS NULL
GROUP BY s.id, s.nama;

Query ini menjalankan scan sekali saja tanpa subquery berulang. Tapi ingat, keuntungan JOIN baru terasa jika jumlah baris di tabel cukup besar — untuk tabel kecil (48 baris), perbedaan waktunya mungkin tidak signifikan.

Hubungan EXPLAIN dengan Composite Index

Setelah memahami EXPLAIN, kita bisa memverifikasi apakah composite index yang dibuat sudah optimal. Beberapa poin kunci:

Pertama, perhatikan key_len. Jika composite index punya 4 kolom tapi key_len hanya menunjukkan 1 kolom, berarti ada kolom tengah yang di-skip. Ini kembali ke aturan leftmost prefix rule — MySQL tidak bisa melompati kolom di tengah composite index.

Kedua, jika Extra menunjukkan Using intersect(), artinya MySQL harus menggabungkan dua index secara terpisah (index merge). Ini kurang efisien dibandingkan satu composite index yang menampung semua kolom filter.

Ketiga, pastikan urutan kolom di composite index sesuai dengan urutan filter di query. Jika query selalu filter survei_id dulu, survei_id harus jadi kolom pertama di index. Kalau ada query lain yang filter tanpa survei_id, buat index terpisah untuk query tersebut — satu composite index tidak bisa melayani semua pola filter.

Cheat Sheet: Membaca EXPLAIN dengan Cepat

Berikut panduan cepat untuk memeriksa hasil EXPLAIN:

  1. Cek kolom type — pastikan bukan ALL di tabel besar
  2. Cek kolom key — jika NULL padahal seharusnya ada index, investigasi lebih lanjut
  3. Cek kolom rows — pastikan angka tidak mendekati total baris di tabel
  4. Cek kolom Extra — waspadai Using temporary, Using filesort, atau Using intersect()
  5. Cek kolom key_len — pastikan composite index terpakai penuh (bukan hanya kolom pertama)
  6. Jika ada DEPENDENT SUBQUERY, pertimbangkan rewrite ke JOIN

Kesimpulan

EXPLAIN adalah alat wajib sebelum mengoptimasi query. Tanpa EXPLAIN, kita hanya menebak-nebak. Dengan EXPLAIN, kita bisa melihat secara persis bagaimana MySQL merencanakan eksekusi query kita — index mana yang dipakai, berapa baris yang diperiksa, dan bottleneck di mana.

Tambahan index tanpa EXPLAIN ibarat obat tanpa diagnosis — mungkin cocok, mungkin tidak. Tapi dengan EXPLAIN, kita tahu persis masalahnya dan bisa memilih solusi yang tepat: menambah index baru, memperbaiki urutan kolom composite index, atau merewrite query dari subquery ke JOIN.

Seri belajar MySQL ini terus berlanjut. Setelah memahami perbedaan single dan composite index, sekarang kita punya cara untuk memverifikasi apakah index yang dibuat benar-benar bekerja.


Sumber

Belajar Laravel #3: Blade Dasar — Template Layout, Kondisi & Looping

Episode ketiga! Setelah di episode #2 kita sudah bisa membuat route dan controller pertama, sekarang waktunya belajar Blade — template engine bawaan Laravel yang bikin kita nggak perlu copy-paste HTML bolak-balik tiap buat halaman.

Jujur, pas pertama kali pakai Blade, saya agak bingung dengan syntax @-nya. Ternyata setelah beberapa jam utak-atik, Blade itu justru sangat ramah pemula. Intinya: tulis HTML biasa, tambahin sedikit directive @ untuk logika, dan biarkan Blade menggabungkannya.

Mengapa Pakai Template Engine?

Bayangkan punya 10 halaman dengan header & footer yang sama. Tanpa template engine, kita harus menulis ulang header & footer di setiap file PHP/HTML. Dengan Blade, kita pisahkan layout (yang diulang) dan konten (yang berbeda per halaman).

Tiga konsep utama Blade yang harus dipahami:

  • Layout inheritance@extends, @section, @yield
  • Conditional@if, @elseif, @else, @endif
  • Looping@foreach, @forelse, @endforeach

Layout dengan @extends, @section, @yield

Mari buat layout utama dulu. Di Laravel, file Blade disimpan di resources/views/. Buat file resources/views/layouts/app.blade.php:

<!DOCTYPE html>
<html lang="id">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@yield('title', 'Judul Default')</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <nav class="navbar navbar-dark bg-primary px-3">
        <a class="navbar-brand" href="/">Catatan Rudy</a>
    </nav>

    <div class="container mt-4">
        @yield('content')
    </div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

Perhatikan @yield('content') — ini placeholder yang akan diisi oleh child view. Judulnya juga pakai @yield dengan default value.

Sekarang buat halaman home di resources/views/home.blade.php:

@extends('layouts.app')

@section('title', 'Beranda')

@section('content')
    <h1>Selamat Datang!</h1>
    <p>Ini halaman pertama saya belajar Laravel.</p>
@endsection

Cukup tiga langkah: @extends untuk menyatakan pakai layout mana, @section('title', ...) untuk judul (satu baris), dan @section('content') ... @endsection untuk isi kontennya. Blade akan menggabungkan semuanya saat render.

Jangan lupa, di route-nya kita return view: return view('home'); — Laravel otomatis cari file home.blade.php.

Kondisi @if / @elseif / @else

Sama seperti if di PHP, tapi syntax-nya lebih ringkas:

@extends('layouts.app')

@section('content')
    <h1>Dashboard</h1>

    @if(isset($user))
        <p>Halo, {{ $user['name'] }}!</p>
    @else
        <p>Halo, tamu! Silakan <a href="/login">login</a>.</p>
    @endif
@endsection

Ada juga @unless, kebalikan dari @if — berguna kalau logikanya negatif. Dan @isset($var) / @empty($var) sebagai shortcut untuk pengecekan yang sering dipakai.

Looping dengan @foreach

Seringkali data dari database berupa array/collection. Blade punya @foreach untuk mengulang data:

@extends('layouts.app')

@section('content')
    <h1>Daftar Artikel</h1>

    @if(count($articles) > 0)
        <ul class="list-group">
            @foreach($articles as $article)
                <li class="list-group-item">
                    <strong>{{ $article['title'] }}</strong>
                    <br>
                    <small>{{ $article['date'] }}</small>
                </li>
            @endforeach
        </ul>
    @else
        <p class="text-muted">Belum ada artikel.</p>
    @endif
@endsection

Di controller-nya, kita kirim data sebagai array:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ArticleController extends Controller
{
    public function index()
    {
        $articles = [
            ['title' => 'Belajar Routing', 'date' => '2026-08-10'],
            ['title' => 'Belajar Blade', 'date' => '2026-08-12'],
        ];

        return view('articles.index', compact('articles'));
    }
}

Cara compact('articles') itu shortcut — secara otomatis jadi ['articles' => $articles]. Kalau suka eksplisit, boleh juga return view('articles.index', ['articles' => $articles]).

{{ }} vs {!! !!} — Output Aman vs Raw HTML

Ini bagian penting. Blade punya dua cara menampilkan variabel:

  • {{ $name }} — otomatis escaped (aman dari XSS). Jika $name = "<script>alert('hack')</script>", yang muncul teks biasa, bukan script jahat.
  • {!! $name !!} — output raw (tanpa escape). Hanya pakai kalau kamu pasti isinya aman, misalnya konten HTML dari editor tepercaya.

Aturan sederhana: selalu pakai {{ }} kecuali ada alasan kuat. Keamanan lebih penting dari kenyamanan.

Kesalahan Umum Pemula

  • Lupa @endsection — Blade akan error. Setiap @section harus ditutup.
  • @foreach tanpa @endforeach — sama, harus seimbang.
  • Nama view salah — kalau return view('home') tapi file-nya tidak ada di resources/views/home.blade.php, error View [home] not found.
  • Pakai {!! !!} sembarangan — bisa membuka celah XSS. Kalau ragu, pakai {{ }}.
  • Mixing PHP & Blade — bisa pakai @php ... @endphp, tapi sebisa mungkin pakai Blade directive. Lebih rapi, lebih mudah dibaca.

Kesimpulan

Blade itu sederhana tapi powerful. Dengan tiga konsep utama — layout inheritance, kondisi, dan looping — kita sudah bisa membuat halaman-halaman yang rapi tanpa duplikasi kode. Di episode berikutnya, kita akan refresh HTML & CSS supaya tampilan makin enak dilihat.

Sumber

Single Index vs Composite Index di MySQL: Kapan Pakai yang Mana?

Hari ini saya belajar tentang perbedaan single index dan composite index di MySQL. Topik ini sering muncul saat optimasi query yang lambat — saya sendiri kadang bingung kapan harus pakai yang mana. Catatan ini saya rangkum dari beberapa sumber, termasuk mysqltutorial.org, dev.to, dan datacamp.com. Semoga bermanfaat.

Apa itu Single Index?

Single index adalah indeks yang dibuat pada satu kolom saja. Ini adalah jenis indeks yang paling sederhana dan paling sering digunakan. Contohnya:

-- Single index pada kolom email
CREATE INDEX idx_users_email ON users(email);

-- Single index pada kolom status
CREATE INDEX idx_orders_status ON orders(status);

Single index cocok untuk query yang hanya melakukan filter pada satu kolom, seperti:

SELECT * FROM users WHERE email = 'rudy@example.com';
SELECT * FROM orders WHERE status = 'pending';

Kelebihannya: sederhana, mudah dipahami, dan index maintenance-nya ringan. Kekurangannya: tidak bisa membantu query yang filter pada banyak kolom sekaligus.

Apa itu Composite Index?

Composite index (atau multiple-column index) adalah indeks yang dibuat pada dua kolom atau lebih. MySQL memungkinkan composite index hingga 16 kolom. Contohnya:

-- Composite index pada kolom (status, created_at)
CREATE INDEX idx_orders_status_created ON orders(status, created_at);

-- Composite index pada kolom (user_id, product_id, quantity)
CREATE INDEX idx_order_items_composite ON order_items(user_id, product_id, quantity);

Composite index sangat powerful karena bisa melayani banyak jenis query dengan satu indeks saja. Tapi ada aturan penting yang harus dipahami: leftmost prefix rule.

Leftmost Prefix Rule: Aturan Terpenting

Ini adalah konsep kunci yang harus dipahami saat menggunakan composite index. MySQL hanya bisa menggunakan composite index jika query melakukan filter pada kolom-kolom yang dimulai dari kolom paling kiri (leftmost).

Misalnya kita punya composite index (status, created_at, user_id):

-- ✅ BISA menggunakan index (leftmost prefix: status)
SELECT * FROM orders WHERE status = 'pending';

-- ✅ BISA menggunakan index (leftmost prefix: status, created_at)
SELECT * FROM orders WHERE status = 'pending' AND created_at > '2026-01-01';

-- ✅ BISA menggunakan index (leftmost prefix: status, created_at, user_id)
SELECT * FROM orders WHERE status = 'pending' AND created_at > '2026-01-01' AND user_id = 100;

-- ❌ TIDAK BISA menggunakan index (skip kolom pertama: status)
SELECT * FROM orders WHERE created_at > '2026-01-01';

-- ❌ TIDAK BISA menggunakan index (skip kolom pertama: status)
SELECT * FROM orders WHERE created_at > '2026-01-01' AND user_id = 100;

Bayangkan composite index seperti buku telepon yang diurutkan berdasarkan: Kota → Kecamatan → Nama. Kamu bisa mencari semua orang di “Jakarta”, atau semua orang di “Jakarta Selatan”, tapi kamu tidak bisa langsung mencari semua orang di “Kecamatan X” tanpa menyebut kota dulu.

Perbandingan: Single vs Composite Index

AspekSingle IndexComposite Index
DefinisiIndex pada 1 kolomIndex pada 2+ kolom
Query yang dilayaniFilter 1 kolom sajaFilter 1, 2, atau N kolom (sesuai leftmost prefix)
Ukuran indexKecilLebih besar (menyimpan kombinasi nilai)
Index maintenanceRinganLebih berat saat INSERT/UPDATE
Kapan pakaiQuery selalu filter 1 kolomQuery filter banyak kolom, atau ingin covering index
Covering indexSulit dicapaiMudah dicapai (semua kolom SELECT ada di index)

Covering Index: Keunggulan Tersembunyi Composite Index

Ada satu konsep penting yang sering terlewat: covering index. Covering index terjadi ketika semua kolom yang diminta oleh query sudah tersedia di dalam index, tanpa perlu melakukan “table lookup” (mengakses data di tabel utama).

Contoh: kita punya tabel orders dengan kolom id, user_id, status, total. Query ini:

SELECT status, total FROM orders WHERE user_id = 100;

Jika kita hanya punya single index pada user_id, MySQL akan:

  1. Menggunakan index user_id untuk menemukan baris yang cocok
  2. Melakukan table lookup untuk mengambil kolom status dan total

Tapi jika kita punya composite index (user_id, status, total), MySQL bisa:

  1. Menggunakan index untuk menemukan baris yang cocok
  2. Mengambil status dan total langsung dari index (tidak perlu table lookup)

Ini jauh lebih cepat karena mengurangi I/O operasi. Anda bisa melihatnya di EXPLAIN — jika kolom Extra tertulis Using index, berarti query menggunakan covering index.

Urutan Kolom dalam Composite Index

Urutan kolom dalam composite index sangat kritikal. Berikut best practices yang saya temukan:

1. Kolom equality filter di depan, range filter di belakang

-- ✅ BAIK: status (= equality) di depan, created_at (range) di belakang
CREATE INDEX idx_orders_status_created ON orders(status, created_at);

-- Query yang bisa manfaatkan index ini:
SELECT * FROM orders WHERE status = 'pending' AND created_at > '2026-01-01';

2. Kolom dengan HIGH cardinality di depan

Cardinality adalah jumlah nilai unik dalam suatu kolom. Kolom dengan cardinality tinggi (banyak nilai unik) sebaiknya diletakkan di depan karena lebih efektif mempersempit hasil pencarian.

-- email punya cardinality tinggi (setiap user berbeda)
-- status punya cardinality rendah (hanya: pending, shipped, delivered)

-- ✅ BAIK: email di depan
CREATE INDEX idx_users_email_status ON users(email, status);

-- ❌ KURANG IDEAL: status di depan (terlalu banyak baris yang status = 'active')
CREATE INDEX idx_users_status_email ON users(status, email);

3. Pertimbangkan query yang paling sering dijalankan

Urutan kolom harus disesuaikan dengan pola query yang paling sering dijalankan di aplikasi Anda. Jika query paling sering filter berdasarkan user_id lalu created_at, buat index (user_id, created_at).

Kapan pakai Single Index vs Composite Index?

SituasiRekomendasi
Query selalu filter 1 kolomSingle index
Query filter 2+ kolom dengan ANDComposite index
Query butuh covering indexComposite index (include kolom SELECT)
Query filter dengan OR antar kolom berbedaSingle index per kolom (index merge)
Tabel INSERT-heavy (write performance kritikal)Hati-hati dengan composite index besar
Ingin kurangi jumlah indexComposite index (1 index bisa gantikan beberapa single index)

Contoh Praktis

Misalnya kita punya tabel posts dengan kolom: id, author_id, category_id, status, created_at, title.

Scenario 1: Query filter satu kolom

-- Query ini hanya filter berdasarkan author_id
SELECT * FROM posts WHERE author_id = 5;

-- Single index sudah cukup
CREATE INDEX idx_posts_author ON posts(author_id);

Scenario 2: Query filter dua kolom + ORDER BY

-- Query ini filter author_id + status, lalu ORDER BY created_at
SELECT * FROM posts WHERE author_id = 5 AND status = 'published' ORDER BY created_at DESC;

-- Composite index (author_id, status, created_at)
-- - author_id & status untuk WHERE (equality)
-- - created_at untuk ORDER BY (menghindari filesort)
CREATE INDEX idx_posts_author_status_created ON posts(author_id, status, created_at);

Scenario 3: Query dengan SELECT spesifik (covering index)

-- Query ini hanya butuh title, tidak perlu SELECT *
SELECT title FROM posts WHERE author_id = 5 AND status = 'published';

-- Composite index yang "cover" semua kolom yang diakses
CREATE INDEX idx_posts_cover ON posts(author_id, status, title);

-- EXPLAIN akan tunjukkan "Using index" (covering index!)
EXPLAIN SELECT title FROM posts WHERE author_id = 5 AND status = 'published';

Kesalahan Umum yang Sering Terjadi

Berdasarkan pengalaman dan bacaan, berikut beberapa kesalahan umum yang sering dilakukan:

1. Membuat terlalu banyak single index

-- ❌ Terlalu banyak index
CREATE INDEX idx_orders_status ON orders(status);
CREATE INDEX idx_orders_user ON orders(user_id);
CREATE INDEX idx_orders_created ON orders(created_at);

-- ✅ Lebih baik: composite index yang melayani semua query
CREATE INDEX idx_orders_composite ON orders(user_id, status, created_at);

2. Urutan kolom yang salah

-- Query paling sering: WHERE status = 'pending' AND user_id = 5
-- Tapi index dibuat: (user_id, status)

-- ❌ Index kurang optimal
CREATE INDEX idx_orders_bad ON orders(user_id, status);

-- ✅ Index lebih optimal (status di depan karena equality filter)
CREATE INDEX idx_orders_good ON orders(status, user_id);

3. Tidak menggunakan EXPLAIN

Selalu gunakan EXPLAIN sebelum dan sesudah membuat index untuk memastikan index benar-benar digunakan:

EXPLAIN SELECT * FROM orders WHERE status = 'pending' AND created_at > '2026-01-01';

-- Perhatikan kolom:
-- - type: ref, range, atau eq_ref (bagus) vs ALL (full scan = buruk)
-- - key: nama index yang digunakan
-- - Extra: "Using index" = covering index (bagus sekali)

Refleksi Saya

Belajar tentang index ini membuat saya sadar: index bukan sekadar “tambah biar cepat”. Ada ilmu di balik urutan kolom, pemilihan kolom, dan pemahaman pola query. Composite index memang lebih powerful, tapi harus dipahami aturan leftmost prefix-nya. Kalau tidak, index yang sudah dibuat malah tidak terpakai.

Satu hal yang paling berkesan: EXPLAIN adalah sahabat terbaik. Sebelum optimasi, jalankan EXPLAIN dulu. Sesudah buat index, jalankan lagi. Kalau type masih ALL (full table scan), berarti index belum optimal. Semoga catatan ini bermanfaat, dan semoga saya bisa mengaplikasikan ilmu ini di proyek-proyek berikutnya.

Belajar Adab Minum: Sunnah Sehari-hari yang Sering Kita Abaikan

Hari ini saya belajar tentang adab minum dalam Islam. Selama ini saya hanya tahu “baca bismillah sebelum minum” — ternyata ada cukup banyak adab lain yang saya sendiri belum praktikkan. Catatan ini saya rangkum dari beberapa hadits shahih yang membahas tata cara minum menurut Rasulullah ﷺ. Semoga bermanfaat untuk saya pribadi dan siapa saja yang membacanya.

Membaca Bismillah Sebelum Minum

Adab pertama dan paling mendasar: mengucapkan bismillah sebelum mulai minum. Ini berlaku sama seperti sebelum makan. Jika lupa membaca bismillah di awal, Rasulullah ﷺ mengajarkan untuk mengucapkan:

“Bismillaahi awwalahu wa aakhirahu” — “dengan nama Allah pada awal dan akhirnya” (HR. Abu Dawud no. 3767 dan Tirmidzi no. 1858, dishahihkan Al-Albani).

Syaitan akan menjauhi makanan dan minuman yang dibacakan bismillah sebelum dikonsumsi. Jadi, setiap kali mau minum — entah air putih, teh, atau kopi — ucapkan “bismillah” dulu. Sederhana, tapi bernilai ibadah.

Minum dengan Tangan Kanan, Bukan Kiri

Selain membaca bismillah, Rasulullah ﷺ juga menganjurkan untuk minum dengan tangan kanan. Dari Ibnu Umar radhiyallahu ‘anhuma,

“Apabila salah seorang di antara kalian makan, maka hendaklah dia makan dengan tangan kanannya; dan apabila minum, hendaklah minum dengan tangan kanannya. Karena setan makan dengan tangan kirinya dan minum dengan tangan kirinya.” (HR. Muslim no. 5233)

Tangan kanan dalam Islam identik dengan kebaikan, sedangkan tangan kiri ditinggalkan untuk keperluan yang kurang bersih. Menggunakan tangan kanan saat minum adalah bentuk simbolis memilih kebaikan dalam setiap tegukan.

Minum Sambil Duduk, Bukan Berdiri

Salah satu adab yang mungkin paling sering kita langgar: minum sambil berdiri. Dari Abu Hurairah radhiyallahu ‘anhu, Rasulullah ﷺ bersabda:

“Janganlah sekali-kali salah seorang dari kalian minum dengan berdiri; jika lupa hendaklah ia memuntahkannya.” (HR. Muslim)

Perintah “memuntahkannya” ini cukup tegas — menunjukkan betapa pentingnya adab ini di sisi Rasulullah ﷺ. Minum sambil duduk memberikan ketenangan dan menunjukkan sikap tawadhu’ di hadapan Allah. Meskipun ada riwayat yang menyebutkan Nabi ﷺ pernah minum sambil berdiri, para ulama menjelaskan bahwa perkataan beliau lebih didahulukan daripada perbuatan beliau dalam hal ini.

Tiga Tegukan, Bernafas di Luar Gelas

Ini mungkin adab minum yang paling unik: minum secara bertahap dalam tiga tegukan, dengan bernafas di luar gelas di antara tegukan. Dari Anas radhiyallahu ‘anhu, beliau bersabda:

“Apabila salah seorang di antara kalian minum, janganlah bernafas di dalam wadah minumannya.” (HR. Muslim no. 5458)

Dalam riwayat lain dari Anas radhiyallahu ‘anhu, Rasulullah ﷺ bersabda:

“Sesungguhnya dengan begini (minum tiga tegukan dengan bernafas di luar) dahaga lebih hilang, lebih sehat, dan lebih enak.” (Muttafaq ‘alaih)

Ternyata minum tiga kali teguk dengan bernafas di luar gelas itu bukan hanya sunnah, tapi juga secara medis membantu tubuh lebih optimal dalam menghidrasi diri. Tidak perlu minum dengan terburu-buru — pelan-pelan saja, sambil menikmati setiap tegukan.

Jangan Meniup Minuman

Saat minuman terlalu panas atau ada serpihan di permukaan, kebiasaan kita adalah meniupnya. Padahal, meniup minuman adalah perbuatan yang dilarang dalam Islam. Alasannya selain tidak higienis, meniup juga termasuk perbuatan yang tidak disukai Allah karena dianggap sia-sia.

Jika minuman terlalu panas, cukup diamkan saja hingga suhunya turun, atau gunakan wadah lain untuk menuang bolak-balik (dikocok). Lebih sabar, lebih sunnah.

Adab Jika Lalat Jatuh ke Minuman

Pernahkah lalat jatuh ke gelas minuman kita? Kebanyakan orang langsung membuang minumannya. Tapi ternyata Rasulullah ﷺ mengajarkan hal yang berbeda. Dari Abu Hurairah radhiyallahu ‘anhu, Nabi ﷺ bersabda:

“Apabila salah seorang di antara kalian jatuhkan ke dalam minumannya seekor lalat, hendaklah ia mencelupkan seluruh tubuh lalat tersebut ke dalam minumannya, kemudian membuangnya. Karena pada salah satu sayap lalat terdapat penyakit, dan pada sayap yang lain terdapat obat.” (HR. Abu Dawud no. 3758, dishahihkan Al-Albani)

Sungguh luar biasa — Allah memberikan obat dan penyakit berdampingan pada makhluk yang sering kita anggap menjijikkan. Cukup celupkan seluruh tubuh lalat ke dalam minuman, lalu buang. Tidak perlu membuang minumannya.

Akhiri dengan Memuji Allah

Setelah selesai minum, ucapkan hamdalah. Dari Anas bin Malik radhiyallahu ‘anhu, Rasulullah ﷺ bersabda:

“Sesungguhnya Allah ridha terhadap seorang hamba yang menikmati makanan lalu memuji Allah setelahnya, atau meneguk minuman lalu memuji Allah setelahnya.” (HR. Muslim no. 2734)

Bayangkan, hanya karena mengucap hamdalah setelah minum, Allah ridha kepada kita. Masya Allah, begitu murahnya pahala yang Allah sediakan. Cukup ucapkan: “Alhamdulillaah” — segala puji bagi Allah yang telah memberi minuman ini dan merizkikannya tanpa daya dan kekuatan dariku.

Refleksi Saya

Belajar adab minum ini membuat saya sadar: betapa banyak kebiasaan minum saya yang belum sesuai sunnah. Sering minum sambil berdiri, apalagi saat terburu-buru. Kadang meniup minuman panas tanpa pikir panjang. Belum lagi soal lalat — selama ini pasti langsung saya buang minumannya.

Nyatanya, semua adab ini sederhana dan bisa dimulai dari tegukan berikutnya. Minum sambil duduk, baca bismillah, tiga tegukan dengan nafas di luar, akhiri dengan hamdalah — tidak ada yang sulit, hanya perlu dibiasakan. Semoga Allah memberi kita taufik untuk istiqamah mengamalkan sunnah-sunnah kecil ini. Aamiin.

Berita Coding & Web Dev Terbaru (17 Agustus 2026)

Kumpulan berita coding, web dev & AI paling menarik minggu ini (10-16 Agustus 2026). Dari Anthropic yang buka system prompt Claude sampai drama Cloudflare yang suntikkan analytics diam-diam — ada yang perlu kamu tahu sebagai developer.

1. Anthropic Publikasikan System Prompts Claude

Anthropic membuka sistem prompt yang digunakan di claude.ai dan aplikasi mobile Claude. Dokumentasi ini menunjukkan bagaimana prompt system mendorong perilaku Claude — mulai dari memberikan tanggal terkini, memastikan kode selalu dalam format Markdown, hingga berbagai panduan perilaku lainnya. Berguna banget buat yang mau memahami cara Anthropic membingkai interaksi model mereka.

🔗 Baca di Claude Platform Docs

2. OpenAI Ultrafast: GPT-5.6 Sol 14x Lebih Cepat

OpenAI meluncurkan mode preview “Ultrafast” untuk model terbaru mereka GPT-5.6 Sol yang membuat inference berjalan hingga 14x lebih cepat. Ini bagian dari strategi OpenAI untuk menarik pengguna enterprise yang butuh kecepatan tanpa mengorbankan kualitas respons.

🔗 Baca di TechCrunch

3. SpaceX Resmi Akuisisi Cursor

Startup AI coding Cursor resmi menjadi bagian dari SpaceX setelah proses akuisisi selesai. Cursor, yang dikenal sebagai code editor berbasis AI yang populer di kalangan developer, kini bergabung di bawah payung perusahaan Elon Musk — sebuah langkah menarik yang menggabungkan AI coding dengan aerospace.

🔗 Baca di TechCrunch

4. Cloudflare Suntikkan Analytics JavaScript Secara Diam-diam

Seorang developer menemukan bahwa Cloudflare diam-diam menyuntikkan snippet analytics JavaScript ke halaman HTML saat kamu mengganti nameserver ke Cloudflare. Temuan ini bikin heboh di HN karena banyak yang tidak menyadari bahwa ada kode tracking yang otomatis ditambahkan ke website mereka. Penting untuk diketahui developer yang concern soal privasi dan transparansi.

🔗 Diskusi di Hacker News

5. CSS Tricks: Custom Highlight API, Navigation Matching & Lainnya

Edisi #17 dari seri What’s !important di CSS-Tricks membahas beberapa fitur CSS terbaru: Custom Highlight API untuk styling selection, CSS Navigation Matching, perbaikan text-stroke, cara membuat skeleton UI, dan diagonal scrolling. Plus bagaimana gambar bisa overflow dari container sendiri. Kumpulan technique CSS yang powerful untuk daily use.

🔗 Baca di CSS-Tricks

6. Blocked aria-hidden: Fix yang Salah & Yang Benar

Artikel penting soal accessibility: peringatan aria-hidden di browser itu benar, dan sebagian besar “fix” yang beredar di internet (blur, setTimeout, menghapus attribute) sebenarnya salah — mereka hanya menutupi warning tanpa menyelesaikan masalah bagi pengguna screen reader. Kuncinya: focus harus keluar dari region sebelum region tersebut di-hide atau di-inert. Artikel ini juga merekomendasikan migrasi ke elemen <dialog> native jika memungkinkan.

🔗 Baca di CSS-Tricks

7. Software Engineering Fundamentals Matter More Than Ever

Di tengah maraknya AI coding tools, artikel ini mengingatkan bahwa fundamental software engineering justru semakin penting. Dengan AI yang bisa menghasilkan kode dalam sekejap, kemampuan untuk menulis arsitektur yang solid, memahami trade-off, dan melakukan code review yang kritis menjadi skill yang semakin berharga — bukan malah tergantikan.

🔗 Baca selengkapnya

Itu dia 7 berita paling menarik minggu ini. Yang follow perkembangan AI wajib tahu soal Claude system prompts dan OpenAI Ultrafast. Jangan lupa cek aria-hidden fix kalau lagi kerja modal dialog — accessibility itu bukan optional!

Belajar AI #1: Sejarah AI — dari Turing Test (1950) sampai ChatGPT (2022)

Hari ini saya mulai seri baru: Belajar AI. Sebagai programmer yang sehari-hari menulis kode, saya merasa perlu memahami teori di baliknya — bukan sekadar memakai API. Dan saya memutuskan mulai dari awal: sejarah. Bukan karena nostalgia, tapi karena sejarah menjelaskan satu hal yang sering membingungkan saya: kenapa AI sempat “mati” dua kali, lalu bangkit lagi dan sekarang ada di mana-mana. Mari kita telusuri.

Timeline sejarah AI dari Turing Test 1950 hingga ChatGPT 2022
Timeline sejarah AI: dari Turing Test (1950) sampai ChatGPT (2022). Saya buat dengan Python PIL sebagai catatan visual.

1950: Turing Test — titik tolak

Alan Turing, matematikawan asal Inggris, menerbitkan paper berjudul “Computing Machinery and Intelligence” di jurnal Mind tahun 1950. Pertanyaannya sederhana: can machines think? Tapi karena “berpikir” terlalu kabur untuk diuji, Turing menggantinya dengan permainan imitasi: seorang pemeriksa mengobrol dengan mesin dan manusia melalui teks. Kalau pemeriksa tidak bisa membedakan mana mesin mana manusia, mesin itu dianggap lolos uji kecerdasan.

Dari sudut pandang programmer, Turing Test itu seperti unit test yang kasar: kita tidak mengukur cara kerja dalamnya, hanya hasil luarnya. Mesin boleh diimplementasikan dengan cara apa pun — yang penting outputnya tidak bisa dibedakan dari manusia. Konsep ini penting karena menanamkan pola pikir “kecerdasan bisa dinilai dari perilaku”, bukan dari jiwa atau kesadaran.

1956: Konferensi Dartmouth — lahirnya nama “AI”

Enam tahun kemudian, John McCarthy mengusulkan sebuah workshop musim panas di Dartmouth College, Amerika Serikat. Para pesertanya adalah tokoh yang kelak menjadi bapak pendiri AI: McCarthy, Marvin Minsky, Claude Shannon, dan Nathaniel Rochester. Di konferensi inilah istilah artificial intelligence pertama kali dicetuskan, dan AI resmi menjadi bidang riset.

Yang menarik: optimisme saat itu sangat tinggi. Mereka memperkirakan masalah AI bisa diselesaikan dalam satu generasi. Setelah konferensi, muncul beberapa hasil awal yang menjanjikan — Frank Rosenblatt membuat perceptron (neuron buatan sederhana) tahun 1958, dan Joseph Weizenbaum membuat chatbot ELIZA tahun 1966 yang bisa “ngobrol” ala terapis. Tapi dari kejauhan, saya melihat pola yang sama dengan hype teknologi sekarang: ekspektasi melampaui kenyataan.

Dua musim dingin: AI winter

Janji besar tanpa hasil yang konsisten membuat pendanaan riset AI membeku dua kali. Para sejarawan menyebut periode ini AI winter:

  • 1974–1980 (AI winter pertama): laporan Lighthill di Inggris tahun 1973 mengkritik habis-habisan capaian AI, pendanaan pemerintah dan industri menyusut drastis.
  • 1980-an (kebangkitan sistem pakar): expert systems — program berisi aturan if-then yang ditulis manual oleh ahli — dipakai di industri, misalnya untuk diagnosis dan konfigurasi. Populer, tapi mahal dan rapuh: aturan tidak bisa menangani kasus di luar yang tertulis.
  • 1987–1993 (AI winter kedua): pasar komputer LISP (bahasa favorit riset AI) runtuh, proyek sistem pakar ditinggalkan satu per satu.

Bagi saya ini pelajaran berharga: AI berbasis aturan yang ditulis tangan tidak pernah benar-benar “belajar”. Ia sekuat pengetahuan yang dimasukkan manusia, dan rapuh di luar domain itu. Paradigma inilah yang akhirnya digantikan oleh machine learning.

Kebangkitan: machine learning & deep learning

Alih-alih menulis aturan secara manual, machine learning membuat model belajar dari data. Beberapa tonggak yang saya catat:

  • 1997 — Deep Blue: komputer IBM mengalahkan juara catur dunia Garry Kasparov. Masih berbasis pencarian dan aturan, tapi momen penting: AI mengalahkan manusia di game populer.
  • 2012 — AlexNet: jaringan saraf konvolusional karya Alex Krizhevsky, Ilya Sutskever, dan Geoffrey Hinton menang telak di kompetisi ImageNet. Inilah momen deep learning “resmi” naik daun — berkat data besar dan GPU.
  • 2016 — AlphaGo: sistem DeepMind mengalahkan Lee Sedol di permainan Go, yang selama ini dianggap terlalu kompleks untuk diselesaikan dengan brute force.
  • 2017 — Transformer: paper “Attention Is All You Need” memperkenalkan arsitektur transformer, yang kelak menjadi fondasi GPT dan semua LLM modern.

2022–sekarang: AI generatif arus utama

ChatGPT dirilis November 2022 dan dipakai ratusan juta orang dalam hitungan bulan. Yang tadinya riset laboratorium menjadi produk harian: menulis, coding, menggambar, menganalisis. Sejarah tidak berhenti di sini — tapi untuk seri ini, 2022 adalah titik awal era yang sedang kita jalani.

Catatan programmer: mengingat lewat data

Sebagai programmer, cara paling natural buat saya mengingat sejarah ini adalah menjadikannya data — list pasangan (tahun, peristiwa):

sejarah = [
    (1950, "Turing Test"),
    (1956, "Konferensi Dartmouth"),
    (1974, "AI winter pertama"),
    (1987, "AI winter kedua"),
    (1997, "Deep Blue kalahkan Kasparov"),
    (2012, "AlexNet menang ImageNet"),
    (2017, "Paper Transformer"),
    (2022, "ChatGPT rilis"),
]
for tahun, peristiwa in sejarah:
    print(f"{tahun}: {peristiwa}")

Dari data ini saya menangkap pola besar: ide neural network sebenarnya sudah ada sejak 1958 (perceptron), tapi baru meledak tahun 2012. Yang berubah bukan teorinya, melainkan dua bahan bakar: data besar dan komputasi murah (GPU). Jadi ketika orang bilang “AI baru”, yang baru sebenarnya adalah skala, bukan konsep dasarnya.

Sumber

Pakai Model Non-Thinking untuk Coding Kompleks: Bisa, Asal Tahu Batasnya

Hari ini saya coba jawab pertanyaan yang sering muncul di kalangan yang baru mulai pakai AI coding agent: “Kalau pakai model non-thinking, apakah tetap bisa dipakai untuk coding yang kompleks?” Singkatnya: bisa, tapi dengan syarat — dan ada jenis pekerjaan yang memang lebih aman diserahkan ke model thinking. Catatan ini saya tulis khusus dari sudut pandang coding (Hermes Agent dan OpenCode), tidak membahas hal lain di luar itu.

Apa Bedanya Model Thinking vs Non-Thinking?

Dalam konteks coding, “thinking” (atau extended thinking / reasoning) artinya model diberi ruang untuk berpikir panjang sebelum menjawab — ia menuliskan rantai penalaran internal dulu, baru menghasilkan kode atau keputusan. Ini memakai token tambahan (biasanya tidak terlihat di output akhir), sehingga lebih lambat dan lebih mahal.

Model non-thinking menjawab langsung: input diproses, output langsung keluar. Lebih cepat, lebih murah, tapi untuk masalah yang butuh penalaran bertingkat, hasilnya bisa meleset lebih cepat.

Penting: yang wajib dimiliki untuk coding adalah kemampuan tool calling yang baik (membaca file, menjalankan perintah, melihat error, mengedit). Mode thinking itu nilai tambah, bukan prasyarat.

Kapan Non-Thinking Masih Andal

Untuk jenis pekerjaan berikut, model non-thinking yang bagus biasanya sudah cukup:

  • Boilerplate dan scaffolding — kerangka proyek, file konfigurasi, stub fungsi.
  • CRUD sederhana — endpoint API, form, operasi database standar.
  • Task satu file dengan spesifikasi eksplisit — “ubah fungsi ini supaya menerima parameter X”.
  • Refactor kecil dan mekanis — rename, pindah fungsi, ubah format.
  • Perbaikan bug yang error-nya jelas dan lokal.

Ciri umumnya: tugasnya jelas, ruang lingkupnya sempit, dan ada cara verifikasi cepat (test, compile, atau output yang bisa dicek).

Di Mana Non-Thinking Sering Gagal

Yang berikut ini justru menjadi titik lemah model non-thinking:

  • Debugging bertingkat — error menimpa error, root cause tersembunyi di balik gejala.
  • Refactor lintas file — banyak dependensi silang yang harus diikuti konsistensinya.
  • Keputusan arsitektur — memilih desain dengan trade-off yang saling bertabrakan.
  • Memahami codebase besar yang belum familiar — butuh penalaran panjang untuk membangun model mental.
  • Task ambigu — spesifikasi tidak lengkap, perlu inferensi dan pertimbangan.

Pada kasus seperti ini, model non-thinking berisiko gagal diam-diam: hasilnya terlihat jalan, tapi logikanya salah di tempat yang tidak langsung kelihatan.

Bukti dari Benchmark

Di benchmark agentic coding seperti SWE-bench Verified (500 isu GitHub nyata dari repo populer seperti Django, Flask, scikit-learn yang harus diselesaikan oleh model), posisi papan atas hampir selalu diisi model reasoning/thinking. Menurut leaderboard yang dipantau benchlm.ai per Agustus 2026, model teratas (Claude Opus 5) mencapai sekitar 96% resolusi.

Pola umumnya: versi thinking dari sebuah model hampir selalu mengungguli versi non-thinking-nya pada task multi-step, dan gap-nya mengecil drastis pada task sederhana. Jadi benchmark ini menegaskan — bukan soal “bisa atau tidak”, tapi soal di titik mana non-thinking mulai jebol.

Perbandingan Ringkas

AspekNon-ThinkingThinking
BiayaMurahMahal (token reasoning)
KecepatanCepatLambat
Konsumsi tokenHematBoros
Task sederhana & jelasSangat cocokBisa, tapi boros
Debugging & refactor besarRisiko gagal diam-diamAndal

Pola Praktis: Campur, Jangan Pilih Satu

Cara paling efektif bukan memilih salah satu, tapi memakai keduanya sesuai tugas — di Hermes dan OpenCode, ganti model per sesi/per task itu normal.

Hermes Agent

Ganti model kapan saja via hermes model, atau langsung set default:

hermes model
hermes config set model.default nama-model-non-thinking

Di dalam sesi, perintah /model untuk ganti model, dan /reasoning untuk mengatur level reasoning — kalau modelnya memang mendukung mode thinking. Model non-thinking tetap jalan normal tanpa setting tambahan.

OpenCode

Pilih model via /models. Kalau model default-nya thinking (misalnya Claude lewat provider Anthropic) dan ingin mematikannya, set lewat opencode.json:

{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "anthropic": {
      "models": {
        "claude-sonnet-4-5": {
          "options": {
            "thinking": { "type": "disabled" }
          }
        }
      }
    }
  }
}

Untuk provider OpenAI-compatible (misalnya GPT), mode reasoning diatur lewat reasoningEffort — nilai rendah berarti “hampir non-thinking”.

Kesimpulan

Model non-thinking bisa dipakai untuk coding kompleks, asal tugas dipecah kecil dan ada loop verifikasi: tulis sedikit → jalankan → lihat error → perbaiki. Untuk boilerplate, CRUD, dan task single-file, non-thinking bahkan pilihan paling efisien.

Tapi untuk debugging bertingkat, refactor lintas file, dan keputusan desain, model thinking masih pilihan yang jauh lebih aman — terutama kalau pekerjaan berjalan tanpa pengawasan. Prinsipnya: pakai model sesuai tingkat kesulitan tugas, bukan satu model untuk segalanya.

Sumber

Belajar Laravel #2: Routing & Controller Pertama — Dari URL ke View

Hari ini saya lanjut belajar Laravel ke episode kedua: routing dan controller pertama. Setelah minggu lalu berhasil membuat project dan menjalankan php artisan serve, sekarang saya penasaran — dari mana sebenarnya halaman awal itu muncul? Ternyata jawabannya ada di satu file kecil bernama routes/web.php. File inilah yang menjadi pintu masuk hampir semua request ke aplikasi web kita.

routes/web.php: pintu masuk aplikasi

Buka folder project lalu buka routes/web.php. Isinya singkat, hanya satu route default:

<?php

use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    return view('welcome');
});

Bisa dibaca seperti ini: “kalau ada request GET ke alamat /, jalankan fungsi ini, lalu kembalikan view welcome“. Di sinilah saya sadar perbedaan mendasar Laravel dengan PHP biasa: alih-alih membuat file index.php per halaman, semua alamat dipetakan lewat satu file route. Ini disebut front controller — semua request masuk lewat public/index.php, lalu Laravel yang memutuskan ke mana request itu diteruskan berdasarkan route yang cocok.

Route closure vs controller

Di contoh di atas, logika ditulis langsung di dalam route sebagai closure (fungsi anonim). Untuk halaman sekecil welcome, cara ini cukup. Tapi kalau semua logika ditumpuk di routes/web.php, file itu cepat sekali jadi penuh sesak. Solusi Laravel: pindahkan logika ke controller.

Buat controller lewat Artisan:

php artisan make:controller HalamanController

Perintah itu membuat file app/Http/Controllers/HalamanController.php. Isinya kelas kosong. Lalu saya tambahkan method sendiri:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class HalamanController extends Controller
{
    public function index()
    {
        return view('welcome');
    }
}

Dan route-nya berubah menjadi:

Route::get('/', [HalamanController::class, 'index']);

Pola [NamaController::class, 'namaMethod'] adalah cara Laravel modern menunjuk controller di route. Hasilnya sama dengan versi closure, tapi logika sudah pindah ke tempat yang lebih rapi dan bisa diuji. Prinsipnya: route itu peta, controller itu pekerja.

View pertama

Method index() tadi mengembalikan view('welcome'). View adalah file template yang disimpan di resources/views dengan ekstensi .blade.php — jadi view('welcome') mencari resources/views/welcome.blade.php. Saya coba buat halaman sendiri, misalnya halaman tentang:

Route::get('/tentang', [HalamanController::class, 'tentang']);

Lalu di controller:

public function tentang()
{
    return view('tentang');
}

Dan buat file resources/views/tentang.blade.php berisi HTML sederhana. Setelah itu php artisan serve dan buka http://127.0.0.1:8000/tentang — halaman muncul. Kesannya ajaib padahal sederhana: route → controller → view. Tiga lapis yang akan terus kita pakai di semua episode berikutnya.

Kesalahan yang umum

  • Lupa menjalankan php artisan serve, lalu bingung kenapa halaman 404. Route tidak akan jalan tanpa server.
  • Nama view salah ketik: view('welcome') tapi file bernama welcome.blade.php — pastikan nama persis.
  • Menaruh route di bawah route lain yang polanya sama; di Laravel, route yang cocok pertama yang dipakai. Urutan route bisa penting.
  • Menulis logika bisnis menumpuk di route closure sampai file web.php ratusan baris — kebiasaan yang sebaiknya dihindari sejak awal dengan memakai controller.

Kesimpulan

Hari ini saya paham alur dasar aplikasi Laravel: request masuk ke routes/web.php, dicocokkan dengan pola URL, lalu diteruskan ke closure atau controller, dan akhirnya controller mengembalikan view Blade. Konsep ini kecil tapi fondasi — semua fitur berikutnya (form, database, login) menumpang di alur yang sama. Minggu depan saya lanjut ke Blade: layout, @extends, dan perulangan @foreach.

Sumber

Mengganti Model di Tengah Sesi Coding: Dampaknya dan Solusi Failover (OpenCode, Hermes, 9Router)

Beberapa hari terakhir saya memakai beberapa tool coding berbasis AI sekaligus, dan muncul pertanyaan yang sering ditanyakan banyak orang: kalau di tengah sesi kode modelnya ganti-ganti — entah karena rate limit atau provider sedang turun — apakah berpengaruh ke hasil kerja? Apalagi sekarang ada 9Router yang bisa auto-fallback ke model lain. Catatan ini saya rangkum dari eksperimen dan riset saya soal OpenCode, Hermes Agent, dan 9Router.

Apa yang Sebenarnya Terjadi Saat Model Ganti di Tengah Sesi?

Ganti model di tengah sesi coding bukan sekadar soal “sambungannya pindah”. Ada tiga hal yang berubah:

  • Konteks percakapan bisa terpotong. Setiap model punya batas token window sendiri. Saat failover, riwayat prompt biasanya di-truncate agar muat di model baru. Model pengganti bisa jadi tidak paham penuh alur debug yang sedang kita jalani.
  • Gaya dan keputusan teknis bisa melenceng. Tiap model punya kebiasaan sendiri soal penamaan, struktur folder, sampai pilihan pola kode. Kalau berganti di tengah fitur yang sama, hasilnya bisa tidak konsisten.
  • Ada biaya dan latency tambahan. Koneksi ke provider baru perlu inisialisasi ulang, dan jika konteks dikirim ulang, token yang dipakai juga bertambah.

Bagaimana OpenCode Menangani Ini?

OpenCode (tool agentic coding open source yang awalnya dikembangkan oleh Andrej Karpathy) mendukung banyak provider sekaligus: OpenAI, Anthropic, Google, hingga model lokal. Beberapa hal yang saya perhatikan:

  • Bisa ganti model/provider secara dinamis tanpa restart sesi, dan konteks terbaru tetap dibawa.
  • Ada failover bawaan — jika satu provider rate limited, ia beralih ke model berikutnya sesuai urutan yang kita daftarkan.
  • Semua perubahan model tercatat di log sesi, sehingga riwayatnya bisa ditelusuri kembali.

Kekurangannya: tiap provider perlu API key sendiri, dan failover bisa menambah latency kalau tidak hati-hati mengatur urutan.

Bagaimana dengan Hermes Agent?

Hermes Agent (dari Nous Research) adalah agen yang lebih umum — bisa untuk coding, riset, sampai otomasi. Untuk urusan ganti model di tengah sesi, pengalamannya sedikit berbeda:

  • Provider bisa di-load/unload secara dinamis, tapi sinkronisasi konteks cenderung manual — perlu resume sesi atau menyuntikkan ringkasan konteks.
  • Failover otomatis tidak sepenuhnya built-in seperti OpenCode, tapi bisa dikonfigurasi lewat fallback_providers di config — ini yang saya pakai untuk beberapa cron blog saya.
  • Restore konteks dari disk butuh waktu, jadi ada latency tambahan saat pindah sesi.

Di Mana Posisi 9Router?

Di sinilah 9Router berperan. Daripada tiap tool mengurus failover-nya sendiri, kita bisa mengarahkan semuanya ke satu router yang mengatur urutan model:

opencode --model "9router://gpt-4o,claude-3.5-sonnet,gemini-1.5-flash"

Jika model pertama kena limit, 9Router otomatis meneruskan ke model berikutnya. Sisi tool (OpenCode/Hermes) tidak perlu tahu apa-apa — mereka tetap memakai endpoint yang sama. Ini menyederhanakan masalah jadi satu: atur urutan prioritas, sisanya otomatis.

Jadi, Apakah Ganti Model Berpengaruh?

AspekDampakCara Mitigasi
Konteks sesiBisa terpotong (truncate)Checkpoint / ringkasan konteks tiap beberapa langkah
Konsistensi kodeGaya bisa berubah antar modelBiarkan satu model menyelesaikan satu fitur utuh
KetersediaanTurun jika satu provider downFailover 9Router dengan urutan model cadangan
Latency & biayaNaik saat failoverUrutkan model dari yang paling sering dipakai

Perspektif Hermes Agent: Kenapa Task Tetap Nyambung?

Ada satu hal yang memperjelas gambaran ini: jawaban langsung dari tim Hermes Agent (Nous Research) saat saya tanyakan hal yang sama — “jika saat eksekusi task model/provider diganti 9Router, apakah task yang dijalankan model berbeda akan tetap nyambung?”. Jawabannya: ya, tetap nyambung — dengan satu syarat penting.

Konteks Dikelola Hermes, Bukan Provider

Konteks (prompt + tools + state) disimpan di sisi gateway/sesi Hermes. Saat 9Router mengganti provider/model, Hermes tetap mengirim konteks yang sama — lewat history.messages — ke provider baru. Provider hanya “menumpang” pada konteks yang sudah disiapkan Hermes.

Syaratnya: Provider OpenAI-Compatible

Provider OpenAI-compatible (kagiro, laguna, dsb — yang memang terhubung lewat 9Router) memakai format message yang sama. Jadi model yang berbeda sekalipun akan menerima konteks yang sama persis. Yang bikin putus hanyalah perubahan model yang sangat signifikan — misalnya dari OpenAI-style ke Claude-style — karena format outputnya berbeda.

Contoh Jalan atau Tidaknya

PerpindahanKonteks Tetap?Alasan
kagiro → laguna (9Router)✅ YaFormat message sama
laguna → deepseek (9Router)✅ YaFormat message sama
deepseek → llama.cpp (GGUF lokal)⚠️ TergantungKalau via endpoint OpenAI-compatible masih nyambung, tapi gaya model baru bisa beda
ke Claude (non-OpenAI)❌ PutusFormat beda, konteks harus di-reformat

Bottom Line

Selama semua provider yang dipakai adalah OpenAI-compatible endpoint (yang memang dilakukan via 9Router), ganti model/provider tidak akan memutus alur task — Hermes akan mengirim konteks yang sama ke provider baru. Logika tool (mengubah config, restart gateway, dsb) tetap jalan karena Hermes yang mengeksekusi, bukan provider. Yang nyata berubah hanyalah cost dan kecepatan response.

Bagaimana dengan OpenCode? Apakah Sama?

Pertanyaan lanjutannya: apakah prinsip yang sama berlaku di OpenCode? Berdasarkan dokumentasi resminya — ya, prinsipnya sama.

“Select a model to use it in the current session. Switching models updates that session without changing your config.” — Dokumentasi resmi OpenCode (opencode.ai/v2/docs/models)

Konteks Dipegang OpenCode, Bukan Provider

Seperti Hermes, konteks (transcript + state) disimpan di sisi OpenCode — bukan di provider. Ganti model lewat model picker (tab) atau per-run (--model) hanya mengganti model yang dipakai sesi itu; riwayat percakapan tetap utuh dan dikirim penuh ke model baru.

9Router Transparan untuk OpenCode

OpenCode melihat 9Router sebagai satu provider OpenAI-compatible biasa. Saat failover terjadi di balik layar, OpenCode tidak tahu modelnya berganti — ia tetap mengirim konteks sesi yang sama ke endpoint yang sama. Perilakunya persis seperti Hermes.

Perbandingan Hermes vs OpenCode

AspekHermes AgentOpenCode
Pemegang konteksGateway/sesi Hermes (history.messages)Session store OpenCode (transcript)
Switch model mid-sessionDinamis via configModel picker tab / --model, tanpa ubah config
Auto-failover native antar provider✅ Ada (fallback_providers)❌ Tidak ada — manual
Failover via 9RouterKonteks nyambung ✅Konteks nyambung ✅

Satu perbedaan penting: Hermes punya failover native antar provider, OpenCode tidak. Di OpenCode, 9Router justru perannya lebih besar — dialah satu-satunya mekanisme failover otomatisnya. Tanpa 9Router, ganti model di OpenCode harus manual.

Kesimpulan

Mengganti model di tengah sesi berpengaruh, terutama pada konteks dan konsistensi — tapi dampaknya bisa dikelola. Untuk masalah ketersediaan (rate limit, provider down), 9Router sudah menyelesaikan bagian terberatnya dengan failover otomatis. Untuk masalah konteks, kebiasaan kecil seperti menyimpan ringkasan pekerjaan setiap beberapa langkah sudah sangat membantu.

Pendekatan yang saya pakai sekarang: satu model untuk satu fitur utuh, dan 9Router sebagai jaring pengaman kalau model utama sedang bermasalah. Kombinasi yang cukup untuk sebagian besar pekerjaan harian.

Failover Model Cerdas: Pakai 9Router Biar Nggak Macet Rate Limit

Siapa yang tidak pernah mengalami ini: sedang asyik menulis kode dengan bantuan AI, tiba-tiba muncul pesan rate limit exceeded atau connection timeout. Apalagi saat sedang fokus menyelesaikan satu fitur. Alur kerja jadi terputus, dan kita harus menunggu atau berpindah tool secara manual.

Hari ini saya belajar tentang cara mengatasi masalah ini dengan lebih elegan: routing model dengan failover otomatis. Alih-alih terpaku pada satu model atau satu provider, kita bisa menyusun rantai model cadangan yang otomatis dipakai ketika model utama sedang bermasalah.

Apa Itu Failover Model?

Failover model adalah mekanisme di mana ketika satu model/provider gagal — karena rate limit, timeout, atau layanan sedang turun — permintaan kita otomatis dialihkan ke model lain yang sudah kita daftarkan sebagai cadangan. Kita cukup menentukan urutan prioritas, sisanya ditangani otomatis.

Salah satu tool yang menyediakan fitur ini adalah 9Router, sebuah AI router gratis yang bisa diarahkan dari berbagai tool coding seperti OpenCode maupun Hermes Agent.

Cara Kerja 9Router

Kita menentukan urutan prioritas model yang ingin dipakai. Contohnya, jika model utama sedang terkena limit, sistem akan otomatis mencoba model berikutnya dalam daftar:

opencode --model "9router://gpt-4o,claude-3.5-sonnet,gemini-1.5-flash"

Jika gpt-4o gagal (rate limit atau timeout), sistem langsung mencoba claude-3.5-sonnet, lalu gemini-1.5-flash — tanpa intervensi manual. Kita tidak perlu berhenti menulis kode hanya karena satu provider sedang sibuk.

Konfigurasi di OpenCode dan Hermes Agent

Untuk OpenCode, model dengan failover bisa dipanggil langsung lewat CLI:

opencode --model "9router://gpt-4o,claude-3.5-sonnet,command-r-plus"

Sementara di Hermes Agent, konfigurasi dapat ditambahkan pada file config.yaml:

providers:
  - name: 9router
    type: router
    model_chain: ["gpt-4o", "claude-3.5-sonnet", "gemini-1.5-flash"]

Keuntungan Menggunakan 9Router

FiturManfaat
Auto failoverAlur kerja tidak terputus saat rate limit
Multi-providerBisa memakai model terbaik yang tersedia saat itu
Tanpa konfigurasi manualCukup atur sekali, sisanya otomatis
Resume sessionKonteks percakapan tetap dilanjutkan di model berikutnya

Kapan Failover Tidak Cukup?

Untuk sesi debugging yang sangat panjang, failover model tetap disarankan dilengkapi dengan checkpoint — menyimpan ringkasan konteks setiap beberapa langkah. Ini menjaga agar model pengganti tetap memahami arah pekerjaan yang sedang dikerjakan.

Namun untuk mayoritas kasus, failover lewat 9Router sudah cukup. Fokus utama kita: menentukan urutan model yang masuk akal, lalu membiarkan sistem bekerja.

Kesimpulan

Mengganti model di tengah sesi kode bukan lagi hal yang merepotkan. Dengan 9Router, kita cukup menyusun urutan prioritas model — sistem yang akan memastikan pekerjaan tetap berjalan meski salah satu provider sedang bermasalah.