Redis ile Leaderboard Oluşturma

Sercan Çakır
İyi Programlama
Published in
5 min readOct 7, 2020

--

Neredeyse her uygulamada karşımıza çıkan oyunlaştırma, aslında ziyaretçileri (oyuncuları) hırsa teşvik etmek ve uygulama içinde daha fazla zaman geçirmesini sağlamak için geliştirilmiş bir iş kuralıdır.

Uygulama türünün ne olduğu önemli değildir. Oyunlaştırma her alanda uygulanabilir. Farklı oyunlaştırma yöntemleri olsa da lider tablosu (leaderboard) en kolay uygulanabilir çeşididir. Oyuncuların o listeye girme arzusu uygulamanın daha fazla kullanılmasını sağlar.

https://techcrunch.com/2015/08/20/swarm-gets-back-into-the-game-with-leaderboards/

Kullanıcıların hareketlerini veritabanında saklarız. Kim ne kadar başarılı olmuş, nerelerde hatalar yapmış gibi istatistiksel verilere sahip olduğumuzu göz önüne aldığımızda basit bir SQL sorgusu ise lider tablosunu elde edebileceğimizi düşünebilirsiniz.

Kesinlikle haklısınız, yük altında olmayan uygulamalarda SQL sorgusu ile bu işlemi hızlıca karşılayabilirsiniz. Ancak reklam çalışmaları başladığında, artık uygulamanıza eskisinden çok daha fazla sayıda kullanıcı gelmeye başlarsa ne olacak? Belirli bir süre sonra veritabanına giden çok sayıda sorgu canınızı yakacak. Belki veritabanına giden sorguları önbelleğe almayı bir çözüm olarak görebilirsiniz. Ancak bu seferde veriler güncelliğini kaybedecek. Oyuncu yeni puan kazandı ve bu puanı veritabanına yazmanıza rağmen lider tablosu güncellenmedi! Sürekli önbelleği temizle, yeniden oluştur gibi çözümlerde sizi istediğiniz hedefe götürmeyecek.

Bu problemi teknik açıdan ele aldığımızda, üretilecek çözüm için aşağıdaki beklentilerin karşılanması gereklidir:

  • Performans sorunlarına yol açmamalı.
  • Elde edilen son veriler güncel olmalı.
  • Kolay uygulanabilir olmalı.

Tüm bu beklentileri karşılamak zor gibi görünüyor. Ancak endişelenmeyin çözümü var.

Redis

Redis, diğer önbellek sürücülerinin aksine veriyi sadece hash tablosu olarak tutmak yerine, farklı veri yapıları desteği ile önbellekteki veri üzerinde işlem yapma ve karar verme yeteneğine sahiptir.

Lider tablolarını saklamak ve sonuç elde edebilmek için sıralı kümeler (sorted sets) veri yapısını kullanacağız.

Hızlıca bazı komutları tanıyalım:

  • ZINCRBY: Tabloya yeni kayıt ekler. Lider tablosunda sadece skor ve oyuncu kimliği (tercihe göre username veya ID değeri) çifti tutulur. ZINCRBY yerine INCR seçeneği ile ZADD komutu da kullanılabilir. Eklemek istediğiniz kayıt lider tablosunda yoksa başlangıç değeri 0 kabul edilerek eklediğiniz skora eşitler.
  • ZREVRANGE: Tabloda kayıtlı verileri skor değerine göre azalan modda (en yüksek skordan en düşüğe doğru) sıralar. Oyuncunun lider tablosundaki sıralamasıyla birlikte mevcut skor değerini de almak için WITHSCORES seçeneği ile kullanılabilir. Puanı eşit olan oyuncular, oyuncu adına göre alfabetik önceliğe göre sıralanır.
  • ZREVRANK: Belirli bir oyuncunun lider tablosundaki sıralamasını (konumunu) döndürür.
  • ZSCORE: Belirli bir oyuncunun lider tablosundaki o anki puanını döndürür.
  • ZCARD: Lider tablosundaki toplam oyuncu sayısını döndürür.

Redis, bir lider tablosuna veri eklendiğinde tablodaki oyuncuların sıralamasını hızlıca hesaplar. Herhangi bir gecikme yaşamadan güncel sonuçlara hızlıca erişebilirsiniz.

Uygulama

Bu bölümde şimdiye kadar ifade ettiğimiz senaryolardan yola çıkarak basit bir örnek yapacağız. Uygulamayı deneyeceğiniz bilgisayarda Redis kurulu olduğunu kabul ediyorum.

Adı “scores” olan lider tablosuna bazı verileri ekleyelim:

ZINCRBY scores 10 "PL_1"
ZINCRBY scores 20 "PL_2"
ZINCRBY scores 20 "PL_3"
ZINCRBY scores 25 "PL_4"
ZINCRBY scores 15 "PL_5"
ZINCRBY scores 20 "PL_1"
ZINCRBY scores 50 "PL_2"

ZINCRBY komutu 3 parametre alıyor. Birinci parametre lider tablonun adı veya anahtarıdır, bu örnekte “scores” isimli lider tablosu oluşturmak istiyoruz. Daha sonra sırasıyla puan ve oyuncu adı. Tabloya eklemek istediğiniz puan oyuncunun o an hak ettiği puan değeridir.

