Redis Functions, Redis 7 ile gelen ve Lua kodunu sunucuda adıyla, kalıcı ve replike edilmiş biçimde saklamanızı sağlayan programlanabilirlik arayüzüdür. Kodunuzu bir kütüphane (library) olarak FUNCTION LOAD ile bir kez yüklersiniz; kütüphane içinde redis.register_function() ile kaydettiğiniz her fonksiyonu artık herhangi bir istemci FCALL ile adıyla çağırabilir. EVAL betiklerinin aksine fonksiyonlar önbellekte geçici olarak değil, veriyle birlikte AOF'a yazılır ve replikalara aktarılır; bu yüzden yeniden başlatma ya da failover sonrasında NOSCRIPT derdi yaşanmaz.
Bu yazıda fonksiyonların hangi sorunu çözdüğünü, kütüphane yükleme biçimini, anahtar ve argümanların fonksiyona nasıl geldiğini, kütüphaneyi büyütmeyi ve kod paylaşmayı, fonksiyon bayraklarını, FUNCTION LIST, DELETE, DUMP ve RESTORE komutlarını ve cluster ortamındaki yükleme adımlarını belgelerdeki örnek kütüphane üzerinden anlatıyoruz. Lua dilinin Redis içindeki kullanımı, redis.call ile redis.pcall farkı ve veri tipi dönüşümleri Lua betikleri yazımızda anlatıldığı için burada tekrar etmiyoruz. Serinin tamamı için Redis öğrenme rehberi sayfasına bakın.
EVAL betiklerinin sorunu neydi?

EVAL ile çalışan betikler uygulamanın parçası kabul edilir: sunucu onları yalnızca önbelleğe alır ve önbellek her an kaybolabilir. Belgeler, uygulama karmaşıklaştıkça bu yaklaşımın beş zorluk çıkardığını anlatır:
- Uygulamanın her örneği tüm betiklerin bir kopyasını tutmak zorundadır ve betik güncellemelerini tüm örneklere dağıtacak bir mekanizma gerekir.
- Önbellekteki betikleri bir transaction içinde çağırmak, eksik betik yüzünden transaction'ın başarısız olma ihtimalini artırır.
- SHA1 özetleri anlamsızdır; örneğin bir MONITOR oturumunda hangi betiğin çalıştığını anlamak çok zorlaşır.
- Dikkatsiz kullanıldığında EVAL, KEYS ve ARGV yerine değerleri betiğe gömme anti-desenini teşvik eder.
- Betikler geçici olduğu için bir betik başka bir betiği çağıramaz; kod paylaşımı neredeyse imkânsızdır.
Functions bu ihtiyaçlara, mevcut EVAL kullanımını bozmadan cevap vermek için tasarlandı. Temel fark şudur: geçici betikler uygulamanın alanına aitken fonksiyonlar veritabanı sunucusunu kullanıcı mantığıyla genişletir. Belgeler bunu modüllere benzetir: bir kez geliştirilir, başlangıçta yüklenir ve farklı uygulamalar tarafından tekrar tekrar kullanılır. Fonksiyonu yüklemek böylece bir uygulama işi olmaktan çıkıp, modül yüklemeye benzer bir yönetim ve dağıtım işi haline gelir.
Kütüphane ve fonksiyon kavramları
Her fonksiyon tek bir kütüphaneye aittir ve bir kütüphane birden fazla fonksiyon içerebilir. Kütüphanenin içeriği değiştirilemez; içindeki fonksiyonlardan yalnızca birini güncelleyemezsiniz, kütüphane bütün fonksiyonlarıyla birlikte tek işlemde yenilenir. Bu sayede aynı kütüphanedeki fonksiyonlar birbirini çağırabilir ve ortak yardımcı kodu paylaşabilir. Her fonksiyonun kullanıcı tarafından verilmiş benzersiz bir adı vardır; bu da çağırmayı ve izlemeyi kolaylaştırır.
Fonksiyonlar da betikler gibi atomik çalışır: yürütme sırasında tüm sunucu etkinliği bloklanır ve etkiler ya hiç olmamış ya da tamamen olmuş olur. Sunucuyu blokladıkları için kısa sürede bitmeleri beklenir; uzun süren fonksiyonlardan kaçınmalısınız. Belgelere göre şu an tek motor Lua 5.1'dir ve fonksiyonlar, Lua hata ayıklayıcısı hariç, geçici betiklerde kullanılabilen tüm Lua yeteneklerine sahiptir.
İlk kütüphaneyi yüklemek
Kütüphane yükü (payload) bir shebang satırıyla başlamalıdır. Bu satır kullanılacak motoru ve kütüphane adını bildirir; biçimi #!<motor adı> name=<kütüphane adı> şeklindedir ve motor adı şu an yalnızca lua olabilir. İçinde fonksiyon olmayan bir kütüphane yüklenemez:
redis> FUNCTION LOAD "#!lua name=mylib\n"
(error) ERR No functions registeredHer kütüphanede en az bir kayıtlı fonksiyon bulunmalıdır; kayıtlı fonksiyon, kütüphanenin dışarıya açılan giriş noktasıdır. Lua motoru kütüphaneyi yüklerken kaynak kodu derleyip çalıştırır ve redis.register_function() çağrılarını bekler. Belgedeki en basit kütüphane knockknock adlı tek bir fonksiyon kaydeder:
#!lua name=mylib
redis.register_function(
'knockknock',
function() return 'Who\'s there?' end
)redis> FUNCTION LOAD "#!lua name=mylib\nredis.register_function('knockknock', function() return 'Who\\'s there?' end)"
mylib
redis> FCALL knockknock 0
"Who's there?"FUNCTION LOAD yüklenen kütüphanenin adını döndürür; bu adı daha sonra FUNCTION LIST ve FUNCTION DELETE ile kullanırsınız. FCALL komutunun ilk argümanı fonksiyon adı, ikincisi EVAL'de olduğu gibi ardından gelecek anahtar adı sayısıdır.
Anahtarlar ve argümanlar fonksiyona nasıl gelir?

