Deploy RubyLLM ke produksi tidak cukup hanya memastikan aplikasi Ruby bisa berjalan. Risiko utama justru sering muncul dari sisi integrasi AI: provider lambat, model berubah perilaku, error rate naik, biaya melonjak, atau fallback tidak bekerja saat dibutuhkan. Karena itu, deployment yang aman harus dirancang untuk membatasi blast radius, mendeteksi masalah cepat, dan memungkinkan rollback tanpa menunggu rilis ulang besar.

Pendekatan yang paling praktis adalah menggabungkan canary release, feature flag per provider/model, timeout dan retry yang ketat, fallback yang terukur, serta observability yang memantau latensi, error, dan biaya. Di artikel ini, RubyLLM dipakai sebagai konteks integrasi LLM di aplikasi Ruby, sementara fokus utamanya adalah praktik deployment produksi yang reliabel.

Mengapa deploy aplikasi RubyLLM lebih berisiko dibanding endpoint biasa

Endpoint aplikasi web biasa umumnya bergantung pada database, cache, dan service internal yang perilakunya relatif stabil. Pada aplikasi berbasis LLM, Anda menambahkan dependency eksternal yang lebih sulit diprediksi: latensi jaringan ke provider AI, limit rate, variasi output model, kebijakan throttling, hingga biaya per request yang dapat berubah sesuai pola trafik.

Masalah umum saat deploy ke produksi antara lain:

  • Latensi naik setelah deploy karena prompt lebih panjang atau model default berubah.
  • Error rate meningkat karena provider tertentu bermasalah, rate-limited, atau respons tidak sesuai ekspektasi parser.
  • Biaya melonjak akibat fallback ke model yang lebih mahal atau retry yang berlebihan.
  • Hasil tidak konsisten karena sebagian traffic masuk ke model baru tanpa kontrol rollout.
  • Rollback lambat karena perubahan perilaku AI tidak dibungkus dalam feature flag.

Karena itu, strategi deploy harus memisahkan dua hal: deploy kode dan aktivasi perilaku AI. Kode bisa naik ke produksi lebih dulu, tetapi provider, model, prompt, atau fallback diaktifkan bertahap lewat konfigurasi dan flag.

Arsitektur deploy yang aman untuk RubyLLM

Pisahkan routing AI dari logika bisnis

Jangan biarkan controller atau service bisnis langsung memutuskan provider dan model. Buat satu lapisan khusus, misalnya LLM gateway atau AI client wrapper, yang bertanggung jawab atas:

  • pemilihan provider/model,
  • timeout, retry, dan circuit breaker sederhana,
  • fallback saat provider utama gagal,
  • logging dan tracing,
  • pengukuran biaya dan penggunaan token bila tersedia.

Dengan pola ini, rollback perilaku AI bisa dilakukan di satu tempat tanpa menyentuh banyak endpoint.

Contoh wrapper service Ruby

