Redis

Redis #

Redis adalah in-memory data structure store yang berfungsi sebagai database, cache, message broker, dan queue secara bersamaan. Kecepatannya yang luar biasa — ratusan ribu operasi per detik dengan latensi sub-milidetik — membuatnya menjadi komponen yang hampir selalu ada di aplikasi Ruby on Rails modern: session store, cache halaman, rate limiting, Sidekiq job queue, ActionCable pub/sub, distributed lock, leaderboard, dan banyak lagi. Gem redis adalah client resmi yang paling matang di ekosistem Ruby, sementara connection_pool menangani thread-safety untuk aplikasi multi-threaded. Artikel ini membahas semua struktur data Redis, pola caching yang idiomatik, dan integrasi mendalam dengan Rails.

Instalasi dan Koneksi #

gem install redis
gem install connection_pool   # untuk multi-thread
# Gemfile
gem 'redis',           '~> 5.0'
gem 'connection_pool', '~> 2.4'
require 'redis'

# Koneksi dasar
redis = Redis.new(
  host:     "localhost",
  port:     6379,
  db:       0,             # database 0-15
  password: ENV["REDIS_PASSWORD"],
  timeout:  1,             # connection timeout
  read_timeout:  1,        # read timeout
  write_timeout: 1         # write timeout
)

# Via URL (lebih ringkas)
redis = Redis.new(url: ENV.fetch("REDIS_URL", "redis://localhost:6379/0"))

# Dengan TLS (Redis Cloud, production)
redis = Redis.new(
  url: ENV["REDIS_TLS_URL"],
  ssl_params: { verify_mode: OpenSSL::SSL::VERIFY_PEER }
)

# Test koneksi
puts redis.ping   # => "PONG"
puts redis.info["redis_version"]

# Selalu tutup koneksi
redis.close

Connection Pool — Wajib untuk Multi-Thread #

require 'redis'
require 'connection_pool'

# Buat pool — satu koneksi per thread
REDIS = ConnectionPool.new(size: 10, timeout: 5) do
  Redis.new(url: ENV.fetch("REDIS_URL", "redis://localhost:6379/0"))
end

# Gunakan dengan blok
REDIS.with do |redis|
  redis.set("kunci", "nilai")
  redis.get("kunci")
end

# Atau dengan Redis::Pool (Redis gem 5+)
redis = Redis.new(
  url:          ENV["REDIS_URL"],
  timeout:      1,
  pool_size:    10,    # pool terintegrasi di Redis gem 5+
  pool_timeout: 5
)

String — Tipe Data Dasar #

String adalah tipe paling fundamental di Redis — bisa menyimpan teks, angka, JSON, atau data biner:

redis = Redis.new

# SET dan GET
redis.set("nama", "Rina")
puts redis.get("nama")    # => "Rina"

# SET dengan TTL (Time To Live) dalam detik
redis.set("token_sesi", SecureRandom.hex(32), ex: 3600)   # expire 1 jam
redis.set("otp",        "123456",              ex: 300)    # expire 5 menit

# SET dengan TTL dalam milidetik
redis.set("lock", "1", px: 5000)   # expire 5000ms = 5 detik

# SETNX — set hanya jika key belum ada (return true/false)
berhasil = redis.setnx("kunci_unik", "nilai")
puts berhasil   # => true jika berhasil, false jika sudah ada

# SET dengan NX dan EX sekaligus (atomic)
redis.set("distributed_lock", "process_id", nx: true, ex: 10)

# GET dan SET sekaligus (GETSET)
nilai_lama = redis.getset("counter", "0")

# MSET dan MGET — bulk operations
redis.mset("a", 1, "b", 2, "c", 3)
puts redis.mget("a", "b", "c").inspect   # => ["1", "2", "3"]

# Increment dan Decrement (atomic!)
redis.set("view_count", 0)
redis.incr("view_count")         # => 1
redis.incrby("view_count", 10)   # => 11
redis.decr("view_count")         # => 10
redis.incrbyfloat("rating", 0.5) # => 0.5

# String operations
redis.append("log", "baris pertama\n")
redis.append("log", "baris kedua\n")
redis.strlen("log")   # panjang string

# TTL — sisa waktu hidup key
puts redis.ttl("token_sesi")    # => sisa detik, -1 jika tidak punya TTL, -2 jika tidak ada
puts redis.pttl("lock")         # dalam milidetik

