Redis'te transaction, MULTI ile başlayıp EXEC ile çalıştırılan bir komut grubudur. MULTI sonrasında gönderdiğiniz komutlar hemen çalışmaz, QUEUED yanıtıyla kuyruğa alınır; EXEC geldiğinde hepsi sırayla ve araya başka bir istemcinin komutu girmeden çalışır. DISCARD kuyruğu boşaltıp transaction'dan çıkar, WATCH ise izlenen anahtarlar değiştiyse EXEC'in hiç çalışmamasını sağlayarak iyimser kilitleme (check-and-set) sunar. Önemli bir fark: Redis transaction'ları geri alma (rollback) desteklemez.
Bu yazıda transaction'ın verdiği iki garantiyi, kuyruğa alma sırasında ve EXEC sırasında oluşan hataların farkını, EXECABORT hatasını, WATCH ile yarış koşullarını nasıl önleyeceğinizi ve bunu Python ile Node.js istemcilerinde nasıl kullanacağınızı belgelerdeki oturumlar üzerinden anlatıyoruz. Serinin diğer yazıları için Redis öğrenme rehberi sayfasına bakabilirsiniz.
Redis transaction'ının iki garantisi
Belgeler Redis transaction'larının iki temel garanti verdiğini söyler. Birincisi yalıtımdır: transaction'daki tüm komutlar sıraya konur ve art arda çalıştırılır; başka bir istemcinin isteği transaction'ın ortasında asla işlenmez. Bu sayede komut grubu tek bir yalıtılmış işlem gibi davranır.
İkincisi ya hep ya hiç ilkesine benzer bir yürütme kuralıdır: komutları tetikleyen EXEC'tir. İstemci EXEC göndermeden bağlantıyı kaybederse kuyruktaki işlemlerin hiçbiri yapılmaz; EXEC çağrılmışsa hepsi yapılır. AOF (append-only file) kalıcılığı açıksa Redis transaction'ı diske tek bir write(2) sistem çağrısıyla yazmaya çalışır. Yine de sunucu çöker ya da sert biçimde durdurulursa yalnızca bir kısmı kaydedilmiş olabilir; Redis yeniden başlarken bunu fark eder ve hatayla çıkar. Bu durumda redis-check-aof aracı yarım kalan transaction'ı dosyadan temizleyerek sunucunun yeniden açılmasını sağlar. RDB ve AOF kavramları için Redis nedir yazımıza bakın.
Temel kullanım: MULTI, QUEUED ve EXEC
MULTI her zaman OK yanıtı verir. Bu noktadan sonra gönderdiğiniz komutlar çalıştırılmaz, kuyruğa eklenir ve her biri QUEUED durum yanıtı döndürür. Belgedeki örnek foo ve bar anahtarlarını atomik olarak artırır:
> MULTI
OK
> INCR foo
QUEUED
> INCR bar
QUEUED
> EXEC
1) (integer) 1
2) (integer) 1EXEC bir dizi yanıt döndürür; dizinin her elemanı transaction'daki bir komutun yanıtıdır ve sıralama komutların gönderiliş sırasıyla aynıdır. Bu örnekte iki anahtar da daha önce yoktu, bu yüzden ikisi de 1 oldu. Kuyruğa alınmış bir komutun yanıtı EXEC öncesinde bilinmez; bu da transaction içinde bir okumanın sonucuna göre karar veremeyeceğiniz anlamına gelir. O tür ihtiyaçlar için aşağıdaki WATCH bölümüne ya da Lua betiklerine bakın.
Transaction ile pipelining birbirinden farklıdır ama birlikte de kullanılabilir. İstemci kütüphaneleri genellikle MULTI, kuyruktaki komutlar ve EXEC dizisini tek bir pipeline olarak gönderir, böylece kuyruğa alma adımları için ayrı ayrı RTT ödenmez. Pipelining'in kendisi için pipelining yazımıza bakın.
Transaction içindeki hatalar: iki farklı tür

