Jika endpoint status bar SVG dipanggil terus-menerus oleh Emacs, masalahnya sering bukan pada ukuran file SVG, melainkan pada cara backend menghasilkan respons. Gejala yang umum adalah CPU API naik tanpa lonjakan trafik besar, latensi endpoint status memburuk, dan cache terlihat aktif di atas kertas tetapi tidak pernah benar-benar hit.

Dalam studi kasus ini, kita bahas bagaimana layanan backend yang merender atau menyajikan SVG status bar dinamis bisa menjadi boros CPU. Konteksnya terinspirasi dari tren svg-line untuk Emacs: klien melakukan polling berkala untuk mengambil status visual. Di sisi server, masalah muncul ketika SVG dirender per request, metadata membuat ETag selalu berubah, dan query status dipanggil berulang meski datanya hampir tidak berubah.

Gejala yang Terlihat di Produksi

Kasus seperti ini biasanya tidak langsung terlihat sebagai bug render. Dari sisi aplikasi, semuanya tampak normal: endpoint hanya mengembalikan teks SVG, payload kecil, dan tidak ada operasi berat seperti unggah file atau komputasi numerik. Namun pola beban nyata menunjukkan hal berbeda.

  • CPU API naik meski throughput total tidak terlalu besar.
  • Latensi endpoint status meningkat lebih cepat dibanding endpoint lain.
  • Cache tidak efektif karena hampir semua request berakhir sebagai render penuh, bukan 304 atau cache hit.
  • Garbage collection bisa lebih sering muncul bila implementasi banyak membuat string sementara.
  • Database atau dependency internal ikut sibuk karena status dihitung ulang setiap polling.

Masalah ini makin terasa jika ada banyak editor aktif, masing-masing melakukan refresh dalam interval pendek. Satu klien yang polling tiap beberapa detik mungkin tidak masalah. Ratusan klien atau beberapa proses editor per pengguna bisa mengubah endpoint kecil menjadi hotspot.

Arsitektur Masalah: Kenapa SVG Kecil Bisa Mahal?

SVG memang hanya teks, tetapi backend tetap harus menyiapkan isi responsnya. Jika setiap request memicu rangkaian kerja seperti mengambil status terbaru, memformat warna, menghitung lebar elemen, menyusun XML, menambahkan metadata dinamis, lalu mengirim ETag baru, biaya totalnya menjadi signifikan.

Pola implementasi yang rawan

  • Setiap request memanggil beberapa query status yang sebenarnya sama untuk banyak pengguna atau sesi.
  • SVG dibangun dari nol menggunakan konkatenasi string berulang.
  • Respons menyertakan timestamp render atau request ID di dalam isi SVG.
  • ETag dihitung dari payload yang selalu berubah karena metadata dinamis.
  • Cache-Control sudah ada, tetapi validator cache tidak stabil sehingga browser atau klien tetap mengunduh ulang isi penuh.

Di atas kertas, endpoint ini tampak ringan. Dalam praktik, endpoint berubah menjadi kombinasi dari polling tinggi, render dinamis, dan cache miss terus-menerus.

Root Cause yang Paling Masuk Akal

1. Render SVG dilakukan per request

Ini akar masalah yang paling umum. Jika status bar merepresentasikan kondisi yang berubah perlahan—misalnya branch git, status layanan, indikator build, atau ringkasan kesehatan sistem—maka merender ulang SVG untuk setiap polling adalah pemborosan. Banyak request datang sebelum sumber datanya berubah.

Yang perlu dibedakan adalah frekuensi request dan frekuensi perubahan data. Jika data berubah setiap menit tetapi klien polling setiap 2-5 detik, maka sebagian besar render sebenarnya identik.

2. String builder atau konkatenasi boros alokasi

SVG sering dibangun sebagai string panjang. Jika implementasi menyusun banyak fragmen kecil, escape karakter berulang, atau melakukan format template berkali-kali, runtime akan membuat banyak objek sementara. Pada beban tinggi, ini meningkatkan alokasi memori dan tekanan GC.

Masalah ini biasanya tidak terlihat pada uji lokal satu request. Ia baru terasa saat ada request terus-menerus dan payload selalu digenerasi ulang.