# Perpanjang atau hapus TTL
redis.expire("token_sesi", 7200)   # perpanjang ke 2 jam
redis.persist("token_sesi")        # hapus TTL (jadi permanen)
redis.expireat("event", Time.now.to_i + 86400)  # expire pada timestamp tertentu

Hash — Objek Terstruktur #

Redis Hash adalah map field-value dalam satu key — ideal untuk menyimpan objek:

# HSET — set satu atau banyak field
redis.hset("pengguna:1", "nama", "Rina", "email", "[email protected]", "umur", 28)

# HGET dan HMGET
puts redis.hget("pengguna:1", "nama")                        # => "Rina"
puts redis.hmget("pengguna:1", "nama", "email").inspect      # => ["Rina", "[email protected]"]

# HGETALL — semua field sebagai Hash Ruby
profil = redis.hgetall("pengguna:1")
puts profil.inspect   # => {"nama"=>"Rina", "email"=>"[email protected]", "umur"=>"28"}

# HSETNX — set field hanya jika belum ada
redis.hsetnx("pengguna:1", "created_at", Time.now.iso8601)

# HINCRBY — increment field numerik (atomic)
redis.hincrby("pengguna:1", "login_count", 1)

# HDEL — hapus field
redis.hdel("pengguna:1", "field_tidak_perlu")

# HEXISTS, HKEYS, HVALS, HLEN
puts redis.hexists("pengguna:1", "nama")   # => true
puts redis.hkeys("pengguna:1").inspect     # => ["nama", "email", "umur", ...]
puts redis.hlen("pengguna:1")              # => jumlah field

# Pola umum: simpan model sebagai Redis Hash
class UserCache
  KEY_PREFIX = "user:"

  def self.simpan(user)
    key = "#{KEY_PREFIX}#{user.id}"
    Redis.new.hset(key,
      "id",         user.id,
      "nama",       user.nama,
      "email",      user.email,
      "role",       user.role,
      "cached_at",  Time.now.iso8601
    )
    Redis.new.expire(key, 3600)   # TTL 1 jam
  end

  def self.ambil(id)
    data = Redis.new.hgetall("#{KEY_PREFIX}#{id}")
    return nil if data.empty?
    data.transform_keys(&:to_sym)
  end

  def self.hapus(id)
    Redis.new.del("#{KEY_PREFIX}#{id}")
  end
end

List — Queue dan Stack #

Redis List adalah linked list yang mendukung push/pop dari kedua ujung — ideal untuk queue, stack, dan riwayat:

# LPUSH / RPUSH — tambah di kiri / kanan
redis.rpush("antrian_email", "[email protected]")
redis.rpush("antrian_email", "[email protected]", "[email protected]")
redis.lpush("log_aktivitas", "login:user1")   # tambah di awal (terbaru di kiri)

# LRANGE — ambil range elemen
puts redis.lrange("antrian_email", 0, -1).inspect   # semua elemen
puts redis.lrange("log_aktivitas", 0, 9).inspect    # 10 terbaru

# LPOP / RPOP — ambil dan hapus dari kiri / kanan
email = redis.lpop("antrian_email")    # ambil dari depan (FIFO)
redis.rpop("antrian_email")            # ambil dari belakang (LIFO)

# BLPOP — blocking pop (tunggu sampai ada elemen)
# Sangat berguna untuk consumer queue
hasil = redis.blpop("antrian_email", timeout: 30)
# => ["antrian_email", "[email protected]"] atau nil jika timeout

# LLEN — panjang list
puts redis.llen("antrian_email")

# LINSERT — sisipkan sebelum/setelah elemen tertentu
redis.linsert("list", :before, "elemen_target", "elemen_baru")

# Batasi panjang list (hanya simpan N elemen terbaru)
redis.lpush("aktivitas_user:1", "event baru")
redis.ltrim("aktivitas_user:1", 0, 99)   # pertahankan hanya 100 terbaru

# LPOS — cari posisi elemen (Redis 6.0.6+)
redis.lpos("list", "nilai_dicari")

Set — Koleksi Unik #

Redis Set adalah koleksi elemen unik yang tidak berurutan — sangat efisien untuk membership check:

# SADD — tambah anggota
redis.sadd("tag:artikel:1", "ruby", "pemrograman", "tips")
redis.sadd("tag:artikel:2", "ruby", "rails", "web")

# SMEMBERS — semua anggota
puts redis.smembers("tag:artikel:1").inspect   # => Set {"ruby", "pemrograman", "tips"}

# SISMEMBER — cek keanggotaan (O(1))
puts redis.sismember("tag:artikel:1", "ruby")   # => true
puts redis.sismember("tag:artikel:1", "java")   # => false

