Layanan Stateless

Alasan lapisan aplikasi shared-nothing mudah di-scale dan tempat state ditempatkan.

Daftar isi

Layanan stateless adalah layanan yang menangani setiap permintaan (request) tanpa bergantung pada data yang tersimpan di server yang melayaninya. Topik ini menjelaskan mengapa sifat itu membuat lapisan aplikasi mudah dikembangkan kapasitasnya, menunjukkan dampaknya melalui simulasi Python, dan menetapkan tempat yang tepat bagi state yang tetap harus hidup di antara permintaan.

Prinsip shared-nothing#

Pada layanan stateless, setiap permintaan membawa seluruh informasi yang dibutuhkan untuk dilayani, misalnya token otentikasi dan identitas session, sehingga server tidak perlu mengingat apa pun dari permintaan sebelumnya. Sifat ini diwujudkan dalam pola shared-nothing: setiap salinan aplikasi, yang lazim disebut instance, berdiri sendiri dengan memori dan disknya sendiri, tidak membagikan apa pun dengan salinan lain, dan salinan mana pun mampu melayani permintaan mana pun.

Keberadaan state tidak hilang pada desain ini, melainkan dipindahkan ke luar instance. Session pengguna disimpan di penyimpanan terpusat, berkas yang diunggah ditempatkan di object storage, yaitu penyimpanan berkas yang diakses melalui API, dan data tetap disimpan di basis data. Instance aplikasi tinggal menjadi lapisan pemroses yang dapat diganti kapan saja. Kebalikannya adalah layanan stateful, yang menyimpan data di dalam dirinya: session di memori lokal, unggahan di disk lokal, atau cache dalam proses yang diperlakukan sebagai sumber kebenaran.

Mengapa state mempersulit scaling#

State yang tinggal di dalam instance menimbulkan dua biaya sekaligus. Pertama, routing tidak lagi bebas: permintaan session tertentu hanya mampu dilayani oleh instance yang menyimpannya, sehingga load balancer dipaksa menempel pada server lama. Kedua, hilangnya satu instance berarti hilangnya seluruh state yang tinggal di dalamnya, dan penggunanya kehilangan session tanpa melakukan kesalahan apa pun.

Simulasi berikut memperlihatkan kedua biaya itu. Lima session dipakai bergantian, dengan session S1 paling sering diakses, dan worker, yaitu proses yang melayani permintaan pada setiap instance, dipilih secara acak untuk setiap permintaan. Skenario A menyimpan session di memori worker tanpa afinitas routing, skenario B memakai routing yang menempel pada worker pembuat session, dan skenario C menyimpan session di penyimpanan terpusat sehingga setiap worker mampu melayani apa pun.

python simulasi-sesi.py
# Simulasi penempatan session: di memori worker vs di penyimpanan terpusat.
# Lima session dipakai bergantian dan worker penerima dipilih acak.
# Seed acak ditetapkan tetap agar keluaran dapat direproduksi.

import random

random.seed(42)

WORKER = ["W1", "W2", "W3", "W4"]
POLA = ["S1", "S2", "S1", "S3", "S1", "S4", "S1", "S5", "S1", "S2"]
PERMINTAAN = POLA * 10  # 100 permintaan; S1 paling sering diakses

# Skenario A: stateful, session hanya ada di memori worker yang membuatnya,
# sedangkan load balancer membagi permintaan tanpa afinitas.
memori = {}
gagal_a = 0
for sesi in PERMINTAAN:
    worker = random.choice(WORKER)
    if sesi not in memori:
        memori[sesi] = worker  # session terbentuk di worker ini
    elif memori[sesi] != worker:
        gagal_a += 1  # session berada di memori worker lain
print("SKENARIO A: stateful, session di memori worker, routing acak")
print(f"  permintaan: {len(PERMINTAAN)}, tidak menemukan session: {gagal_a}")

# Skenario B: stateful dengan sticky session, yaitu routing yang menempel
# pada worker pembuat session.
memori_c = {}
beban_c = {w: 0 for w in WORKER}
for sesi in PERMINTAAN:
    if sesi not in memori_c:
        memori_c[sesi] = random.choice(WORKER)
    beban_c[memori_c[sesi]] += 1
print("SKENARIO B: stateful dengan sticky session (routing menempel)")
print(f"  permintaan: {len(PERMINTAAN)}, tidak menemukan session: 0")
print("  beban worker: " + ", ".join(f"{w}={beban_c[w]}" for w in WORKER))

# Skenario C: stateless, session disimpan di penyimpanan terpusat sehingga
# setiap worker mampu melayani permintaan session mana pun.
penyimpanan = set()
beban_b = {w: 0 for w in WORKER}
for sesi in PERMINTAAN:
    worker = random.choice(WORKER)
    beban_b[worker] += 1
    penyimpanan.add(sesi)  # state dibaca dari penyimpanan bersama
print("SKENARIO C: stateless, session di penyimpanan terpusat")
print(f"  permintaan: {len(PERMINTAAN)}, tidak menemukan session: 0")
print("  beban worker: " + ", ".join(f"{w}={beban_b[w]}" for w in WORKER))
text Keluaran
SKENARIO A: stateful, session di memori worker, routing acak
  permintaan: 100, tidak menemukan session: 67