Bir transaction sırasında iki tür komut hatasıyla karşılaşabilirsiniz ve Redis bunları çok farklı ele alır.
1. Kuyruğa alma sırasında hata: EXECABORT
Komut sözdizimsel olarak hatalıysa (yanlış argüman sayısı, bilinmeyen komut adı gibi) ya da sunucu maxmemory sınırı nedeniyle bellek yetersizliği gibi kritik bir durumdaysa komut kuyruğa hiç alınmaz ve hemen hata döner. Redis 2.6.5 ile birlikte sunucu bu hataları kuyruk oluşurken fark eder ve EXEC geldiğinde transaction'ı çalıştırmayı reddedip tamamen atar. Hata sayfasındaki örnek:
> MULTI
OK
> FOO
(error) ERR unknown command 'FOO'
> SET a 1
QUEUED
> EXEC
(error) EXECABORT Transaction discarded because of previous errors.Buradaki SET a 1 geçerli bir komut olduğu halde çalışmaz; transaction'ın tamamı iptal edilir. Çözüm, transaction'daki her komutun yanıtına bakmaktır: QUEUED yerine hata alan komut sorunun kaynağıdır. Telnet ile protokol düzeyinde yapılan aşağıdaki örnekte yanlış sayıda argümanla gönderilen INCR komutunun hiç kuyruğa alınmadığı görülür:
MULTI
+OK
INCR a b c
-ERR wrong number of arguments for 'incr' commandEXECABORT hata sayfası bir ayrıntı daha verir: Redis EXEC komutunun kendisini reddederse, mesaj iki noktadan sonra nedeni de yazar. Örneğin kullanıcının EXEC çalıştırma yetkisi yoksa mesaj EXECABORT Transaction discarded because of: NOPERM ... biçiminde gelir. ACL yetkileri ACL yazımızda anlatılıyor; yaygın hata mesajlarının tamamı için Redis hata mesajları yazımıza bakın.
2. EXEC sırasında hata: diğer komutlar yine çalışır
Sözdizimi doğru olan ama çalışırken başarısız olan komutlar ise özel bir işlem görmez. Örneğin string tutan bir anahtara liste komutu uygulamak WRONGTYPE hatası verir, fakat bu yalnızca o komutu etkiler; kuyruktaki diğer komutlar çalışmaya devam eder. Belgedeki protokol düzeyindeki örnekte EXEC iki elemanlı bir yanıt döndürür: biri OK, diğeri hata.
MULTI
+OK
SET a abc
+QUEUED
LPOP a
+QUEUED
EXEC
*2
+OK
-WRONGTYPE Operation against a key holding the wrong kind of valueBu hatanın kullanıcıya nasıl gösterileceği istemci kütüphanesine kalmıştır. Uygulamanızda EXEC sonucunu yalnızca "başarılı mı" diye değil, her elemanı tek tek kontrol ederek değerlendirmeniz gerekir.
| Hata ne zaman oluşur | Örnek | Sonuç |
|---|---|---|
| Kuyruğa alma sırasında | Bilinmeyen komut, yanlış argüman sayısı, bellek sınırı | EXEC, EXECABORT döndürür; hiçbir komut çalışmaz |
| EXEC sırasında | Yanlış tipte anahtara komut (WRONGTYPE) | Yalnızca o komut hata döndürür; diğerleri çalışır |
| WATCH edilen anahtar değişmişse | Başka istemci anahtarı değiştirdi | EXEC null yanıt döndürür; hiçbir komut çalışmaz |
Geri alma (rollback) neden yok?
Belgelerin bu soruya cevabı kısadır: rollback desteği Redis'in sadeliğini ve performansını ciddi biçimde etkileyeceği için sunulmaz. Pratikteki sonucu şudur: EXEC sırasında bir komut hata verirse, ondan önce ve sonra çalışan komutların etkileri kalır. İlişkisel veritabanlarındaki gibi "bir adım başarısız olursa hepsini geri al" davranışına güveniyorsanız mantığınızı yeniden kurmanız gerekir. Çalışma zamanı hatalarının çoğu yanlış veri tipi gibi programlama hatalarından kaynaklandığı için, komutları kuyruğa göndermeden önce anahtar adlarını ve tiplerini doğru tasarlamak en iyi korumadır.
DISCARD ile kuyruğu iptal etmek
DISCARD transaction'ı iptal eder: kuyruktaki komutların hiçbiri çalışmaz ve bağlantı normal duruma döner. WATCH kullanılmışsa DISCARD bağlantının izlediği tüm anahtarları da izlemeden çıkarır.
> SET foo 1
OK
> MULTI
OK
> INCR foo
QUEUED
> DISCARD
OK
> GET foo
"1"WATCH ile iyimser kilitleme (check-and-set)

Transaction tek başına bir okumanın sonucuna göre karar vermenize izin vermez. Belgelerdeki klasik örnekte INCR komutu yokmuş gibi bir anahtarı bir artırmak istediğinizi düşünün. İlk deneme şu sözde kodla yapılır:
val = GET mykey
val = val + 1
SET mykey $valBu kod yalnızca aynı anda tek bir istemci çalışıyorsa güvenilirdir. İki istemci yaklaşık aynı anda çalışırsa ikisi de eski değeri, diyelim 10'u okur, ikisi de 11'i hesaplar ve ikisi de 11 yazar. Sonuç 12 olması gerekirken 11 olur. WATCH bu yarış koşulunu şöyle çözer:
WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXECWATCH ile işaretlenen anahtarlar değişikliğe karşı izlenir. WATCH ile EXEC arasında izlenen anahtarlardan en az biri değişirse transaction'ın tamamı iptal edilir ve EXEC null yanıt döndürür. Uygulamanın yapması gereken, işlemi baştan tekrarlamaktır. Kilit tutulmadığı, yalnızca çakışma olursa yeniden denendiği için buna iyimser kilitleme denir. Belgelerin belirttiği gibi çoğu senaryoda istemciler farklı anahtarlara eriştiğinden çakışma seyrektir ve tekrar gerekmez.
WATCH tam olarak neyi izler?
- İstemcilerin yaptığı yazma işlemleri ve Redis'in kendisinin yaptığı değişiklikler, yani süre dolması (expire) ve bellek için çıkarma (eviction) da anahtarı "değişmiş" sayar.
- Redis 6.0.9 öncesinde süresi dolan bir anahtar transaction'ı iptal ettirmiyordu; daha eski sunucularda bu farkı hesaba katın.
- Transaction içindeki komutlar WATCH koşulunu tetiklemez, çünkü EXEC gelene kadar yalnızca kuyruktadır.
- WATCH birden çok kez çağrılabilir ve tek çağrıda birden çok anahtar alabilir; her çağrı, kendi anından EXEC anına kadar geçerlidir.
- EXEC çağrıldığında, transaction iptal edilse de edilmese de tüm anahtarlar izlemeden çıkar. Bağlantı kapandığında da aynısı olur.
Bazen birkaç anahtarı izlemeye aldıktan ve içeriklerini okuduktan sonra transaction'a hiç devam etmemeye karar verirsiniz. Bu durumda argümansız UNWATCH çağrısı izlenen tüm anahtarları bırakır ve bağlantı yeni transaction'lar için serbest kalır. EXEC veya DISCARD çağırdıysanız ayrıca UNWATCH göndermeniz gerekmez.
Örnek: WATCH ile ZPOP benzeri işlem
Belgeler WATCH'un Redis'te olmayan atomik işlemleri kurmaya nasıl yardım ettiğini, sıralı kümeden en düşük puanlı elemanı çıkaran bir ZPOP örneğiyle gösterir. Bugün bu iş için hazır ZPOPMIN ve ZPOPMAX komutları vardır (belgelere göre 5.0 ile geldiler); ancak desen öğreticidir:
WATCH zset
element = ZRANGE zset 0 0
MULTI
ZREM zset element
EXECEXEC null yanıt döndürürse, yani başka bir istemci bu arada kümeyi değiştirdiyse, işlem tekrar edilir. Sıralı kümeler için sorted set yazımıza bakın.
Daha basit alternatif: koşullu SET ve DELEX
Tek bir string anahtar için "okuduğumdan beri değişmediyse güncelle" ihtiyacınız varsa belgeler daha basit ve hızlı bir yol gösterir: Redis 8.4 ile gelen SET seçenekleri IFEQ, IFNE, IFDEQ ve IFDNE bu karşılaştırmayı tek komutta atomik olarak yapar; DELEX komutu da aynı mantıkla karşılaştırıp silmeyi sağlar. Bu seçenekler Redis 8.4 ve sonrasını gerektirir; daha eski sunucularda WATCH deseni geçerliliğini korur. String komutları için SET, GET ve INCR yazımıza bakın.
İstemci kütüphanelerinde transaction