# SCARD — jumlah anggota
puts redis.scard("tag:artikel:1")   # => 3

# Operasi himpunan
# SUNION — gabungan
puts redis.sunion("tag:artikel:1", "tag:artikel:2").inspect
# => {"ruby", "pemrograman", "tips", "rails", "web"}

# SINTER — irisan
puts redis.sinter("tag:artikel:1", "tag:artikel:2").inspect
# => {"ruby"}

# SDIFF — selisih
puts redis.sdiff("tag:artikel:1", "tag:artikel:2").inspect
# => {"pemrograman", "tips"}

# SREM — hapus anggota
redis.srem("tag:artikel:1", "tips")

# SPOP — ambil dan hapus anggota acak (berguna untuk random sampling)
redis.spop("hadiah_pool", 3)   # ambil 3 pemenang acak

# Pola: tracking online users
redis.sadd("online_users", user_id)
redis.expire("online_users", 300)   # reset setiap 5 menit

# Pola: unique visitor tracking per hari
hari_ini = Date.today.strftime("%Y%m%d")
redis.sadd("visitor:#{hari_ini}", ip_address)
redis.expire("visitor:#{hari_ini}", 86400)
puts redis.scard("visitor:#{hari_ini}")   # jumlah unique visitor hari ini

Sorted Set — Leaderboard dan Ranking #

Sorted Set adalah Set dengan score numerik — sangat efisien untuk leaderboard, rate limiting, dan delayed queue:

# ZADD — tambah anggota dengan score
redis.zadd("leaderboard", 1500, "rina")
redis.zadd("leaderboard", 2300, "budi")
redis.zadd("leaderboard", 1800, "citra")
redis.zadd("leaderboard", 2300, "deni")   # score sama dengan budi

# ZRANGE — ambil berdasarkan rank (ascending)
puts redis.zrange("leaderboard", 0, -1, with_scores: true).inspect
# => [["rina", 1500.0], ["citra", 1800.0], ["budi", 2300.0], ["deni", 2300.0]]

# ZREVRANGE — descending (skor tertinggi dulu)
top3 = redis.zrevrange("leaderboard", 0, 2, with_scores: true)
top3.each_with_index do |(nama, skor), i|
  puts "##{i+1}: #{nama}#{skor.to_i} poin"
end

# ZSCORE — skor anggota tertentu
puts redis.zscore("leaderboard", "rina")   # => 1500.0

# ZRANK / ZREVRANK — posisi ranking
puts redis.zrevrank("leaderboard", "budi")   # => 0 (rank pertama dari atas)

# ZINCRBY — tambah skor (atomic)
redis.zincrby("leaderboard", 200, "rina")   # rina sekarang 1700

# ZRANGEBYSCORE — ambil anggota dalam range skor
redis.zrangebyscore("leaderboard", 1500, 2000)

# Rate Limiting dengan Sorted Set
def rate_limit_ok?(user_id, max_requests: 100, window: 60)
  kunci   = "rate:#{user_id}"
  sekarang = Time.now.to_f
  batas   = sekarang - window

  redis.multi do |r|
    r.zadd(kunci, sekarang, sekarang.to_s)   # tambah timestamp request ini
    r.zremrangebyscore(kunci, "-inf", batas)  # hapus yang sudah kadaluarsa
    r.zcard(kunci)                            # hitung request dalam window
    r.expire(kunci, window)
  end.last <= max_requests
end

# Delayed Queue — eksekusi tugas di waktu tertentu
def jadwalkan(tugas, waktu_eksekusi)
  redis.zadd("delayed_queue", waktu_eksekusi.to_f, tugas.to_json)
end

def ambil_tugas_jatuh_tempo
  sekarang = Time.now.to_f
  redis.zrangebyscore("delayed_queue", "-inf", sekarang, limit: [0, 10]).tap do |tugas|
    tugas.each { |t| redis.zrem("delayed_queue", t) }
  end
end

Pipelining dan Transaksi #

Pipelining mengirim banyak perintah sekaligus tanpa menunggu respons — sangat meningkatkan throughput:

# Tanpa pipelining: N round-trips ke Redis
100.times { |i| redis.set("key:#{i}", i) }   # lambat

# Dengan pipelining: 1 round-trip untuk semua perintah
redis.pipelined do |pipe|
  100.times { |i| pipe.set("key:#{i}", i) }
end

# Atau kumpulkan respons
hasil = redis.pipelined do |pipe|
  pipe.get("a")
  pipe.get("b")
  pipe.incr("counter")
