Menggunakan antarmuka LLM berbasis sesi—seperti reverse-engineered API wrapper atau akun web browser automation—memiliki tantangan stabilitas yang besar. Masalah utama meliputi burst traffic yang memicu HTTP 429 (Too Many Requests), batas konkurensi ketat per akun, serta risiko sesi di-banned atau di-invalidasi saat menerima beban paralel yang tidak wajar.

Untuk mengamankan akses ke upstream LLM tersebut, diperlukan arsitektur Session Pool LLM Gateway. Pendekatan ini memisahkan penerimaan request HTTP dari eksekusi upstream menggunakan antrean Redis, membatasi konkurensi tiap sesi dengan distributed semaphore, dan menerapkan caching serta proteksi circuit breaker.

Arsitektur Gateway dan Pola Masalah Upstream LLM

Ketika klien langsung mengakses reverse proxy tanpa kontrol antrean terpusat, lonjakan traffic akan langsung membebani upstream. Upstream endpoint sering kali memberlakukan pembatasan berupa:

  • Konkurensi per Sesi: Maksimal 1 request aktif per token/kuki sesi secara simultan. Request kedua pada detik yang sama memicu pembatalan sesi atau error.
  • Rate Limit Per Menit (RPM): Batas kuota panggilan per jendela waktu tertentu.
  • Session Invalidation: Deteksi anomali pada payload atau frekuensi yang mengakibatkan token hangus (HTTP 401/403).

Solusi untuk mengisolasi risiko ini adalah membagi gateway menjadi dua komponen: Ingress Gateway (API Producer) dan Worker Pool (Consumer) yang dihubungkan melalui Redis.

Meratakan Burst Traffic dengan Redis Queue

Ingress Gateway menerima request dari klien (misalnya format standar OpenAI API), memvalidasi payload, mengecek layer cache, dan jika cache miss, memasukkan job ke antrean Redis (menggunakan Redis List atau Redis Streams). Gateway kemudian menunggu sinyal hasil melalui pola Redis Pub/Sub atau polling key unik.

Dengan antrean ini, lonjakan 500 request per detik tidak langsung diteruskan ke upstream LLM, melainkan ditampung dalam buffer Redis. Worker memproses antrean sesuai kapasitas riil pool sesi yang tersedia, sehingga kurva lalu lintas ke upstream menjadi datar (traffic shaping).

Distributed Semaphore Berbasis Redis untuk Concurrency per Sesi

Setiap sesi upstream yang valid disimpan dalam Redis Hash atau Set. Karena satu sesi upstream hanya boleh mengeksekusi satu prompt dalam satu waktu (konkurensi = 1), worker harus mendapatkan lease (izin pinjam) sebelum mengirim payload ke upstream.

Implementasi distributed semaphore yang aman memerlukan script Lua atomik agar tidak terjadi race condition antar worker ketika memilih sesi yang sedang menganggur.

-- Lua script: acquire_session_lease.lua
-- KEYS[1]: pool:idle_sessions (Set ID sesi yang idle)
-- KEYS[2]: pool:active_leases (Hash ID sesi -> worker info)
-- ARGV[1]: worker_id
-- ARGV[2]: lease_ttl_seconds

local session_id = redis.call('SPOP', KEYS[1])
if not session_id then
    return nil -- Tidak ada sesi idle yang tersedia
end

redis.call('HSET', KEYS[2], session_id, ARGV[1])
redis.call('SET', 'lease:ttl:' .. session_id, ARGV[1], 'EX', tonumber(ARGV[2]))
return session_id

Saat request upstream selesai atau gagal, worker wajib melepaskan sesi kembali ke set pool:idle_sessions. Key TTL (lease:ttl:<session_id>) berfungsi sebagai safety net: jika worker mengalami crash di tengah eksekusi, mekanisme rekonsiliasi dapat memulihkan sesi setelah TTL habis tanpa menyebabkan deadlock permanen.

Prompt Normalization dan Respons Caching SHA-256

Upstream LLM memakan waktu puluhan detik untuk komputasi inference. Caching respons identik secara signifikan menurunkan beban pool sesi.

Sebelum job dimasukkan ke antrean, gateway melakukan normalisasi payload prompt. Input JSON distandarisasi dengan mengurutkan key, melakukan trimming whitespace pada pesan, dan mengabaikan parameter non-deterministik acak. Gateway menghitung hash SHA-256 dari canonical string tersebut.