Burada EVAL betiklerinden önemli bir fark vardır: fonksiyonlarda KEYS ve ARGV global değişkenleri bulunmaz. Lua API referansına göre bu iki değişken yalnızca geçici betiklerde vardır. Fonksiyonda anahtar adları geri çağırma (callback) fonksiyonunun birinci parametresine, diğer argümanlar ikinci parametresine Lua tablosu olarak gelir. Anahtar kuralı ise aynıdır: fonksiyonun eriştiği tüm anahtar adları açıkça anahtar argümanı olarak verilmelidir.
Belgelerdeki örnekte uygulama verisini hash'lerde tuttuğunu ve HSET benzeri bir işlemle alanları güncellerken son değişiklik zamanını da _last_modified_ alanına yazmak istediğinizi düşünün. Fonksiyon sunucu saatini TIME ile alır ve hash'i günceller:
#!lua name=mylib
local function my_hset(keys, args)
local hash = keys[1]
local time = redis.call('TIME')[1]
return redis.call('HSET', hash, '_last_modified_', time, unpack(args))
end
redis.register_function('my_hset', my_hset)Kütüphaneyi mylib.lua dosyasına kaydettiyseniz, boşlukları ve satır sonlarını koruyarak redis-cli'ın -x seçeneğiyle yükleyebilirsiniz. -x son argümanı standart girdiden okur:
$ cat mylib.lua | redis-cli -x FUNCTION LOAD REPLACEREPLACE değiştiricisi aynı adlı mevcut kütüphanenin üzerine yazılmasını sağlar; verilmezse Redis kütüphanenin zaten var olduğunu söyleyen bir hata döndürür. Yüklendikten sonra fonksiyon şöyle çağrılır:
redis> FCALL my_hset 1 myhash myfield "some value" another_field "another value"
(integer) 3
redis> HGETALL myhash
1) "_last_modified_"
2) "1640772721"
3) "myfield"
4) "some value"
5) "another_field"
6) "another value"FCALL burada 1 anahtar sayısıyla çağrıldığı için myhash callback'in keys tablosuna, kalan dört değer args tablosuna gitti. Yanıttaki 3, HSET komutunun eklediği yeni alan sayısıdır: _last_modified_, myfield ve another_field. Hash komutlarının ayrıntısı için hash komutları yazımıza bakın.
Kütüphaneyi büyütmek ve kod paylaşmak
Eklenen meta veri alanı, hash okunurken yanıtta görünmemelidir; öte yandan değiştirilme zamanını ayrıca sorgulayabilmek isteriz. Belgeler bunun için kütüphaneye iki fonksiyon ekler: my_hgetall tüm alanları _last_modified_ hariç döndürür, my_hlastmodified ise yalnızca zaman damgasını verir. my_hgetall içinde redis.setresp(3) çağrılır; böylece HGETALL yanıtı RESP3 map olarak gelir ve res['map']['_last_modified_'] = nil ile tek bir alan yanıttan çıkarılabilir.
local function my_hgetall(keys, args)
redis.setresp(3)
local hash = keys[1]
local res = redis.call('HGETALL', hash)
res['map']['_last_modified_'] = nil
return res
end
local function my_hlastmodified(keys, args)
local hash = keys[1]
return redis.call('HGET', hash, '_last_modified_')
end
redis.register_function('my_hset', my_hset)
redis.register_function('my_hgetall', my_hgetall)
redis.register_function('my_hlastmodified', my_hlastmodified)Kütüphane değiştirilemez olduğu için bu üç fonksiyonun tamamı aynı dosyada durur ve dosya yine FUNCTION LOAD REPLACE ile bütün halinde yüklenir. Sonuç:
redis> FCALL my_hgetall 1 myhash
1) "myfield"
2) "some value"
3) "another_field"
4) "another value"
redis> FCALL my_hlastmodified 1 myhash
"1640772721"Kütüphane yalnızca fonksiyonları bir arada paketlemekle kalmaz, kod paylaşımını da kolaylaştırır. Belgelerde kütüphaneye check_keys() adlı yerel bir yardımcı eklenir: keys tablosunda tam olarak bir anahtar olup olmadığını denetler, sorun varsa redis.log() ile sunucu günlüğüne uyarı yazar ve redis.error_reply() ile bir hata yanıtı döndürür. Üç fonksiyon da işe bu yardımcıyı çağırarak başlar. Yardımcı local olarak tanımlandığı için dışarıdan çağrılamaz, yalnızca kütüphane içinde kullanılır. Değiştirilmiş kütüphaneyle hatalı çağrılar şöyle sonuçlanır:
127.0.0.1:6379> FCALL my_hset 0 myhash nope nope
(error) Hash key name not provided
127.0.0.1:6379> FCALL my_hgetall 2 myhash anotherone
(error) Only one key name is allowedFonksiyon bayrakları ve FCALL_RO
Redis, kaynak kullanım kurallarını uygulayabilmek ve veri tutarlılığını koruyabilmek için fonksiyonun nasıl davranacağını bilmek ister. Varsayılan olarak her fonksiyonun hem okuyup hem yazabileceğini varsayar. Bu yüzden yalnızca okuma yapan my_hgetall bile bayraksız kaydedildiyse FCALL_RO ile çalışmaz:
redis > FCALL_RO my_hgetall 1 myhash
(error) ERR Can not execute a function with write flag using fcall_ro.Belgelere göre sunucu bu hatayı üç durumda verir: salt okunur bir replikaya FCALL ile fonksiyon çalıştırmak, fonksiyonu FCALL_RO ile çağırmak ve sunucunun disk hatası nedeniyle yazmaları reddettiği durum. Çözüm, fonksiyonu no-writes bayrağıyla kaydetmektir. Bayrak vermek için redis.register_function'ın adlandırılmış argümanlı biçimi kullanılır:
redis.register_function('my_hset', my_hset)
redis.register_function{
function_name='my_hgetall',
callback=my_hgetall,
flags={ 'no-writes' }
}
redis.register_function{
function_name='my_hlastmodified',
callback=my_hlastmodified,
flags={ 'no-writes' }
}Adlandırılmış biçimde function_name ve callback zorunludur; flags ve description isteğe bağlıdır. Kütüphane yeniden yüklendikten sonra iki okuma fonksiyonu salt okunur replikada FCALL_RO ile çalışır. Diğer bayraklar (allow-oom, allow-stale, no-cluster, allow-cross-slot-keys) Lua betikleriyle ortaktır. Bayrak yazılmayan fonksiyonların cluster'da farklı hash slot'lardaki anahtarlara erişemediğini de unutmayın.
Kütüphaneleri yönetmek
FUNCTION LIST yüklü kütüphaneleri ve fonksiyonlarını listeler. Her kütüphane için library_name, engine ve functions alanları, her fonksiyon için de name, description ve flags döner. LIBRARYNAME ile ada göre süzebilir, WITHCODE ile kaynak kodu da alabilirsiniz. Belgedeki çıktının bir bölümü:
redis> FUNCTION LIST
1) 1) "library_name"
2) "mylib"
3) "engine"
4) "LUA"
5) "functions"
6) 1) 1) "name"
2) "my_hset"
3) "description"
4) (nil)
5) "flags"
6) (empty array)FUNCTION DELETE bir kütüphaneyi tüm fonksiyonlarıyla siler; kütüphane yoksa hata döner. Silinen fonksiyon çağrılırsa ERR Function not found hatası alınır. Komut sayfasındaki örnek, tek fonksiyonlu ayrı bir mylib kütüphanesiyle çalışır:
redis> FUNCTION LOAD "#!lua name=mylib \n redis.register_function('myfunc', function(keys, args) return 'hello' end)"
"mylib"
redis> FCALL myfunc 0
"hello"
redis> FUNCTION DELETE mylib
OK
redis> FCALL myfunc 0
(error) ERR Function not foundFUNCTION DUMP yüklü kütüphanelerin serileştirilmiş yükünü döndürür; FUNCTION RESTORE bu yükü geri yükler. RESTORE mevcut kütüphanelerle nasıl başa çıkacağını belirleyen üç politika alır: varsayılan APPEND geri yüklenenleri mevcutlara ekler ve ad çakışmasında işlemi durdurur, FLUSH önce mevcut tüm kütüphaneleri siler, REPLACE ise ekler ve aynı adlı kütüphaneleri değiştirir. Sayfanın uyarısına göre REPLACE kütüphane çakışmalarını çözer ama fonksiyon adı çakışmalarını önlemez.
FUNCTION LOAD şu durumlarda hata döndürür: geçersiz motor adı, aynı adlı kütüphane varken REPLACE verilmemesi, kütüphanedeki bir fonksiyonun başka bir kütüphanede zaten var olan bir adı kullanması (REPLACE verilse bile), derleme hatası gibi nedenlerle motorun fonksiyonları oluşturamaması ve kütüphanede hiç fonksiyon tanımlanmamış olması. Fonksiyon adlarının kütüphaneler arasında da benzersiz olması gerektiğini bu yüzden baştan planlayın; örneğin adlara kütüphaneye özgü bir ön ek vermek işinizi kolaylaştırır.
Uzun süren bir fonksiyonu incelemek için FUNCTION STATS kullanılır: çalışan fonksiyonun adını, çağrıldığı komutu ve duration_ms cinsinden süresini, ayrıca motor başına fonksiyon ve kütüphane sayılarını verir. Fonksiyon henüz yazma yapmadıysa FUNCTION KILL ile durdurulabilir. Uzun süren bir fonksiyon karşısında diğer istemcilerin aldığı BUSY hatası, betiklerdeki gibi FUNCTION KILL komutunu önerir; davranışın ayrıntısı için Lua yazımızdaki BUSY bölümüne bakın.
Cluster ve geçici (cache) sunucularda fonksiyonlar
Redis yüklenen fonksiyonları replikalara kendiliğinden aktarır. Redis Cluster'da ise fonksiyonların tüm düğümlere yüklenmesi gerekir ve bu iş cluster tarafından otomatik yapılmaz; tıpkı modül yükleme ya da yapılandırma gibi yöneticinin sorumluluğundadır. Belgeler bunun için tüm primary düğümlerde komutu çalıştıran şu redis-cli kullanımını önerir:
redis-cli --cluster-only-masters --cluster call host:port FUNCTION LOAD ...redis-cli --cluster add-node ise yeni eklenen düğüme mevcut düğümlerden birindeki fonksiyonları kendiliğinden kopyalar. Cluster kavramları için Redis Cluster yazımıza bakın.
Redis yalnızca önbellek olarak kullanılıyor ve kalıcılık kapalıysa, yeniden başlatılan sunucu fonksiyonlarını da kaybedebilir. Belgeler bu durumda sunucu bağlantı kabul etmeye başlamadan önce fonksiyonların hazır olması için redis-cli --functions-rdb seçeneğini önerir: bu seçenek mevcut bir sunucudan yalnızca fonksiyonları içeren bir RDB dosyası çıkarır ve Redis bu dosyayı başlangıçta yükleyebilir. Yedekleme komutları için BGSAVE ve yedekleme yazımıza bakın.
EVAL betikleri ile Functions karşılaştırması