Python (redis-py)
redis-py'da pipeline nesnesi varsayılan olarak transaction gibi çalışır; komutlar kesintisiz bir dizi halinde yürütülür. Bu davranışı kapatmak için transaction=False verilir. WATCH ile izlenen bir anahtar başka bir istemci tarafından değiştirilirse redis-py WatchError istisnası fırlatır; bu istisnayı redis.exceptions modülünden içe aktarabilirsiniz. Belgeler, tekrar deneme döngüsünü elle yazmak yerine kütüphanenin transaction() yardımcı yöntemini önerir: yönteme transaction kodunuzu içeren bir fonksiyon ve izlenecek anahtarları verirsiniz, o da izleme, çalıştırma ve gerekirse yeniden deneme işini üstlenir. Dikkat: transaction() sizin yerinize multi() çağrısını eklemez, onu fonksiyonunuzun içinde doğru yere sizin koymanız gerekir.
Node.js (node-redis)
node-redis'te transaction multi() ile oluşturulur, komut yöntemleri bu nesne üzerinde çağrılır ve sonunda exec() çalıştırılır. Belgedeki örnek ve gösterilen çıktılar:
const [res1, res2, res3] = await client.multi()
.incrBy("counter:1", 1)
.incrBy("counter:2", 2)
.incrBy("counter:3", 3)
.exec();
console.log(res1); // >>> 1
console.log(res2); // >>> 2
console.log(res3); // >>> 3WATCH kullanırken belgeler önemli bir uyarı yapar: izlenen anahtarlar için yapılan okuma komutlarını normal client nesnesi üzerinden, transaction komutlarını ise multi() ile oluşturulan nesne üzerinden çağırmalısınız. Sunucu WATCH durumunu bağlantı başına tuttuğu için, birden çok eşzamanlı isteğin aynı bağlantıyı paylaştığı bir web sunucusunda createClientPool() ile ayrı bağlantı almak gerekir; aksi halde aynı bağlantı üzerindeki eşzamanlı WATCH ve MULTI/EXEC çağrıları birbirini bozar.
// Belgedeki örnekten: izlenen anahtarı okuyup transaction içinde yazmak
await client.watch('shellpath');
const currentPath = await client.get('shellpath');
const newPath = currentPath + ':/usr/mycmds/';
await client.multi()
.set('shellpath', newPath)
.exec()
.then((result) => {
console.log(result);
}, (err) => {
// izlenen anahtar değiştiyse buraya düşer
console.log(err);
});Üretimde bu kodu bir döngü içinde çağırıp başarılı olana kadar yeniden denemeniz ya da hatayı kaydetmeniz beklenir. Kütüphanelerin ayrıntıları için redis-py ve node-redis yazılarımıza bakın.
Cluster ve Lua betikleriyle ilişkisi
Redis Open Source cluster modunda bir MULTI/EXEC transaction'ındaki anahtarlar tek bir hash slot'ta olmalıdır; WATCH da bu kurala tabidir. Anahtarları aynı slota toplamak için hash tag kullanılır; bu konu Redis Cluster yazımızda anlatılıyor.
Belgeler transaction benzeri işlemler için Lua betiklerini de hatırlatır: betikler transactional'dır ve bir transaction ile yapabileceğiniz her şeyi betikle de yapabilirsiniz; üstelik betik genellikle hem daha basit hem daha hızlıdır. Okuyup karar verip yazmanız gereken durumlarda WATCH döngüsü yerine bir betik yazmayı düşünün. Ayrıntılar için EVAL ve EVALSHA yazımıza ve sunucuda kalıcı fonksiyonlar için Redis Functions yazımıza bakın.
Sık yapılan hatalar
- EXEC'in "başarılı" dönmesini her komutun başarılı olduğu sanmak: çalışma zamanı hataları dizinin içinde tek tek gelir.
- Rollback beklemek: hata veren komuttan önceki ve sonraki komutların etkileri kalır.
- Transaction içinde bir GET yanıtına göre karar vermeye çalışmak: kuyruktaki komutlar QUEUED döndürür, gerçek değer ancak EXEC sonunda gelir. Karar gerekiyorsa WATCH ya da Lua kullanın.
- WATCH sonrası transaction'dan vazgeçip UNWATCH çağırmamak: bağlantı izlemeye devam eder ve sonraki transaction beklenmedik biçimde iptal olabilir.
- Paylaşılan tek bağlantıda eşzamanlı WATCH kullanmak: WATCH bağlantı başına tutulur, bağlantı havuzu kullanın.
- EXEC null döndüğünde yeniden denememek: iyimser kilitlemenin özü tekrar denemektir.
Sık Sorulan Sorular
Redis transaction ACID midir?
Belgeler bu terimi kullanmaz; verdikleri garantiler yalıtım (araya başka komut girmemesi) ve EXEC ile hep birlikte yürütmedir. Rollback yoktur ve EXEC sırasında hata veren komut diğerlerini durdurmaz. Kalıcılık ise sunucunun RDB/AOF ayarlarına bağlıdır.
EXEC neden (nil) döndürdü?
WATCH ile izlediğiniz anahtarlardan en az biri WATCH ile EXEC arasında değişti; bu değişiklik başka bir istemciden, süre dolmasından ya da eviction'dan gelmiş olabilir. Transaction hiç çalışmadı; işlemi baştan tekrarlamanız gerekir.
EXECABORT hatasını nasıl düzeltirim?
Transaction'daki her komutun yanıtına bakın. QUEUED yerine hata alan komut, transaction'ın atılmasının nedenidir. Çoğunlukla bilinmeyen komut adı ya da yanlış argüman sayısıdır.
Transaction mı, Lua betiği mi?
Bağımsız birkaç yazmayı birlikte ve kesintisiz çalıştırmak için transaction yeterlidir. Bir değeri okuyup ona göre yazmanız gerekiyorsa Lua betiği genellikle daha basit ve daha hızlı bir çözümdür.
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 pipelining · Redis Lua betikleri · Redis Functions · Redis hata mesajları · Redis ile dağıtık kilit