class AiGateway
  ProviderResult = Struct.new(:text, :provider, :model, :latency_ms, :fallback_used, keyword_init: true)

  def initialize(config:, logger:, tracer:, metrics:)
    @config = config
    @logger = logger
    @tracer = tracer
    @metrics = metrics
  end

  def generate_text(task:, prompt:, user_id:, request_id:)
    primary = route_for(task)
    started = monotonic_ms

    with_trace("ai.generate", request_id: request_id, user_id: user_id, provider: primary[:provider], model: primary[:model]) do
      result = call_provider(primary, prompt, request_id: request_id)
      latency = monotonic_ms - started
      record_success(primary, latency)

      ProviderResult.new(
        text: result,
        provider: primary[:provider],
        model: primary[:model],
        latency_ms: latency,
        fallback_used: false
      )
    end
  rescue Timeout::Error, StandardError => e
    record_failure(primary, e)
    fallback = fallback_for(task)
    raise if fallback.nil?

    fallback_started = monotonic_ms
    result = call_provider(fallback, prompt, request_id: request_id)
    latency = monotonic_ms - fallback_started
    record_success(fallback, latency, fallback: true)

    ProviderResult.new(
      text: result,
      provider: fallback[:provider],
      model: fallback[:model],
      latency_ms: latency,
      fallback_used: true
    )
  end

  private

  def route_for(task)
    @config.fetch(:routes).fetch(task)
  end

  def fallback_for(task)
    @config.fetch(:fallbacks, {})[task]
  end

  def call_provider(route, prompt, request_id:)
    Timeout.timeout(route.fetch(:timeout_seconds)) do
      RubyLLM.chat(model: route.fetch(:model)) do |chat|
        chat.with_instructions(route[:system_prompt]) if route[:system_prompt]
        chat.ask(prompt)
      end
    end
  end

  def with_trace(name, attrs = {})
    @tracer.in_span(name, attributes: attrs) { yield }
  end

  def record_success(route, latency, fallback: false)
    @metrics.increment("ai.requests", tags: { provider: route[:provider], model: route[:model], status: "ok", fallback: fallback.to_s })
    @metrics.histogram("ai.latency_ms", latency, tags: { provider: route[:provider], model: route[:model] })
  end

  def record_failure(route, error)
    @logger.warn(
      event: "ai_request_failed",
      provider: route[:provider],
      model: route[:model],
      error_class: error.class.name,
      error_message: error.message
    )
    @metrics.increment("ai.requests", tags: { provider: route[:provider], model: route[:model], status: "error" })
  end

  def monotonic_ms
    Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
  end
end

Contoh di atas sengaja generik. Intinya bukan API detail RubyLLM, tetapi pola operasionalnya: semua panggilan AI lewat satu gateway agar lebih mudah diobservasi, dibatasi, dan di-rollback.

Konfigurasi yang bisa diubah tanpa deploy ulang

Simpan keputusan routing AI di konfigurasi runtime atau feature flag, bukan di hardcode. Contoh struktur sederhana:

ai:
  routes:
    summarize:
      provider: primary_vendor
      model: fast-model
      timeout_seconds: 8
      system_prompt: "Ringkas dengan bahasa Indonesia formal."
  fallbacks:
    summarize:
      provider: secondary_vendor
      model: cheaper-backup-model
      timeout_seconds: 6

Keuntungan utamanya: saat provider utama bermasalah, Anda dapat memindahkan traffic ke provider cadangan atau mematikan fitur tertentu tanpa membangun image baru.

Canary release untuk RubyLLM: batasi blast radius sejak awal

Apa yang dicanary-kan?

Pada aplikasi AI, canary tidak harus berarti deploy ke 5% pod lalu selesai. Anda bisa melakukan canary pada beberapa level:

  • Canary kode aplikasi: sebagian instance menjalankan versi baru.
  • Canary routing AI: hanya sebagian request memakai provider/model baru.
  • Canary prompt: prompt baru hanya aktif untuk cohort tertentu.
  • Canary per tenant/user: lebih aman untuk B2B karena mudah dilacak dampaknya.

Untuk RubyLLM, canary yang paling berguna biasanya adalah canary model/provider, karena risiko terbesar sering ada pada dependency AI, bukan pada proses deploy aplikasi itu sendiri.

Strategi rollout yang realistis

  1. Deploy kode lebih dulu dengan flag masih mati.
  2. Aktifkan canary untuk internal user atau tenant kecil.
  3. Naikkan bertahap misalnya 1% → 5% → 25% → 50% → 100% sesuai metrik.
  4. Pantau error, latensi, fallback rate, dan biaya di setiap tahap.
  5. Rollback flag jika indikator melewati ambang yang Anda tetapkan.

Yang penting bukan angka persisnya, tetapi adanya gate yang jelas untuk lanjut atau berhenti. Jangan menaikkan traffic hanya berdasarkan “sepertinya aman”.

Feature flag per provider AI

