Penyimpanan session

Session web di Redis: key per sesi, waktu kedaluwarsa selaras, dan pembacaan di middleware.

Daftar isi

Session menyimpan keadaan per pengguna di sisi server di antara permintaan-permintaan HTTP yang pada dasarnya tidak mengenal keadaan (stateless). Setelah berhasil masuk, pengguna membawa token session pada setiap permintaan berikutnya, sedangkan isinya, misalnya nama dan peran, disimpan di server dan dibaca kembali pada setiap permintaan. Redis cocok untuk peran ini karena data sesi dibaca pada setiap permintaan sehingga harus cepat, dan masa hidupnya berbatas sehingga selaras dengan TTL bawaan Redis. Topik ini membangun simulasi middleware session dengan redis-py, mencakup pembuatan key per sesi, pembacaan oleh middleware pada setiap permintaan, pembaruan masa hidup ketika ada aktivitas, serta berakhirnya sesi ketika pengguna keluar atau diam terlalu lama. Seluruh contoh menggunakan awalan key sesi-web: pada server Redis 8.0.2 di port 6379.

Konsep session web di Redis#

Server HTTP tidak menyimpan keadaan apa pun di antara dua permintaan, sehingga identitas pengguna perlu disematkan pada setiap permintaan. Caranya yang lazim adalah cookie yang hanya berisi token acak, sedangkan seluruh isi sesi disimpan di server pada key yang dibangun dari token itu. Dengan pembagian ini, cookie tidak membawa data yang sensitif, dan pencabutan sesi cukup dilakukan dari sisi server tanpa menunggu cookie hilang. Key sesi mengikuti pola sesi-web:<token>, misalnya sesi-web:b73f21c4, sehingga setiap sesi milik tepat satu pengguna dan mudah dihapus satu per satu.

Isi sesi disimpan sebagai hash, yaitu struktur data berisi pasangan field dan nilai, sehingga nama pengguna dan perannya dibaca per field tanpa menulis ulang seluruh isinya. Pembacaan berlangsung di memori dan hanya memerlukan satu perintah HGETALL, biaya yang murah untuk pekerjaan yang diulang pada setiap permintaan. Masa hidup key menjaga sesi terhapus dengan sendirinya ketika pengguna berhenti beraktivitas, perilaku yang lazim disebut idle timeout. Diagram berikut merangkum siklus hidup sesi dari login sampai sesi berakhir.

LoginAli berhasil masukKey sesi-web:tokenhash + masa hidupSesi berakhirkey terhapusHSET + EXPIREsetiap permintaan: EXPIRE ulanglogout: DELapabila masa hidup habis tanpa aktivitas, key sesi juga terhapus dengan sendirinya
Siklus hidup sesi web: login membuat key sesi dengan masa hidup, setiap permintaan memperbarui masa hidup, dan logout menghapus key

Membuat dan membaca sesi di redis-cli#

Perintah inti pola ini dapat diperiksa langsung di redis-cli sebelum disatukan menjadi kode. Login menulis hash sesi dengan HSET, lalu memasang masa hidupnya dengan EXPIRE pada langkah kedua. Middleware kemudian membaca seluruh isi sesi dengan HGETALL dan memeriksa sisa masa hidupnya dengan TTL, sedangkan logout menghapus key dengan DEL. Urutan berikut memperlihatkan siklus lengkap pada sesi milik Citra, dengan masa hidup 30 menit dan key demo yang dibersihkan di awal agar urutannya selalu sama.

bash
# menghapus sisa key demo dari percobaan sebelumnya
redis-cli DEL sesi-web:demo:citra > /dev/null
# login: hash sesi ditulis untuk pengguna Citra
redis-cli HSET sesi-web:demo:citra pengguna Citra peran siswa
# masa hidup sesi dipasang 30 menit
redis-cli EXPIRE sesi-web:demo:citra 1800
# middleware membaca seluruh isi sesi pada setiap permintaan
redis-cli HGETALL sesi-web:demo:citra
# sisa masa hidup sesi yang baru dibuat
redis-cli TTL sesi-web:demo:citra
# logout: key sesi dicabut dari sisi server
redis-cli DEL sesi-web:demo:citra
text Keluaran
2
1
pengguna
Citra
peran
siswa
1800
1

HSET menjawab 2 karena dua field baru ditulis, dan EXPIRE menjawab 1 karena masa hidup berhasil dipasang. HGETALL mengembalikan pasangan field dan nilai dalam urutan penulisannya, bentuk yang dibaca middleware untuk memutuskan apakah permintaan membawa sesi yang sah. TTL melaporkan 1800 karena pemeriksaan dilakukan seketika setelah pemasangan, sedangkan DEL terakhir menjawab 1 karena logout menghapus key sebelum masa hidupnya habis. Pada aplikasi yang sesungguhnya, token dihasilkan acak dengan panjang yang cukup, misalnya melalui uuid4(), supaya token sesi tidak dapat ditebak.

Simulasi middleware session#

Skrip berikut menyatukan perintah-perintah tersebut menjadi satu siklus yang menyerupai middleware session, yaitu lapisan yang membaca sesi sebelum permintaan mencapai penangan utamanya. Masa hidup dipendekkan menjadi 3 detik supaya kedaluwarsanya dapat ditunggu di dalam demo, sedangkan aplikasi nyata lazimnya menggunakan 15 sampai 60 menit untuk batas tidak aktif. Sesi dibuat saat login, dibaca pada dua permintaan berikutnya, dan masa hidupnya diperbarui pada permintaan kedua sebelum sesi dibiarkan tanpa aktivitas sampai kedaluwarsa. Seluruh angka masa hidup yang dicetak diambil dari balasan perintah TTL, bukan dari penghitungan jam di sisi klien, sehingga keluarannya dapat direproduksi.