SKENARIO B: stateful dengan sticky session (routing menempel)
  permintaan: 100, tidak menemukan session: 0
  beban worker: W1=20, W2=0, W3=10, W4=70
SKENARIO C: stateless, session di penyimpanan terpusat
  permintaan: 100, tidak menemukan session: 0
  beban worker: W1=30, W2=30, W3=18, W4=22

Keluarannya menegaskan ketiga pola. Pada skenario A, 67 dari 100 permintaan gagal menemukan session-nya karena session berada di memori worker lain, sebuah angka kegagalan yang tidak dapat diterima. Skenario B menyelesaikan kegagalan itu melalui routing yang menempel, tetapi beban menjadi timpang: W4 menangani 70 permintaan karena session sibuk S1 menetap di sana, sedangkan W2 tidak pernah menerima satu pun. Skenario C memperoleh keduanya, yaitu nol kegagalan dan beban yang merata, karena setiap worker mampu melayani session mana pun.

Tempat menempatkan state#

Kaidah penempatan state dapat diringkas menjadi dua pertanyaan: apakah state harus hidup lebih lama daripada proses yang membuatnya, dan apakah state harus dapat dijangkau dari setiap instance. Session yang harus selamat dari pergantian server memenuhi keduanya, sehingga tempatnya di penyimpanan terpusat. Sebaliknya, data sementara seperti berkas antara perhitungan boleh tinggal lokal karena hilangnya tidak dirasakan pengguna.

Jenis stateTempat yang tepat
Data sessionpenyimpanan key-value terpusat yang dapat dibaca setiap instance
Berkas yang diunggah penggunaobject storage yang diakses melalui API
Cache hasil komputasicache terpusat di luar instance aplikasi
Data tetapbasis data
State kecil per klien yang tidak sensitifcookie yang ditandatangani server

Cookie menempati posisi khusus pada daftar tersebut. Apabila isinya kecil dan tidak sensitif, misalnya preferensi tampilan, cookie menyimpan state di sisi klien sehingga server tidak perlu menyimpan apa pun. Apabila session menyimpan data yang lebih kaya, cookie cukup membawa penanda session yang ditandatangani, sedangkan isinya tetap di penyimpanan terpusat.

Karakteristik operasional layanan stateless#

Sifat shared-nothing menyederhanakan pekerjaan operasional dalam empat keadaan. Penambahan kapasitas dilakukan dengan menjalankan instance baru di balik load balancer, tanpa perlu memindahkan data. Ketika terjadi kegagalan, instance yang bermasalah dapat dikeluarkan dari layanan karena permintaannya dapat langsung ditangani oleh instance lain. Pembaruan (deploy) dilakukan dengan mengganti instance satu per satu tanpa memutus sesi pengguna. Penyusutan kapasitas pun dapat dilakukan dengan aman karena instance bisa dimatikan tanpa meninggalkan state yang tidak lagi dikelola.

Batasan konsep ini perlu dinyatakan dengan jelas: stateless merupakan sifat lapisan aplikasi, bukan sifat seluruh sistem. Basis data, penyimpanan sesi, dan object storage tetap bersifat stateful karena memang dirancang untuk menyimpan dan mengelola state secara persisten. Keuntungan desain ini adalah pemisahan tanggung jawab: lapisan aplikasi yang perlu sering dikembangkan dibuat sesederhana mungkin sebagai pemroses permintaan, sedangkan tanggung jawab untuk menyimpan dan menjaga data diserahkan kepada lapisan penyimpanan.

Kesalahan umum#

  1. Menyimpan session di memori dengan niat sementara. Penyebabnya, pilihan itu terasa murah pada awal proyek, lalu menjadi pekerjaan refactor besar ketika kumpulan server perlu diperbesar. Perbaikannya, tempatkan session di penyimpanan terpusat sejak permintaan session pertama ditulis.
  2. Menganggap stateless berarti tanpa basis data. Penyebabnya, pembacaan yang harfiah atas istilah tersebut, padahal yang stateless adalah lapisan aplikasi, sedangkan state tetap ada pada layanan penyimpanan. Perbaikannya, nyatakan pemisahan tanggung jawabnya secara eksplisit dalam dokumen desain.
  3. Memperlakukan cache dalam proses sebagai sumber kebenaran. Penyebabnya, data yang dihitung sekali disimpan di memori instance sehingga salinan antarinstance berbeda dan hilang saat instance mati. Perbaikannya, gunakan cache terpusat untuk data yang dipakai bersama dan perlakukan isinya sebagai salinan yang boleh dibangun ulang.

Ringkasan#

  • Layanan stateless menangani permintaan tanpa mengandalkan data lokal instance, dan pola shared-nothing membuat setiap instance identik serta saling menggantikan.
  • Simulasi menunjukkan session di memori lokal gagal 67 dari 100 permintaan saat routing acak, sticky session membebani satu worker 70 dari 100 permintaan, dan penyimpanan terpusat memberikan nol kegagalan dengan beban merata.
  • State ditempatkan menurut dua kaidah: harus hidup lebih lama dari prosesnya dan harus terjangkau dari setiap instance, sehingga session di penyimpanan terpusat, unggahan di object storage, dan data tetap di basis data.

Referensi#