ZINCRBY komutunu kullandığımız için hak edilen skorlar mevcut skor değerinin üzerine eklenerek depolanacağı için genel sonuç aşağıdaki gibi görünecektir:

PL_1 -> 30
PL_2 -> 70
PL_3 -> 20
PL_4 -> 25
PL_5 -> 15

Bu liste Redis tarafından oluşturulmadı. Biz hangi kullanıcının hangi skora sahip olduğunu belirtmek için oluşturduk. Şimdi bu listeyi puana göre sıralayalım:

ZREVRANGE scores 0 10

Yukarıdaki komutu çalıştırdığımızda, Redis hafızasında tuttuğu verileri skor değerine göre azalan modda sıralayarak sonuç döndürür. ZREVRANGE komutu 3 parametre kabul eder. Birinci parametre olarak lider tablosunun adı, ikinci parametre sıralamanın başlayacağı konum ve son parametre olarak başlangıçtan itibaren kaç adet kayıt döndürüleceğidir. Biz burada “scores” tablosundaki en iyi 10 oyuncuyu talep ediyoruz. Eğer talep ettiğiniz sayı kadar sonuç yoksa Redis sahip olduğu kadar veriyi döndürür.

Redis tarafından çıktılanan sonuç:

1) "PL_2"
2) "PL_1"
3) "PL_4"
4) "PL_3"
5) "PL_5"

Eğer bu listede oyuncuların puanlarını da göstermek istersek WITHSCORES seçeneğini kullanmalıyız:

ZREVRANGE scores 0 10 WITHSCORES

Böylece Redis, çıktılanan sonuca oyuncuların o anki toplam puanlarını da ekleyecektir. Çıktı şöyle görünecektir:

 1) "PL_2"
2) "70"
3) "PL_1"
4) "30"
5) "PL_4"
6) "25"
7) "PL_3"
8) "20"
9) "PL_5"
10) "15"

Komut işlendikten sonra ikili değer çifti döndürülür. Elde edilen veri çiftinde ilk değer oyuncunun adı, ikinci değer ise oyuncunun skorudur. Bu değer çiftini ihtiyacınıza göre işleyebilirsiniz.

Belirli bir oyuncunun lider tablosundaki o anki sıralamasını öğrenmek için:

ZREVRANK scores PL_1

komutunu kullanabiliriz. Komut azalan modda sıralanmış lider tablosundaki oyuncu sıralamasını döndürür. En iyi skora sahip kullanıcı için geriye 0 (sıfır) değeri döndürüleceği için, elde edilen yanıt +1 değeri ile toplanması gerekebilir.

Oyuncunun lider tablosundaki toplam puanını öğrenmek için:

ZSCORE scores PL_1

komutunu kullanabiliriz.

Son olarak lider tablosundaki toplam oyuncu sayısını elde etmek istediğimizde ise:

ZCARD scores

komutunu kullanabilirsiniz. Bu komut henüz listede yer almamış oyuncu pozisyonunu belirlemek için kullanılabilir. Mesela oyuna yeni giriş yapmış ve hiç puan kazanmamış bir kullanıcıyı sorguladığınızda Redis geriye (nil) değeri (sonuç yok) döndürür:

ZREVRANK scores PL_6

Elde edilecek (nil) değeri oyuncu için anlamsızdır. Henüz listede yer almayan oyuncuları sanki listede varmış gibi göstermek için basit bir çözüm bulabiliriz:

oyuncunun puanı  = 0
oyuncunun konumu = listedeki toplam oyuncu sayısı + 1

Hepsi bu kadar.

Öneriler

Artık basit lider tabloları oluşturabilir durumdayız. Yukarıda uygulanan model tüm zamanların en iyiler listesini elde etmenizi sağlar.

Günlük lider tablosu oluşturmanız gerektiğinde, gün sonunda sürekli Redis içindeki veriyi temizlemekle uğraşmak yerine lider tablosunun ismini tarihli olarak oluşturabilirsiniz. Örneğin; 7 Ekim 2020 tarihli lider tablosu için “scores-20201007” ismini tercih edebilirsiniz.

Lider tablosunu isimlendirirken tarih verilerini kullanarak istediğiniz tarihli veriye ulaşabilirsiniz. Bazen sadece günün en iyileri yerine, oyuncunun bir önceki güne göre konumunun değişimini veya farklı periyotlardaki liderlik tablolarını servis etmek isteyebilirsiniz. Örneğin, haftanın en iyilerini bulmak isterseniz ZUNIONSTORE ile birden fazla lider tablosunu birleştirip yeni bir lider tablosu elde edilebilir.

Redis sunucusunu konfigüre edecek yeterli bilgiye sahip değilseniz belirli periyotlarla (cron ile) Redis tablolarını temizleyip, veritabanındaki verilerle toplu olarak (bulk) yeniden oluşturabilirsiniz.

Sonuç

Redis, lider tablolarının oluşturulması ve sorgulanması için tatmin edici performansa ve basitliğe sahip. Ayrıca Redis üzerindeki veriyi kalıcı olarak koruyabilme özelliği de göz önüne alındığında gerçekten bu iş için en pratik çözüm olarak karşımıza çıkıyor.

Bu yazıda yer alan komutlar Redis dünyasının çok küçük kısmı. Yaptığımız örnek ise oldukça basitti. Daha karmaşık iş kuralına sahip örnekler için farklı komutlara da yönelebilirsiniz.

Sevgiler.

--

--