| Özellik | EVAL betiği | Redis Functions |
|---|---|---|
| Saklanma | Geçici önbellek, kalıcı değil | Veriyle birlikte AOF ve replikasyon |
| Çağırma | Kaynak kod veya SHA1 özeti | Kullanıcının verdiği fonksiyon adı |
| Anahtar ve argümanlar | KEYS ve ARGV global tabloları | Callback parametreleri (keys, args) |
| Kod paylaşımı | Bir betik diğerini çağıramaz | Aynı kütüphanede ortak yardımcılar |
| Hata ayıklayıcı (LDB) | Var | Yok |
| Gerekli sürüm | Redis 2.6 ve sonrası | Redis 7.0 ve sonrası |
| Salt okunur çağrı | EVAL_RO, EVALSHA_RO | FCALL_RO |
Belgelerin programlanabilirlik sayfası farkı şöyle özetler: EVAL betikleri hızlı ve doğrudan bir yoldur, ama betik uygulamanın parçası olarak kalır ve uygulama büyüdükçe geliştirmesi zorlaşır. Fonksiyonlar ise betiği uygulama mantığından ayırır; bağımsız olarak geliştirilip test edilebilir ve dağıtılabilir.
Sık yapılan hatalar
- Fonksiyon içinde KEYS veya ARGV kullanmak: fonksiyonlarda bu global değişkenler yoktur; callback parametrelerini kullanın.
- Kütüphanedeki tek bir fonksiyonu güncellemeye çalışmak: kütüphane bütün olarak REPLACE ile yeniden yüklenir.
- Fonksiyon adını başka bir kütüphanede tekrar kullanmak: REPLACE verseniz bile FUNCTION LOAD hata verir.
- Okuma fonksiyonlarını bayraksız kaydetmek: FCALL_RO ve salt okunur replikalar bu fonksiyonları reddeder.
- Cluster'da fonksiyonu tek düğüme yüklemek: yükleme otomatik dağıtılmaz, tüm primary düğümlere yükleyin.
- Kalıcılığı kapalı önbellek sunucularında fonksiyonların yeniden başlatmada kaybolabileceğini unutmak.
Sık Sorulan Sorular
Redis Functions hangi sürümle geldi?
Belgelere göre Redis 7.0 ile geldi. Daha eski sunucularda yalnızca EVAL betikleri vardır.
Fonksiyonlar yeniden başlatmada kaybolur mu?
Fonksiyonlar veriyle birlikte kalıcı hale getirilir ve replike edilir; veriniz yeniden başlatmadan sonra geliyorsa fonksiyonlar da gelir. Kalıcılığı kapalı geçici sunucular için belgeler --functions-rdb ile çıkarılan RDB dosyasını önerir.
Bir fonksiyon başka bir fonksiyonu çağırabilir mi?
Aynı kütüphane içinde evet; kütüphanedeki fonksiyonlar birbirini ve ortak yerel yardımcıları çağırabilir. Geçici EVAL betiklerinde ise bu mümkün değildir.
Fonksiyonda hata ayıklayıcı kullanabilir miyim?
Hayır. Belgelere göre fonksiyonlar Lua hata ayıklayıcısı dışında geçici betiklerin tüm Lua yeteneklerine sahiptir. Mantığı geliştirirken EVAL ile ve --ldb seçeneğiyle deneyip sonra kütüphaneye taşıyabilirsiniz.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Redis öğrenme rehberi · Redis Lua betikleri · MULTI, EXEC ve WATCH · Redis hash komutları · Redis Cluster ve hash slot · Redis yedek alma