3. ETag berubah karena metadata dinamis

Cache validator hanya berguna jika representasi resource stabil. Banyak implementasi tanpa sengaja menyisipkan data seperti:

  • waktu render,
  • nonce,
  • request ID,
  • durasi proses,
  • komentar debug di dalam SVG.

Akibatnya, dua respons yang secara visual sama tetap memiliki isi berbeda. ETag pun berubah di setiap request, sehingga klien tidak pernah menerima 304 Not Modified secara konsisten.

4. Query status dipanggil berulang

Sering kali backend SVG tidak hanya menyusun gambar, tetapi juga mengumpulkan data dari beberapa sumber: database, cache internal, API lain, atau command lokal. Jika seluruh pipeline dijalankan pada setiap request, biaya total jauh lebih besar daripada sekadar serialisasi SVG.

Masalah ini memburuk bila tidak ada deduplikasi atau agregasi. Misalnya satu endpoint memanggil tiga status internal yang masing-masing juga punya latensi sendiri.

Langkah Investigasi yang Efektif

Debugging endpoint seperti ini sebaiknya tidak dimulai dari optimasi acak. Mulailah dengan memisahkan tiga pertanyaan:

  1. Apakah mahalnya ada di pengambilan data?
  2. Apakah mahalnya ada di render SVG?
  3. Apakah cache sebenarnya bekerja?

1. Profiling CPU dan alokasi

Gunakan profiler yang sesuai dengan runtime Anda untuk melihat fungsi mana yang dominan. Fokus pada:

  • waktu yang habis di fungsi render,
  • alokasi string atau buffer,
  • parse/format template,
  • fungsi hashing untuk ETag,
  • query atau request downstream.

Jika hasil profiling menunjukkan banyak waktu di builder string atau encoder XML, masalah utamanya kemungkinan render per request. Jika waktu lebih banyak habis di I/O, masalahnya ada di pengambilan status.

2. Tracing per request

Tambahkan tracing sederhana pada endpoint status SVG. Pecah durasi menjadi beberapa fase:

  • ambil data status,
  • transformasi data,
  • render SVG,
  • hitung ETag,
  • tulis respons.

Pemecahan seperti ini penting karena tanpa segmentasi, semua terlihat hanya sebagai “endpoint lambat”.

// Pseudocode terstruktur, tidak terikat framework tertentu
func handleStatusSVG(req, res) {
  span := tracer.Start("status_svg")
  defer span.End()

  status := traceStep("load_status", func() Status {
    return loadStatusSnapshot()
  })

  svg := traceStep("render_svg", func() string {
    return renderSVG(status)
  })

  etag := traceStep("build_etag", func() string {
    return stableETag(svg)
  })

  if req.Header["If-None-Match"] == etag {
    res.StatusCode = 304
    return
  }

  res.Header["ETag"] = etag
  res.Header["Content-Type"] = "image/svg+xml"
  res.Write(svg)
}

Contoh di atas menegaskan titik ukur yang penting: jangan hanya mengukur total durasi endpoint.

3. Bandingkan payload antar request

Ini langkah yang sering diabaikan, padahal sangat cepat memberi petunjuk. Ambil dua atau tiga respons SVG berturut-turut saat data seharusnya tidak berubah, lalu lakukan diff.

curl -s http://api.internal/status.svg > a.svg
sleep 2
curl -s http://api.internal/status.svg > b.svg
diff -u a.svg b.svg

Jika perbedaannya hanya timestamp, request ID, atau urutan atribut yang tidak stabil, Anda sudah menemukan alasan kenapa cache gagal.

4. Validasi header cache, bukan asumsi cache

Banyak sistem merasa “sudah pakai cache” karena ada Cache-Control dan ETag. Itu belum cukup. Yang perlu dicek adalah apakah klien benar-benar mengirim validator, dan apakah server benar-benar membalas 304 saat isi belum berubah.

Gunakan pemeriksaan seperti:

curl -i http://api.internal/status.svg
curl -i -H 'If-None-Match: "etag-sebelumnya"' http://api.internal/status.svg