Gunakan flag yang cukup granular. Hindari satu flag besar seperti new_ai_enabled. Lebih aman memecahnya menjadi:

  • ai.provider.primary_vendor.enabled
  • ai.provider.secondary_vendor.enabled
  • ai.task.summarize.model
  • ai.task.support_reply.canary_percentage
  • ai.fallback.enabled

Granularitas ini penting untuk rollback cepat. Jika satu provider bermasalah, Anda cukup mematikannya tanpa menonaktifkan semua fitur AI.

def route_for(task, user_id:)
  route = config[:routes].fetch(task)

  if canary_enabled_for?(task, user_id)
    config[:canary_routes].fetch(task)
  else
    route
  end
end

def canary_enabled_for?(task, user_id)
  percentage = feature_flags.get("ai.task.#{task}.canary_percentage").to_i
  bucket = Zlib.crc32(user_id.to_s) % 100
  bucket < percentage
end

Pola hashing seperti ini berguna agar user yang sama konsisten masuk cohort yang sama selama rollout.

Health check, timeout, retry, dan fallback yang tidak berbahaya

Health check: jangan sekadar memeriksa proses hidup

Health check produksi untuk aplikasi RubyLLM sebaiknya dibagi menjadi dua:

  • Liveness: proses aplikasi masih hidup.
  • Readiness: aplikasi siap menerima traffic, termasuk dependency penting minimum tersedia.

Namun ada trade-off penting: jangan membuat readiness check terlalu bergantung pada provider AI eksternal jika itu menyebabkan seluruh pod dianggap tidak sehat saat provider sedang gangguan global. Lebih aman menjadikan kesehatan provider AI sebagai sinyal routing internal, bukan satu-satunya penentu readiness pod.

Praktiknya, readiness bisa memeriksa konfigurasi dasar, koneksi database/cache, dan status komponen internal. Sementara kesehatan provider AI dipantau oleh background probe atau metrik request nyata.

Timeout: wajib lebih ketat dari default

Jangan mengandalkan timeout bawaan HTTP client atau SDK. Untuk AI, timeout yang terlalu longgar akan menumpuk request, memperburuk antrean, dan membuat rollback terasa terlambat. Atur timeout berdasarkan jenis task:

  • task sinkron untuk request web: timeout lebih pendek,
  • task asynchronous/background: boleh sedikit lebih longgar,
  • streaming: butuh strategi timeout yang berbeda antara koneksi awal dan idle timeout.

Kesalahan umum adalah menyamakan semua timeout untuk seluruh endpoint. Ringkasan satu paragraf dan ekstraksi data biasanya punya profil latensi yang berbeda.

Retry: hanya untuk error yang layak diulang

Retry membantu saat terjadi kegagalan jaringan sementara atau throttling singkat. Tapi retry yang salah bisa menggandakan biaya dan latensi. Terapkan beberapa aturan:

  • Batasi jumlah retry, biasanya kecil.
  • Gunakan backoff dan idealnya jitter.
  • Jangan retry semua error; misalnya validasi request rusak tidak akan membaik dengan retry.
  • Perhatikan idempotensi, terutama bila output AI memicu aksi lanjutan.

Jika request bersifat sinkron di jalur web, sering kali lebih aman memakai satu percobaan + fallback daripada banyak retry.

Fallback model/provider: alat penyelamat, bukan mode default

Fallback penting, tetapi jangan dianggap gratis. Saat provider utama gagal, fallback dapat:

  • meningkatkan reliabilitas,
  • menjaga SLA endpoint penting,
  • mengurangi insiden total outage.

Namun fallback juga punya risiko:

  • biaya berbeda, bisa lebih mahal atau lebih murah tetapi kualitas turun,
  • format output berbeda, parser Anda bisa gagal,
  • latensi berbeda,
  • perilaku model berbeda pada prompt yang sama.

Karena itu, fallback sebaiknya diuji lebih dulu dalam traffic kecil, bukan baru dicoba pertama kali saat insiden. Pastikan kontrak output cukup tahan terhadap variasi respons.

