Dulu, nyari query yang bikin database spike itu bisa makan berhari-hari. Yang nyari capek, yang nge-fix juga capek. Sekarang? Hitungan jam β dan bonusnya, sambil belajar hal baru juga.
Ceritanya begini. Kalau kamu pernah kerja di aplikasi yang pakai ORM (Object-Relational Mapping β semacam "penerjemah otomatis" antara code dan database), pasti familiar sama situasi ini: database tiba-tiba lambat, kamu dapet raw query yang jadi biang kerok, tapi di codebase kamu nulis pakai syntax ORM yang bentuknya beda jauh dari SQL mentah itu.
Buat yang belum pernah deal sama ORM, bayangin gini: kamu nulis pesan dalam bahasa Indonesia, lalu ada "penerjemah otomatis" yang convert jadi bahasa Jepang sebelum dikirim ke penerima. Suatu hari ada masalah di pesan yang terkirim β tapi kamu cuma bisa lihat versi bahasa Jepang-nya. Nyari bagian mana dari tulisan Indonesia kamu yang bikin terjemahan-nya bermasalah? Itu effort-nya yang bikin pengen balik tidur aja.
Sekarang dengan bantuan Kiro, cukup kasih raw query + akses ke codebase, dia otomatis nyari bagian mana di code yang nge-generate query bermasalah itu. Yang dulu butuh berhari-hari, sekarang bisa selesai dalam hitungan jam β dan itu baru tahap investigasi, belum termasuk fixing-nya.
Akhir-akhir ini lagi aktif pakai Kiro di tempat kerja. Awal tahun lalu kantor dapat credits melalui program Kiro for Startup, jadi ya sekalian dimaksimalkan.
Selain buat debug dan explore query di MS SQL Server, kadang pakai Kiro juga buat analisa log AWS CloudWatch β sambil kasih context aplikasi yang running biar analisa-nya lebih akurat dan gak generic.
Di tulisan kali ini, saya mau sharing gimana pakai Kiro sebagai partner beberapa minggu terakhir buat improve query performance di aplikasi .NET Core. Kenapa "partner"? Karena Kiro-nya gak boleh langsung akses ke database β jadi wajib melalui perantara saya. Kita discuss, kolaborasi, dan nge-solve bareng. Bukan AI yang dikasih tombol terus disuruh jalan sendiri.
Beberapa waktu lalu, metrics database kami menunjukkan alarm β data I/O sering spike. Ini harus dicek dan di-improve sebelum user mulai ngerasain.
Dulu kayak yang saya bilang tadi β ini pekerjaan yang paling malas saya kerjakan. Ribet-nya di level: kita cuma dapet raw query yang di-generate ORM, sementara di codebase kita nulis pakai syntax yang bentuknya jauh berbeda. Gak langsung ketahuan code bagian mana yang nge-generate query lambat itu.
Buat gambaran lebih simpel: ORM itu kayak kamu bilang ke asisten "ambilin data pesanan yang aktif, urutin dari yang terbaru ya." Si asisten nerjemahin instruksi itu jadi perintah teknis ke database. Nah kalau ternyata cara dia nerjemahin gak efisien dan bikin database lambat, kamu harus reverse-engineer dari hasil terjemahan teknis itu balik ke instruksi awalmu β dan itu yang bikin pusing.
Karena database gak bisa dikasih akses langsung ke AI, maka flow-nya begini: Kiro kasih instruksi query apa yang perlu saya jalankan, saya eksekusi di database, dan hasilnya saya kirim balik ke dia untuk dianalisa.
Saya mulai dengan kasih context:
"Saya punya database MS SQL Server yang beberapa menit lalu spike data I/O-nya. Kita akan cari tahu proses/query apa penyebabnya. Kamu info ke saya query apa yang perlu dijalankan, nanti saya yang eksekusi dan kirim hasilnya untuk kamu analisa."
Dari situ, Kiro kasih beberapa diagnostic query yang perlu saya run:
Setelah saya jalankan semua dan kirim hasilnya, Kiro langsung menganalisa dan kasih output yang cukup komprehensif β dari executive summary sampai rencana aksi per-priority.
Kiro kasih temuan lengkap dengan severity level:
| Temuan | Severity |
|---|---|
| Read latency sangat tinggi (130-169ms avg) | π΄ Critical |
| 86% wait time = PAGEIOLATCH_SH (read I/O) | π΄ Critical |
| Missing index pada kolom filter utama | π΄ Critical |
| 3 index tidak pernah dibaca (0 reads, 24K+ writes) | π‘ Warning |
| Eager sebelum pagination di ORM | π΄ Critical |
Yang menarik, dia gak berhenti di level database. Dia langsung identify pattern di code yang bermasalah:
// Pattern bermasalah yang ditemukan Kiro:
var data = _db.Orders
.Include(a => a.OrderItems) // β Eager load SEMUA items dulu
.AsNoTracking()
.Where(x => x.TenantId == tenantId && !x.Deleted)
.OrderByDescending(x => x.UpdatedAt)
.Skip(page * size)
.Take(size)
.ToListAsync();
Buat non-.NET developer: .Include()
itu kayak bilang "sekalian ambilin semua data relasi-nya juga." Masalahnya ini ditaruh sebelum pagination β artinya database load semua data beserta relasi-nya ke memory dulu, baru dipotong per halaman. Kalau datanya jutaan rows? Ya Out of Memory.
Kiro kasih rekomendasi fix dengan pendekatan 2-step:
// Step 1: Ambil ID aja dulu (ringan, tanpa relasi)
var pagedIds = await _db.Orders
.Where(x => x.TenantId == tenantId && !x.Deleted)
.OrderByDescending(x => x.UpdatedAt)
.Skip(page * size)
.Take(size)
.Select(x => x.Id)
.ToListAsync();
// Step 2: Baru load full data + relasi, cuma untuk ID yang sudah di-page
var result = await _db.Orders
.Include(a => a.OrderItems)
.AsNoTracking()
.Where(x => pagedIds.Contains(x.Id))
.ToListAsync();
Konsepnya universal: jangan load semua dulu baru potong β potong dulu, baru load yang diperlukan. Ini applicable di framework dan bahasa apapun yang punya ORM.
Selain fix di level code, Kiro juga kasih rencana aksi di level database:
Kalau dilihat rencana aksi di atas, Kiro gak cuma menganalisa raw query yang lambat terus kasih saran generic "tambahin index." Yang bikin beda: dia juga mempelajari code backend .NET Core yang jadi sumber query tersebut.
Jadi misalnya dari raw query yang lambat, dia langsung bisa trace sendiri β "Oh ini query-nya di-generate dari OrderController.GetDataTableParams
, line sekian, di repository method GetOrdersWithDateFilter
." Saya gak perlu kasih tahu file mana β dia cari sendiri di codebase.
Dari situ saran-nya jadi spesifik ke situasi kita, bukan template jawaban:
.Include()
yang posisinya bikin database load jutaan row sebelum pagination jalan β itu saran refactor di level code, bukan di level database.Ini yang beda dari cuma kasih satu potongan query ke AI terus minta pendapat. Karena Kiro punya akses ke seluruh codebase, dia bisa connect the dots β dari "query lambat di database" ke "code pattern di aplikasi yang nge-generate query itu" β dan kasih solusi yang nyambung di kedua sisi sekaligus.
Hal yang sering saya dengar: "AI bikin kita makin bodoh dan malas belajar." Pengalaman saya di case ini justru kebalikannya.
Begini ceritanya. Waktu Kiro nemuin pattern bermasalah di codebase, dia gak cuma bilang "ini salah, ganti jadi begini." Dia jelaskan kenapa itu salah, apa yang terjadi di balik layar, dan kapan pattern itu sebenernya boleh dipakai. Dan saya β yang udah bertahun-tahun nulis code .NET β baru ngeh beberapa hal fundamental yang selama ini terlewat.
Yang bikin ini jadi "teman belajar" dan bukan sekadar autocomplete pintar: setiap kali Kiro nemuin sesuatu yang menarik, saya minta dia jelaskan lebih detail dan simpan jadi file markdown tersendiri. Judulnya saya buat kayak catatan belajar β "What I Learned Today: Kenapa .ToLower() di LINQ Query Bikin Database Lambat."
File-file itu kemudian saya generate jadi PDF dan share ke Slack tim. Jadi bukan cuma saya yang belajar β satu tim ikut dapat insight-nya.
Ini beberapa contoh yang saya pelajari dari proses ini:
.ToLower()
di ORM Query Ternyata Bikin Database Lambat
Kiro nemuin pattern ini tersebar di banyak repository:
// β Bikin index gak bisa dipakai
db.Products.Any(x => x.Code.ToLower() == code.ToLower());
Kenapa bermasalah? ORM nerjemahin .ToLower()
jadi function LOWER()
di SQL. Ketika ada function yang di-apply ke kolom database di WHERE
clause, index jadi gak bisa dipakai β SQL Server harus scan semua row satu per satu.
Di tabel dengan 1.6 juta rows: yang harusnya < 1 milidetik jadi 2-5 detik. Dan di case kita, function ini dipanggil ~2,400 kali dalam 1 jam. Itu artinya 2,400 full table scan yang seharusnya gak perlu.
Ternyata database kami pakai collation Case Insensitive (CI
) β artinya perbandingan string sudah otomatis case-insensitive tanpa perlu function apapun:
// β
Index terpakai, hasil sama karena collation DB sudah CI
db.Products.Any(x => x.Code == code);
Yang Kiro jelaskan dan bikin saya "ohhh" itu bedanya context: kalau query-nya ke database (LINQ-to-SQL), .ToLower()
gak perlu karena collation yang handle. Tapi kalau data sudah di-load ke memory (LINQ-to-Objects), .ToLower()
atau StringComparison.OrdinalIgnoreCase
tetap wajib karena C# itu case-sensitive. Syntax-nya sama, behavior-nya beda total tergantung context. Bertahun-tahun nulis LINQ dan baru kali ini benar-benar paham bedanya.
Kondisi OR yang Ternyata Redundan
Pattern ini ditemukan di fitur sync mobile:
// β Redundan dan bikin index gak optimal
.Where(a => a.UpdatedAt >= param || a.CreatedAt >= param)
Dua masalah:
OR
di dua kolom berbeda bikin database gak bisa pakai index secara efisien β harus scan dua range terpisah lalu merge hasilnyaCreatedAt
itu UpdatedAt
di-set sama dengan CreatedAt
. Dan setiap kali di-update, cuma UpdatedAt
yang berubah. Jadi UpdatedAt
selalu >= CreatedAt
Yang bikin ini jadi momen belajar: Kiro gak cuma bilang "hapus aja OR-nya." Dia trace ke SaveChangesHelper
di codebase, tunjukin logic-nya β bahwa di entity lifecycle kita, UpdatedAt
selalu di-set bareng CreatedAt
saat insert, dan cuma UpdatedAt
yang berubah saat update. Jadi secara matematis, kondisi OR itu gak pernah menambah hasil apapun.
Cukup:
// β
Satu kondisi, index optimal
.Where(a => a.UpdatedAt >= param)
Dan masih ada beberapa hal lain yang saya pelajari β cukup banyak sampai sepertinya perlu dipisah jadi artikel tersendiri nanti.
Point-nya: selagi kita bisa prompting dengan tepat dan punya curiosity buat nanya "kenapa?", AI malah jadi accelerator buat belajar. Dia bukan pengganti berpikir β dia pembuka pintu ke hal-hal yang selama ini gak kita tahu ada. Dan kalau kita rajin simpan insight-nya, satu sesi belajar bisa jadi knowledge sharing buat satu tim.
Ini bagian yang menurut saya paling impactful.
Setelah diskusi panjang β bolak-balik kirim query, terima analisa, diskusi solusi β saya mulai sadar: "Pattern-nya udah ketemu. Kalau besok database lain spike, masa harus ulang dari awal lagi?"
Jadi saya minta ke Kiro: dari diskusi kita hari ini, formalisasi jadi sesuatu yang reusable.
Konkretnya:
sql-audit/
β pisahkan per file berdasarkan tujuannyaDan Kiro langsung eksekusi. Hasilnya:
Folder sql-audit/ β semua diagnostic query tersusun rapi per file sesuai urutan investigasi. Kedepannya kalau ada masalah I/O, gak perlu mikir "harus mulai dari mana?" β tinggal buka folder, run satu per satu.
| # | File | Tujuan |
|---|---|---|
| 1 | 01_top_io_queries.sql |
|
| Top query penyebab I/O historis | ||
| 2 | 02_currently_running_io_heavy.sql |
|
| Query yang berjalan saat spike | ||
| 3 | 03_io_stats_per_database_file.sql |
|
| Latency per database file | ||
| 4 | 04_io_wait_stats.sql |
|
| Wait type I/O yang dominan | ||
| 5 | 05_current_indexes.sql |
|
| Review semua index | ||
| 6 | 06_index_usage_stats.sql |
|
| Index terpakai atau sia-sia | ||
| 7 | 07_missing_indexes.sql |
|
| Rekomendasi index dari SQL Server | ||
| 8 | 08_index_physical_stats.sql |
|
| Fragmentasi index | ||
| 9 | 09_azure_resource_stats.sql |
|
| Resource usage (Azure) | ||
| 10 | 10_io_per_table.sql |
|
| Tabel dengan I/O tertinggi |
Template Prompt (db-query-optimization-prompt.md
) β instruksi lengkap: apa yang harus Kiro analisa, format output yang diharapkan, severity classification, dan rencana aksi yang harus di-generate. Semacam "SOP" yang tinggal dipanggil.
Sekarang kalau ada database spike, flow saya literally cuma:
Menggunakan prompt di bawah ini, tolong analisa hasil performance database ini.
File prompt: query-optimization/db-query-optimization-prompt.md
Hasil SQL audit terbaru: /sql-audit
Done. Gak ada diskusi panjang lagi. Gak ada "eh kemarin kita bahas apa ya." Semuanya sudah ter-encode di template.
Takeaway-nya: diskusi panjang sama AI itu berharga, tapi lebih berharga lagi kalau kamu convert hasilnya jadi checkpoint yang reusable. Banyak orang pakai AI tapi knowledge dari diskusi-nya "buang" begitu aja β chat ditutup, ilang. Padahal kamu bisa minta AI-nya formalisasi jadi prompt template, folder terstruktur, atau workflow yang bisa jalan tanpa context sebelumnya.
Satu masalah lagi yang muncul di awal-awal: gimana melanjutkan progress lintas hari?
Contohnya, tanggal 13 kita temuin masalah dan apply fix. Tapi butuh beberapa hari buat lihat impact-nya. Pas mau lanjut diskusi tanggal 15 atau 28, chat sebelumnya sudah tertutup dan ribet nyarinya.
Solusinya simpel: setiap selesai satu session analisa, minta Kiro simpan hasilnya ke file dengan format tanggal:
query-optimization/orders-db/2026-05-13_initial_analysis.md
query-optimization/orders-db/2026-05-28_follow_up_review.md
Di dalamnya tercatat semua: temuan, saran, yang sudah di-fix, yang belum, reasoning di balik setiap keputusan. Termasuk hal kayak "tanggal 13 Kiro sarankan buat index X, tapi di follow-up tanggal 28 minta dihapus karena setelah code di-refactor ternyata gak efektif lagi."
Jadi prompt selanjutnya tinggal:
Analisa hasil performance database ini.
- File prompt: db-query-optimization-prompt.md
- Hasil SQL audit terbaru: /sql-audit
- Konteks diskusi sebelumnya: query-optimization/orders-db/
Kiro langsung punya full context. Diskusinya berkelanjutan, saling melengkapi, dan AI-nya dapat gambaran luas tanpa harus dijelaskan ulang dari nol setiap session baru.
Yang gak saya sangka β folder change log itu ternyata punya kehidupan kedua. Sekarang folder itu jadi bagian dari code review automation.
Salah satu rule di code review skill agent kami:
"Pastikan perubahan code mengikuti best practice dan menghindari kesalahan yang pernah dilakukan β datanya ada di folder
query-optimization/
. Pastikan setiap perubahan baru optimal dari sisi query maupun index."
Jadi saat AI melakukan code review pull request developer, dia juga cross-check sama history kesalahan yang pernah terjadi. Feedback loop-nya tertutup β kesalahan yang sama diminimalisir tanpa bergantung pada ingatan manusia.
Dari yang awalnya cuma "debug database spike," sekarang jadi:
Satu masalah, lima output. Gak direncanakan dari awal β tapi karena setiap langkah dibikin reusable, domino-nya jalan sendiri.
Itu sharing kali ini. Kiro bukan cuma bikin kerjaan lebih cepat β tapi juga bikin proses belajar lebih terstruktur, workflow lebih repeatable, dan knowledge gak hilang di chat yang tertutup.
Yang capek bukan database-nya. Yang capek itu manusia yang harus ulang-ulang proses yang sama tanpa sistem.
Sampai jumpa di tulisan selanjutnya. π
About Me
Halo, nama saya Ifan Jaya Suswanto Zalukhu. Di tahun 2026 ini adalah tahun kedua sebagai AWS Community Builder, sekaligus Co-Lead AWS User Group Medan. Suka sharing dan excited belajar hal baru β terutama di intersection cloud, AI, dan engineering productivity.