end
puts hasil.inspect   # => ["nilai_a", "nilai_b", 1]

# MULTI/EXEC — transaksi atomic
# Semua perintah dalam blok dieksekusi atomik
redis.multi do |r|
  r.set("saldo:1", 500_000)
  r.set("saldo:2", 1_500_000)
  r.incr("total_transaksi")
end

# WATCH + MULTI/EXEC — optimistic locking
loop do
  redis.watch("saldo:1") do
    saldo = redis.get("saldo:1").to_i

    break if saldo < 100_000   # tidak cukup saldo

    # Jika saldo berubah antara WATCH dan EXEC → multi mengembalikan nil (gagal)
    hasil = redis.multi do |r|
      r.set("saldo:1", saldo - 100_000)
      r.incrby("saldo:2", 100_000)
    end

    break if hasil   # sukses
    # Jika nil (konflik) → coba ulang loop
  end
end

Distributed Lock #

Lock terdistribusi memastikan hanya satu proses yang menjalankan operasi kritis di sistem terdistribusi:

# Implementasi distributed lock dengan SETNX + EXPIRE (atomic di Redis 2.6.12+)
class DistributedLock
  def initialize(redis, nama, ttl: 10)
    @redis  = redis
    @kunci  = "lock:#{nama}"
    @nilai  = "#{Process.pid}-#{Thread.current.object_id}-#{SecureRandom.hex(8)}"
    @ttl    = ttl
  end

  def acquire
    @redis.set(@kunci, @nilai, nx: true, ex: @ttl)
  end

  def release
    # Hanya hapus jika nilai cocok (kita yang memasang lock)
    # Lua script untuk atomic check-and-delete
    script = <<~LUA
      if redis.call("get", KEYS[1]) == ARGV[1] then
        return redis.call("del", KEYS[1])
      else
        return 0
      end
    LUA
    @redis.eval(script, keys: [@kunci], argv: [@nilai])
  end

  def with_lock
    raise "Gagal acquire lock: #{@kunci}" unless acquire
    begin
      yield
    ensure
      release
    end
  end
end

# Penggunaan
lock = DistributedLock.new(redis, "proses_pembayaran:#{pesanan_id}", ttl: 30)
lock.with_lock do
  # Hanya satu proses yang bisa masuk sini per pesanan
  proses_pembayaran(pesanan_id)
end

# Dengan gem redlock (implementasi Redlock algorithm yang lebih robust)
# gem install redlock
require 'redlock'
redlock = Redlock::Client.new(["redis://localhost:6379"])
redlock.lock("resource_kritis", 10_000) do |locked|
  if locked
    # proses eksklusif
  else
    puts "Tidak bisa acquire lock"
  end
end

Pub/Sub #

Redis Pub/Sub memungkinkan messaging real-time antar proses:

# PUBLISHER — kirim pesan ke channel
publisher = Redis.new
publisher.publish("notifikasi:user:99", JSON.generate({
  tipe: "pesan_baru",
  dari: "Budi",
  isi:  "Halo!"
}))

# SUBSCRIBER — dengarkan channel di thread terpisah
Thread.new do
  subscriber = Redis.new
  subscriber.subscribe("notifikasi:user:99") do |on|
    on.message do |channel, pesan|
      data = JSON.parse(pesan)
      puts "#{channel}: #{data['tipe']} dari #{data['dari']}"
      # Kirim ke WebSocket, notifikasi push, dll
    end

    on.subscribe do |channel, jumlah_subscription|
      puts "Subscribe ke #{channel} (total: #{jumlah_subscription})"
    end
  end
end

# PSUBSCRIBE — subscribe dengan pattern wildcard
Thread.new do
  subscriber = Redis.new
  subscriber.psubscribe("notifikasi:*") do |on|
    on.pmessage do |pattern, channel, pesan|
      puts "Pattern #{pattern} matched #{channel}: #{pesan}"
    end
  end
end

sleep 1
publisher.publish("notifikasi:user:99", "pesan baru")
publisher.publish("notifikasi:user:100", "pesan untuk user lain")

Caching Pattern di Rails #

# config/initializers/redis.rb
REDIS_CACHE = Redis.new(url: ENV.fetch("REDIS_URL", "redis://localhost:6379/1"))