Catatan: Jika aplikasi mengharapkan output terstruktur, jangan hanya mengandalkan “prompt yang rapi”. Tambahkan validasi hasil dan respons degradasi yang aman ketika fallback menghasilkan format yang tidak sesuai.

Observability: logging, tracing, dan metrik yang benar-benar membantu rollback

Logging terstruktur

Log untuk request AI harus bisa menjawab pertanyaan operasional: request mana yang gagal, ke provider/model apa, butuh berapa lama, fallback terjadi atau tidak, dan siapa yang terdampak. Simpan log dalam bentuk terstruktur, bukan string bebas.

logger.info(
  event: "ai_request_completed",
  request_id: request_id,
  user_id: user_id,
  task: task,
  provider: result.provider,
  model: result.model,
  latency_ms: result.latency_ms,
  fallback_used: result.fallback_used,
  deploy_version: ENV["APP_REVISION"]
)

Hindari menulis prompt mentah atau output sensitif ke log jika berisiko memuat data pribadi, rahasia bisnis, atau isi percakapan pengguna. Jika perlu untuk debugging, lakukan redaksi atau sampling ketat.

Tracing end-to-end

Tracing membantu saat Anda perlu membedakan apakah bottleneck ada di controller, antrean, database, atau provider AI. Minimal, buat span untuk:

  • request HTTP masuk,
  • pemanggilan service bisnis,
  • panggilan ke RubyLLM/provider AI,
  • fallback call bila terjadi,
  • penyimpanan hasil ke database atau enqueue job lanjutan.

Tambahkan atribut seperti provider, model, task, fallback_used, dan deploy_version. Saat canary menyebabkan regresi, Anda bisa membandingkan trace antara cohort lama dan baru.

Metrik minimum yang wajib ada

Untuk deployment RubyLLM, tiga metrik inti adalah latensi, error rate, dan cost. Tambahkan beberapa metrik pendukung:

  • latency p50/p95/p99 per task, provider, model, dan versi deploy,
  • error rate per provider/model/error class,
  • fallback rate,
  • timeout count,
  • retry count,
  • request volume,
  • estimated cost per task atau tenant jika datanya tersedia,
  • queue delay bila pemrosesan AI dilakukan asynchronous.

Tanpa segmentasi per provider/model/deploy version, dashboard Anda akan terlihat “normal” padahal satu canary cohort sebenarnya rusak. Label yang terlalu sedikit adalah kesalahan observability yang sering terjadi.

Alert yang berguna

Alert sebaiknya memicu tindakan, bukan sekadar notifikasi bising. Contoh kondisi yang layak dipantau:

  • error rate provider tertentu naik tajam,
  • fallback rate melewati ambang normal,
  • p95 latency task kritis naik setelah deploy,
  • biaya per menit atau per tenant menyimpang dari baseline,
  • success rate canary lebih buruk dari baseline non-canary.

Jika memungkinkan, hubungkan alert dengan runbook dan tombol rollback flag agar respons insiden lebih cepat.

Rollback cepat saat provider AI bermasalah

Prinsip rollback untuk sistem berbasis AI

Pada aplikasi biasa, rollback sering berarti kembali ke image versi sebelumnya. Pada integrasi RubyLLM, itu belum tentu cukup. Sumber masalah bisa berada di:

  • provider AI utama sedang gangguan,
  • model baru memberi output berbeda,
  • prompt baru memperpanjang latensi atau biaya,
  • parser output gagal pada provider fallback,
  • konfigurasi timeout/retry terlalu agresif atau terlalu longgar.

Karena itu, susun rollback berlapis:

  1. Rollback flag: matikan canary, ganti model, nonaktifkan provider tertentu.
  2. Rollback konfigurasi: kembalikan timeout, fallback, atau routing ke nilai stabil terakhir.
  3. Rollback aplikasi: lakukan jika problem ada di kode, bukan hanya provider/model.