Periksa hal-hal berikut:

  • Apakah ETag stabil untuk konten yang sama?
  • Apakah ada Cache-Control yang masuk akal untuk pola polling?
  • Apakah respons 304 benar-benar terjadi?
  • Apakah ada proxy atau gateway yang menghapus atau mengubah header?

Contoh Implementasi yang Bermasalah

Pola berikut sering menjadi sumber CPU tinggi:

// Pseudocode anti-pattern
func statusSVGHandler(req, res) {
  status := queryDatabaseAndServices()   // dipanggil setiap request

  svg := "<svg ...>"
  svg += "<text>" + status.Label + "</text>"
  svg += "<text>generated: " + nowISO8601() + "</text>"
  svg += "<!-- request-id: " + newRequestID() + " -->"
  svg += "</svg>"

  etag := hash(svg)
  res.Header["ETag"] = etag
  res.Header["Cache-Control"] = "public, max-age=30"
  res.Write(svg)
}

Masalahnya ada di beberapa lapisan sekaligus:

  • Data status selalu diambil ulang.
  • SVG selalu dirender ulang.
  • Konten berubah di setiap request karena timestamp dan request ID.
  • ETag jadi selalu berbeda.
  • Cache-Control tidak membantu karena validator tidak pernah cocok.

Perbaikan Konkret yang Layak Diterapkan

1. Precompute SVG dari snapshot status

Alih-alih merender saat request datang, hitung status lebih dulu dalam interval terkontrol atau saat ada perubahan data, lalu simpan hasil SVG final. Endpoint HTTP tinggal membaca hasil yang sudah siap.

Pendekatan ini cocok bila status tidak perlu akurat per milidetik. Untuk status bar editor, kompromi beberapa detik biasanya dapat diterima dan jauh lebih murah.

// Pseudocode: update snapshot di background
var currentSVG string
var currentETag string
var currentVersion string

func refreshStatusSnapshot() {
  status := loadStatusSnapshot()
  svg := renderSVG(status)              // tanpa metadata dinamis
  etag := stableETag(svg)

  currentSVG = svg
  currentETag = etag
  currentVersion = status.VersionKey    // opsional untuk observability
}

func statusSVGHandler(req, res) {
  if req.Header["If-None-Match"] == currentETag {
    res.StatusCode = 304
    return
  }

  res.Header["Content-Type"] = "image/svg+xml"
  res.Header["ETag"] = currentETag
  res.Header["Cache-Control"] = "public, max-age=5, stale-while-revalidate=30"
  res.Write(currentSVG)
}

Kenapa ini efektif? Karena biaya mahal dipindahkan dari jalur request ke proses background yang frekuensinya lebih rendah dan lebih bisa dikendalikan.

2. Buat ETag deterministik

ETag harus merepresentasikan isi yang relevan, bukan detail insidental. Jika SVG visualnya sama, ETag seharusnya sama. Hindari memasukkan:

  • timestamp render,
  • ID acak,
  • komentar debug,
  • urutan field yang tidak stabil.

Jika perlu metadata internal untuk debugging, taruh di log atau tracing, bukan di payload yang mempengaruhi validator cache.

3. Gunakan representasi status yang stabil

Daripada menghitung ETag dari string SVG mentah yang mungkin sensitif terhadap perubahan kecil format, Anda bisa menghitung dari model status yang sudah dinormalisasi. Misalnya, gunakan objek status yang field-nya terurut dan hanya memuat data yang berpengaruh pada tampilan.

Keuntungannya:

  • ETag tidak berubah hanya karena whitespace atau formatting SVG.
  • Perubahan visual tetap terdeteksi.
  • Lebih mudah diuji karena basisnya adalah snapshot data, bukan output string acak.

4. Kurangi refresh dari sisi klien

Tidak semua perbaikan harus terjadi di server. Jika Emacs atau integrasi klien melakukan polling terlalu agresif, pertimbangkan pembatasan refresh. Misalnya:

  • naikkan interval polling,
  • lakukan backoff saat status stabil,
  • hanya refresh saat buffer aktif atau jendela terlihat,
  • hindari beberapa komponen klien memanggil endpoint yang sama secara paralel.