// Pseudocode normalisasi prompt dan query cache
function getPromptHash(model, messages, temperature) {
  const normalized = JSON.stringify({
    model: model.trim().toLowerCase(),
    messages: messages.map(m => ({
      role: m.role.trim().toLowerCase(),
      content: m.content.trim()
    })),
    temperature: Number(temperature || 0).toFixed(2)
  }, Object.keys(normalized).sort());

  return crypto.createHash('sha256').update(normalized).digest('hex');
}

// Cek Redis: GET cache:llm:<hash>
// Jika ada, langsung return SSE atau JSON ke klien tanpa antrean.

Untuk request dengan nilai temperature = 0 atau respons faktual umum, cache hit menghemat kuota sesi dan memotong latensi hingga di bawah 10 milidetik.

Resiliensi Operasional: 429 Backoff, Circuit Breaker, dan Aborted SSE

Pada kondisi operasional riil, sistem harus menangani skenario kegagalan jaringan, pembatasan ketat, dan perilaku klien secara elegan.

1. Penanganan HTTP 429 Upstream via Exponential Backoff + Jitter

Ketika upstream mengembalikan status code 429, worker tidak boleh langsung me-retry pada milidetik berikutnya. Gunakan pola exponential backoff dengan full jitter sebelum mengembalikan sesi ke pool atau mencoba ulang job:

t_wait = min(max_backoff, base_backoff * (2 ^ attempt))
jitter = random_between(0, t_wait)
actual_sleep = jitter

Sesi yang terkena 429 harus diisolasi sementara ke dalam Redis Sorted Set (ZSET) dengan skor timestamp berupa now + cooldown_period, sehingga tidak dipilih oleh worker lain sampai masa blokir upstream berakhir.

2. Circuit Breaker Saat Sesi Hangus (401/403)

Jika upstream mengembalikan status 401, 403, atau pesan Session Expired, sesi tersebut telah tidak valid. Worker harus langsung mengeksekusi sirkuit proteksi:

  • Hapus ID sesi dari pool:idle_sessions dan pool:active_leases secara permanen.
  • Pindahkan ID sesi ke set pool:revoked_sessions untuk alert tim operasional.
  • Kembalikan job ke antrean utama dengan prioritas tinggi agar dieksekusi oleh sesi lain yang masih sehat, tanpa memicu error 500 ke klien.

3. Pemutusan Koneksi Streaming (SSE) Tanpa Membebani Pool Worker

Pada model streaming Server-Sent Events (SSE), klien web kerap menutup tab atau membatalkan request saat jawaban LLM baru tergenerate separuh jalan. Jika gateway tidak mendeteksi client disconnect, worker akan tetap memproses streaming hingga tuntas, menyia-nyiakan bandwidth dan menahan lease sesi secara tidak perlu.

Gunakan sinyal pembatalan koneksi (seperti AbortSignal pada Node.js atau context cancellation pada Go):

// Menangani pemutusan koneksi SSE di sisi worker
const controller = new AbortController();

clientResponse.on('close', () => {
  if (!streamFinished) {
    controller.abort(); // Batalkan HTTP stream upstream seketika
  }
});

try {
  const upstreamStream = await fetch(UPSTREAM_URL, {
    signal: controller.signal,
    // headers, body...
  });
  // Pipe upstreamStream ke clientResponse...
} finally {
  // PENTING: Selalu lepas session lease di block finally
  await releaseSessionLease(redis, sessionId, workerId);
}

Blok finally memastikan sesi dikembalikan ke pool idle terlepas dari apakah stream selesai secara normal, diputus oleh klien, atau mengalami error internal. Ini mencegah lease leakage yang dapat melumpuhkan gateway.

Rangkuman Arsitektur

Pola Session Pool LLM Gateway mentransformasikan antarmuka sesi upstream yang rapuh menjadi layanan yang resilien dan production-ready. Dengan memanfaatkan Redis sebagai antrean traffic shaping, distributed semaphore berbasis Lua untuk isolasi konkurensi, hash cache SHA-256 untuk pemotongan beban, serta penanganan cancellation SSE yang ketat, gateway dapat mempertahankan uptime tinggi tanpa memicu throttling atau pemblokiran akun.