Contoh urutan rollback operasional

  1. Pause peningkatan canary.
  2. Matikan route model/provider baru lewat feature flag.
  3. Paksa semua traffic ke route stabil atau non-AI degradation path.
  4. Naikkan observability sampling bila perlu untuk investigasi singkat.
  5. Jika masalah ada di kode parsing atau orchestration, rollback deployment aplikasi.
  6. Verifikasi metrik 5-15 menit berikutnya sebelum menyatakan insiden mereda.

Degradation path juga penting. Untuk fitur non-kritis, lebih baik menampilkan pesan “fitur sedang dibatasi” daripada memaksa retry berulang yang menambah biaya dan memperburuk antrean.

Kapan fallback, kapan fail fast?

Tidak semua endpoint harus fallback. Gunakan pendekatan berikut:

  • Fallback untuk fitur yang harus tetap tersedia meski kualitas sedikit turun, misalnya ringkasan internal.
  • Fail fast untuk fitur yang butuh konsistensi tinggi, format ketat, atau kontrol biaya, misalnya ekstraksi data terstruktur yang dipakai sistem downstream sensitif.

Keputusan ini sebaiknya dibuat per use case, bukan satu kebijakan global.

Checklist verifikasi sebelum dan sesudah deploy

Checklist sebelum deploy

  • Feature flag untuk provider/model baru sudah dibuat dan default-nya aman.
  • Rute utama dan fallback sudah dikonfigurasi serta pernah diuji di lingkungan non-produksi.
  • Timeout per task sudah ditetapkan, bukan mengandalkan default.
  • Retry dibatasi dan hanya aktif untuk error yang layak diulang.
  • Logging terstruktur menyertakan request_id, provider, model, task, dan deploy_version.
  • Tracing tersedia untuk jalur request AI utama.
  • Dashboard latensi, error, fallback, dan biaya siap dipantau.
  • Alert penting aktif dan tidak dalam status mute tanpa alasan.
  • Rollback plan sudah jelas: flag mana dimatikan lebih dulu, siapa PIC-nya, dan bagaimana jalur fallback/degradasinya.
  • Jika output AI diparse, validasi format dan penanganan error parser sudah diuji.

Checklist sesudah deploy

  • Aplikasi sehat pada liveness dan readiness check.
  • Canary aktif hanya untuk cohort yang direncanakan.
  • Error rate cohort canary tidak lebih buruk secara material dibanding baseline.
  • p95 latency tidak melonjak di task kritis.
  • Fallback rate masih dalam batas wajar.
  • Tidak ada lonjakan timeout atau queue delay.
  • Biaya per request atau per volume trafik tidak menunjukkan anomali.
  • Sample respons dari provider/model baru lolos pemeriksaan kualitas minimum.
  • Tim on-call mengetahui perubahan dan durasi observasi setelah deploy.

Runbook insiden singkat untuk deploy RubyLLM

Runbook tidak perlu panjang, tetapi harus cukup konkret agar on-call tidak menebak-nebak di tengah insiden.

Trigger insiden

  • Error rate AI naik di atas ambang normal.
  • p95 latency endpoint AI meningkat signifikan setelah canary/deploy.
  • Fallback rate melonjak terus-menerus.
  • Biaya meningkat tidak wajar dalam jangka pendek.

Langkah respons awal

  1. Konfirmasi apakah masalah hanya terjadi pada cohort canary, provider tertentu, atau semua traffic.
  2. Cek dashboard per provider, model, task, dan deploy_version.
  3. Periksa log terstruktur untuk error dominan: timeout, rate limit, parser error, atau exception internal.
  4. Jika hanya provider/model baru yang terdampak, matikan canary atau route baru.
  5. Jika provider utama gagal total, aktifkan fallback atau degradation path sesuai runbook.
  6. Jika fallback ikut gagal, fail fast pada fitur non-kritis untuk melindungi sistem inti.
  7. Komunikasikan status singkat ke tim terkait: dampak, mitigasi aktif, ETA pembaruan berikutnya.