Trade-off-nya jelas: status jadi sedikit kurang real-time. Namun untuk status bar visual, penurunan beban sering jauh lebih berharga daripada pembaruan super sering yang nyaris tidak terlihat pengguna.

5. Cache hasil query status, bukan hanya SVG

Jika data status disusun dari beberapa sumber, cache juga bisa diterapkan pada lapisan data. Ini berguna bila Anda tetap perlu merender beberapa varian SVG tetapi sumber statusnya sama.

Pilih pendekatan sesuai karakter sistem:

  • Cache SVG final jika variasi output sedikit dan mahal utama ada di render.
  • Cache snapshot status jika output punya beberapa tema/warna/ukuran tetapi datanya sama.
  • Event-driven refresh jika status berubah karena event yang jelas, misalnya hasil job atau perubahan state internal.

Kesalahan Umum Saat Memperbaiki Masalah Ini

Menambah cache TTL panjang tanpa memperbaiki validator

Jika ETag tetap berubah setiap request, TTL saja tidak menyelesaikan masalah. Klien tetap akan sering menerima konten baru atau gagal memanfaatkan conditional request.

Mengoptimalkan string builder sebelum menghapus render per request

Perbaikan mikro di builder string memang membantu, tetapi sering bukan penyelesaian utama. Menghilangkan render berulang biasanya memberi dampak lebih besar daripada sekadar mengganti teknik konkatenasi.

Mengabaikan beban downstream

CPU endpoint bisa terlihat tinggi, padahal sumber lambat sebenarnya adalah query status ke database atau service lain. Karena itu tracing lintas komponen penting.

Memakai data debug di payload produksi

Informasi seperti waktu render, node host, atau request ID sebaiknya tidak dimasukkan ke SVG publik jika itu mengganggu kestabilan cache.

Checklist Verifikasi Setelah Fix

Setelah perubahan diterapkan, jangan berhenti pada “CPU turun”. Pastikan perilakunya benar-benar sehat.

  • Proporsi respons 304 Not Modified naik untuk klien yang polling.
  • Latensi endpoint status SVG turun dan lebih stabil.
  • CPU aplikasi turun pada jam polling tinggi.
  • Jumlah query atau panggilan downstream per request berkurang.
  • Ukuran alokasi memori per request menurun.
  • ETag tetap sama selama status visual tidak berubah.

Observability Pasca-Fix

Masalah seperti ini mudah kambuh ketika ada penambahan metadata baru atau perubahan kecil di pipeline render. Karena itu, observability setelah fix sama pentingnya dengan debugging awal.

Minimal, pantau metrik berikut:

  • request rate endpoint status SVG,
  • status code distribution, terutama rasio 200 vs 304,
  • latensi p50/p95,
  • CPU time per endpoint,
  • cache hit ratio jika memakai cache aplikasi atau reverse proxy,
  • jumlah refresh snapshot di proses background.

Tambahkan juga log sampling untuk perubahan ETag. Bukan untuk mencatat setiap request, melainkan untuk mendeteksi apakah validator terlalu sering berubah tanpa alasan bisnis yang jelas.

Catatan praktis: jika Anda perlu membedakan penyebab perubahan status, simpan version key atau hash snapshot status di log/tracing internal. Jangan letakkan penanda itu di payload SVG jika tidak benar-benar dibutuhkan oleh klien.

Penutup

Kasus status bar SVG membuat API Emacs boros CPU hampir selalu berakar pada ketidakcocokan antara pola polling klien dan desain backend. Endpoint tampak kecil, tetapi menjadi mahal karena render SVG dilakukan per request, string builder menghasilkan banyak alokasi, ETag tidak stabil, dan query status dipanggil berulang.

Perbaikannya biasanya tidak rumit secara konsep: precompute SVG, gunakan snapshot status yang stabil, buat ETag deterministik, batasi refresh klien, dan ukur hasilnya lewat observability yang tepat. Dengan pendekatan ini, endpoint status tetap dinamis untuk Emacs, tetapi tidak lagi menghabiskan CPU API secara tidak perlu.