# Pola fetch — ambil dari cache, jika tidak ada hitung dan simpan
def ambil_dengan_cache(kunci, ttl: 3600)
  cached = REDIS_CACHE.get(kunci)
  return JSON.parse(cached, symbolize_names: true) if cached

  hasil = yield   # hitung nilai

  REDIS_CACHE.setex(kunci, ttl, JSON.generate(hasil))
  hasil
end

# Penggunaan
statistik = ambil_dengan_cache("stats:penjualan:hari_ini", ttl: 300) do
  Pesanan.hari_ini.group(:status).count
end

# Cache dengan invalidasi
def simpan_produk(produk)
  produk.save!
  REDIS_CACHE.del("produk:#{produk.id}")      # invalidasi cache produk ini
  REDIS_CACHE.del("produk:list:halaman:1")    # invalidasi halaman pertama
end

# Rails.cache dengan Redis backend
# config/environments/production.rb
config.cache_store = :redis_cache_store, {
  url:             ENV["REDIS_URL"],
  expires_in:      1.hour,
  namespace:       "cache:#{Rails.env}",
  pool_size:       10,
  pool_timeout:    5,
  error_handler: ->(method:, returning:, exception:) {
    Rails.logger.error "Redis cache error: #{exception.message}"
  }
}

# Gunakan Rails.cache
Rails.cache.fetch("produk:#{id}", expires_in: 1.hour) do
  Produk.find(id)
end

Rails.cache.write("setting:maintenance", false, expires_in: 5.minutes)
Rails.cache.read("setting:maintenance")
Rails.cache.delete("setting:maintenance")
Rails.cache.delete_matched("produk:*")  # hapus semua cache produk

Lua Scripting — Operasi Atomic Kompleks #

Lua script dieksekusi secara atomic di Redis — tidak ada race condition:

# Script: tambah ke sorted set dan batasi jumlah anggota
tambah_dan_trim_script = <<~LUA
  local key = KEYS[1]
  local score = ARGV[1]
  local member = ARGV[2]
  local max_size = tonumber(ARGV[3])

  redis.call("zadd", key, score, member)
  local size = redis.call("zcard", key)
  if size > max_size then
    redis.call("zremrangebyrank", key, 0, size - max_size - 1)
  end
  return redis.call("zcard", key)
LUA

# Jalankan script
redis.eval(
  tambah_dan_trim_script,
  keys:  ["top_skor"],
  argv:  [1500, "rina", 100]   # maks 100 anggota
)

# Cache script dengan SHA untuk efisiensi (EVALSHA)
sha = redis.script(:load, tambah_dan_trim_script)
redis.evalsha(sha, keys: ["top_skor"], argv: [1600, "budi", 100])

Sidekiq — Background Job dengan Redis #

Sidekiq menggunakan Redis sebagai backend untuk job queue:

# Gemfile
# gem 'sidekiq', '~> 7.2'

# config/initializers/sidekiq.rb
Sidekiq.configure_server do |config|
  config.redis = {
    url:      ENV.fetch("REDIS_URL", "redis://localhost:6379/0"),
    pool_size: ENV.fetch("SIDEKIQ_CONCURRENCY", 10).to_i + 5
  }
end

Sidekiq.configure_client do |config|
  config.redis = { url: ENV.fetch("REDIS_URL", "redis://localhost:6379/0") }
end

# app/workers/kirim_email_worker.rb
class KirimEmailWorker
  include Sidekiq::Worker

  sidekiq_options(
    queue:   :email,
    retry:   5,         # retry maksimal 5x
    backtrace: true
  )

  def perform(user_id, template, data = {})
    user = User.find(user_id)
    NotifikasiMailer.send(template, user, data).deliver_now
  end
end

# Enqueue job
KirimEmailWorker.perform_async(user.id, :selamat_datang)
KirimEmailWorker.perform_in(1.hour, user.id, :pengingat)
KirimEmailWorker.perform_at(Time.now + 2.days, user.id, :followup)

HyperLogLog — Estimasi Unique Count #

HyperLogLog memungkinkan estimasi jumlah unique element dengan penggunaan memori yang sangat kecil (hanya 12KB):

# Tambahkan elemen
redis.pfadd("unique_visitor:20240815", "ip1", "ip2", "ip3", "ip1")   # ip1 duplikat diabaikan

# Hitung estimasi (akurasi ~0.81%)
puts redis.pfcount("unique_visitor:20240815")   # => ~3

# Merge beberapa HyperLogLog
redis.pfmerge("unique_visitor:minggu_ini",
  "unique_visitor:20240812",
  "unique_visitor:20240813",
  "unique_visitor:20240814",
  "unique_visitor:20240815"
)
puts redis.pfcount("unique_visitor:minggu_ini")   # estimasi unique visitor minggu ini