Kriteria eskalasi

  • Insiden melewati durasi tertentu tanpa penurunan dampak.
  • Biaya meningkat cepat karena retry/fallback.
  • Output salah berdampak ke proses bisnis downstream.
  • Rollback flag tidak cukup dan perlu rollback aplikasi.

Template postmortem ringan

Postmortem yang berguna tidak perlu panjang, tetapi harus jujur dan bisa ditindaklanjuti. Fokus pada dampak, timeline, root cause, mitigasi, dan pencegahan.

Judul insiden:
Deploy RubyLLM - peningkatan timeout pada provider utama saat canary model baru

Tanggal/Waktu:
YYYY-MM-DD HH:MM TZ

Dampak:
- Endpoint/fitur terdampak:
- Persentase traffic terdampak:
- Gejala pengguna:
- Dampak bisnis/operasional:

Timeline:
- HH:MM deploy selesai
- HH:MM canary 5% diaktifkan
- HH:MM alert p95 latency meningkat
- HH:MM fallback rate naik
- HH:MM canary dimatikan
- HH:MM metrik kembali mendekati baseline

Root cause:
- Penyebab teknis utama:
- Faktor kontribusi:
- Mengapa tidak terdeteksi sebelum produksi:

Mitigasi saat insiden:
- Flag yang dimatikan:
- Route/fallback yang diaktifkan:
- Apakah perlu rollback aplikasi:

Tindakan pencegahan:
- Perbaikan observability:
- Perbaikan timeout/retry/fallback:
- Perubahan proses deploy/canary:
- Tes tambahan yang akan dibuat:

Pemilik aksi dan target waktu:
- Nama/Tim - Aksi - Tanggal target

Hindari postmortem yang hanya berhenti pada “provider sedang gangguan”. Itu mungkin benar, tetapi belum cukup. Cari juga mengapa blast radius bisa membesar: apakah karena timeout terlalu lama, fallback tak diuji, alert kurang jelas, atau routing tidak bisa dimatikan cepat.

Trade-off biaya, reliabilitas, dan kompleksitas operasional

Tidak ada rancangan deploy RubyLLM yang sempurna untuk semua tim. Setiap pengamanan punya biaya dan kompleksitasnya sendiri.

Canary vs kecepatan rilis

Canary menurunkan risiko, tetapi memperlambat rollout dan menambah beban monitoring. Untuk fitur AI yang memengaruhi user-facing output atau biaya besar, trade-off ini biasanya layak.

Fallback vs konsistensi output

Fallback provider meningkatkan availability, tetapi bisa menurunkan konsistensi hasil. Jika downstream Anda sensitif terhadap format atau gaya output, fallback perlu kontrak validasi yang lebih ketat.

Retry vs biaya

Retry bisa menyelamatkan request sementara, tetapi juga dapat menggandakan biaya. Pada endpoint sinkron, retry berlebih sering lebih merugikan daripada membantu.

Observability vs overhead

Logging, tracing, dan metrik yang detail membantu debugging dan rollback, tetapi menambah overhead operasional dan potensi volume data besar. Solusinya adalah memilih atribut yang benar-benar dipakai, melakukan sampling yang bijak, dan menghindari log sensitif.

Penutup

Deploy RubyLLM ke produksi yang aman bergantung pada kemampuan Anda membatasi dampak perubahan, bukan pada keyakinan bahwa provider AI akan selalu stabil. Praktik yang paling efektif adalah memisahkan deploy kode dari aktivasi perilaku AI, menjalankan canary bertahap, mengontrol provider/model lewat feature flag, memasang timeout dan fallback yang disiplin, serta menyiapkan observability yang cukup untuk rollback cepat.

Jika harus memilih prioritas, mulai dari empat hal ini: feature flag per provider/model, timeout yang ketat, dashboard latensi/error/fallback, dan runbook rollback sederhana. Setelah itu, tambahkan canary yang lebih granular dan postmortem yang konsisten agar setiap insiden benar-benar meningkatkan reliabilitas sistem Anda.