python sesi_web.py
import time
import redis

r = redis.Redis(host="127.0.0.1", port=6379, decode_responses=True)
TTL = 3  # masa hidup dipendekkan agar demo cepat, aplikasi nyata menggunakan menit

# token sesi; pada aplikasi nyata nilainya dihasilkan acak, misalnya uuid4()
token = "b73f21c4"
key = f"sesi-web:{token}"

# bersihkan sisa sesi dengan token sama dari percobaan sebelumnya
r.delete(key)

# login: hash sesi ditulis, lalu masa hidup dipasang
r.hset(key, mapping={"pengguna": "Ali", "peran": "siswa"})
r.expire(key, TTL)
print("login: sesi Ali dibuat dengan masa hidup", r.ttl(key), "detik")

# permintaan pertama: middleware membaca sesi dari Redis
data = r.hgetall(key)
print(f"permintaan 1: sesi milik {data['pengguna']} ditemukan, peran {data['peran']}")

# jeda singkat meniru jarak waktu antar permintaan
time.sleep(1)

# permintaan kedua: sesi dibaca, lalu masa hidupnya diperbarui
sisa = r.ttl(key)
data = r.hgetall(key)
print(f"permintaan 2: sesi ditemukan, sisa masa hidup {sisa} detik sebelum diperbarui")
r.expire(key, TTL)
print(f"permintaan 2: masa hidup kembali {r.ttl(key)} detik")

# tanpa aktivitas sampai masa hidup habis, sesi menghilang dengan sendirinya
time.sleep(TTL + 0.5)
ada = bool(r.exists(key))
print(f"setelah masa hidup habis tanpa aktivitas, key sesi ada: {ada}")
print(f"TTL atas key yang sudah tidak ada: {r.ttl(key)}")
text Keluaran
login: sesi Ali dibuat dengan masa hidup 3 detik
permintaan 1: sesi milik Ali ditemukan, peran siswa
permintaan 2: sesi ditemukan, sisa masa hidup 2 detik sebelum diperbarui
permintaan 2: masa hidup kembali 3 detik
setelah masa hidup habis tanpa aktivitas, key sesi ada: False
TTL atas key yang sudah tidak ada: -2

Keluaran memperlihatkan angka yang seluruhnya bersumber dari server. Sesi baru dilaporkan berumur penuh 3 detik, turun menjadi 2 detik setelah satu detik tanpa aktivitas, lalu kembali 3 detik tepat setelah EXPIRE dipanggil ulang. Pembaruan inilah yang membuat sesi tetap hidup selama pengguna aktif, perilaku yang lazim disebut sliding expiration karena masa hidup bergeser mengikuti aktivitas terakhir dan bukan dihitung sejak login. Setelah masa hidup habis tanpa satu pun permintaan, key menghilang dengan sendirinya, dan TTL melaporkan -2 sebagai tanda key sudah tidak ada.

Logout dan ketahanan sesi#

Logout tidak sama dengan menghapus cookie. Cookie hanya membawa token, sedangkan sesi yang sah tetap hidup di Redis sampai masa hidupnya berakhir. Apabila logout hanya menghapus cookie di sisi klien, token yang sempat terekam oleh pihak lain masih diterima server sebagai sesi yang sah sampai TTL-nya habis. Logout yang benar menghapus key sesi di Redis, sehingga permintaan apa pun yang membawa token sama langsung ditolak.

Menyimpan sesi di memori juga berarti menyetujuinya hilang bersama memori. Tanpa persistensi, seluruh sesi lenyap ketika server Redis dimulai ulang, dan kebijakan eviksi dapat membuang key sesi ketika memori penuh, dua kejadian yang akibatnya hanya berupa pengguna diminta masuk kembali. Untuk sebagian besar aplikasi, konsekuensi ini dapat diterima karena datanya sementara oleh desain, tetapi isinya perlu dijaga ringkas. Data yang harus bertahan melintasi kedaluwarsa dan mulai ulang, misalnya profil atau riwayat, bukan tempatnya di session, melainkan di penyimpanan yang permanen.

Ringkasan#

  • Sesi web disimpan sebagai hash pada key sesi-web:<token>, sedangkan cookie hanya membawa token acaknya.
  • Login menulis hash lalu memasang masa hidup, middleware membacanya dengan HGETALL pada setiap permintaan, dan aktivitas memperbarui masa hidup dengan EXPIRE.
  • Masa hidup bergeser mengikuti aktivitas terakhir, sehingga key sesi terhapus sendiri hanya ketika pengguna berhenti beraktivitas.
  • Logout wajib menghapus key dari sisi server, dan isi session sengaja dibuat sementara sehingga hilangnya akibat mulai ulang atau eviksi dapat diterima.

Referensi#

  • EXPIRE — Redis — Pemasangan dan pembaruan masa hidup key yang menjadi dasar kedaluwarsa sesi.
  • TTL — Redis — Pembacaan sisa masa hidup key beserta arti nilai -1 dan -2.
  • HSET — Redis — Penulisan field hash yang menjadi isi sesi per pengguna.
  • HGETALL — Redis — Pembacaan seluruh field dan nilai hash yang dilakukan middleware pada setiap permintaan.
  • Hashes — Redis — Dokumentasi tipe data hash beserta operasi field yang tersedia.