Redis Stream — Event Log Permanen #

Redis Stream adalah struktur data log append-only mirip Kafka — untuk event sourcing sederhana:

# Tambahkan event ke stream
id = redis.xadd(
  "pesanan_events",
  "*",   # auto-generate ID berdasarkan timestamp
  "event",      "pesanan_dibuat",
  "pesanan_id", "12345",
  "total",      "150000",
  "user_id",    "99"
)
puts "Event ID: #{id}"   # => "1723724400000-0"

# Baca event terbaru
events = redis.xrange("pesanan_events", "-", "+", count: 10)
events.each do |id, fields|
  puts "#{id}: #{fields}"
end

# Consumer group — distribusi event ke beberapa consumer
redis.xgroup("CREATE", "pesanan_events", "worker_group", "$", mkstream: true)

# Consumer baca dari group
pesan = redis.xreadgroup(
  "GROUP", "worker_group", "worker-1",
  COUNT: 10, BLOCK: 5000,
  "pesanan_events" => ">"   # ">" = pesan yang belum diassign
)

# Acknowledge setelah diproses
redis.xack("pesanan_events", "worker_group", id)

Monitoring Redis #

# Info Redis
info = redis.info
puts "Redis versi:     #{info['redis_version']}"
puts "Memory used:     #{info['used_memory_human']}"
puts "Connected clients: #{info['connected_clients']}"
puts "Commands/sec:    #{info['instantaneous_ops_per_sec']}"
puts "Hit rate:        #{info['keyspace_hits'].to_f / (info['keyspace_hits'].to_f + info['keyspace_misses'].to_f) * 100}%"

# Monitor key yang paling sering diakses
redis.debug(:sleep, 0)   # pastikan debug tersedia

# Slowlog — query yang lambat
slowlog = redis.slowlog(:get, 10)
slowlog.each do |entry|
  puts "#{entry[0]}: #{entry[2]}μs — #{entry[3].join(' ')}"
end

# DBSIZE — jumlah key
puts redis.dbsize

# Scan key dengan pattern (tidak blocking seperti KEYS)
cursor = "0"
loop do
  cursor, keys = redis.scan(cursor, match: "produk:*", count: 100)
  keys.each { |k| puts k }
  break if cursor == "0"
end

Ringkasan #

  • Connection pool wajib untuk multi-thread — satu koneksi Redis tidak thread-safe; gunakan ConnectionPool atau pool_size bawaan Redis gem 5+ agar setiap thread dapat koneksi sendiri.
  • Pilih tipe data yang tepat — String untuk nilai sederhana, Hash untuk objek, List untuk queue/stack, Set untuk membership, Sorted Set untuk ranking/rate limiting, HyperLogLog untuk unique count perkiraan.
  • TTL pada semua cache key — selalu set expiry agar Redis tidak kehabisan memori; pilih TTL berdasarkan seberapa sering data berubah dan seberapa lama stale data bisa diterima.
  • Lua script untuk operasi atomic kompleks — operasi yang melibatkan banyak perintah dan harus atomic (bukan MULTI/EXEC) lebih tepat menggunakan Lua script yang dieksekusi atomik di server.
  • Pipelining untuk throughput tinggi — kirim banyak perintah dalam satu round-trip dengan redis.pipelined { } daripada satu per satu; bisa meningkatkan throughput 10-100x.
  • WATCH + MULTI untuk optimistic locking — untuk kondisi race yang jarang terjadi, lebih efisien dari distributed lock; jika nilai berubah antara WATCH dan EXEC, transaksi gagal dan bisa diulang.
  • Distributed lock dengan nilai unik — selalu simpan nilai unik (bukan “1”) sebagai nilai lock, dan gunakan Lua script untuk release agar tidak menghapus lock milik proses lain.
  • SCAN bukan KEYS di productionKEYS * memblokir Redis sampai selesai; gunakan SCAN dengan cursor yang iteratif dan tidak blocking.
  • Sorted Set untuk rate limiting — simpan timestamp request sebagai score, hapus yang sudah kadaluarsa dengan ZREMRANGEBYSCORE, hitung sisa dengan ZCARD; semua operasi O(log N).
  • Monitor hit rate cache — hit rate di bawah 80% menandakan terlalu banyak cache miss; periksa apakah TTL terlalu pendek atau key naming tidak konsisten.

← Sebelumnya: Google Pub/Sub   Berikutnya: Memcached →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact