FOR UPDATE SKIP LOCKED ifadesini, seçilen satırı ready durumundan processing durumuna da değiştiren bir sorgunun içinde kullanın. Böylece eşzamanlı işçiler farklı sentetik makale satırlarını üstlenirken, kaydedilen durum kilit sona erdikten sonra başka bir işçinin aynı satırı üstlenmesini önler.
Bu adım adım kılavuz, çalışan bir PostgreSQL bağlantısı olan Mac’te Terminal kullanan geliştiriciler içindir. Üç sentetik makale oluşturacak, iki veritabanı oturumunda eşzamanlı olarak satır üstlenildiğini doğrulayacak ve node-postgres ile hazırlanmış eksiksiz bir örnek çalıştıracaksınız.
İki işçi neden aynı makaleyi seçebilir?
Sıradan bir SELECT sorgusu, sonucunu ayırmaz. PostgreSQL’in varsayılan Read Committed yalıtım düzeyinde iki işçi, ikisi de değiştirmeden önce aynı ready satırını okuyabilir. Bu sıradan okumayı ve daha sonra yapılacak bir güncellemeyi bir işleme almak, seçimi tek başına eşzamanlı erişime kapatmaz. PostgreSQL anlık görüntü davranışını Transaction Isolation belgesinde açıklar.
Satırı üstlenmek için iki koruma gerekir: makale seçilirken satır kilidi ve bu kilit bırakılmadan önce durum değişikliği. İşlem sona erdiğinde PostgreSQL satır kilitlerini bırakır. Bu nedenle, kilitleme içeren tek başına bir okumanın ardından işlem yapılırsa okumanın işlemi bittikten sonra satır yeniden uygun duruma gelir. Bkz. Explicit Locking.
Her işçide aynı satır üstlenme protokolünü kullanın. ready durumundaki bir makaleyi okuyup başarıyla üstlenmeden işlemeye başlayan uygulama yolu yine de işin yinelenmesine yol açabilir.
Küçük bir sentetik makale kuyruğu oluşturun
Ön koşullar: çalışan bir PostgreSQL veritabanı, Terminal’de kullanılabilen psql komutu ve mevcut bir bağlantı yapılandırması. JavaScript bölümünü uygulamak için desteklenen güncel bir Node.js sürümü ve npm de yüklü olmalıdır. Bu makale, çalışan bir veritabanı bağlantınız olduğunu varsayar; kurulum adımları mevcut Mac yapılandırmanıza göre değişir.
Gerekli SKIP LOCKED özelliği PostgreSQL 9.5’te kullanıma sunuldu. Bu, sözdiziminin en eski desteklenen sürümüdür; desteği sona ermiş bir sürümün kullanılması önerisi değildir. Desteklenen bir PostgreSQL sürümü kullanın. Alınan güncel belgelerde PostgreSQL 18, güncel sürüm olarak belirtiliyor. Bkz. 9.5 release notes ve güncel SELECT documentation.
Terminal’i açın ve alışık olduğunuz bağlantı ayarlarını kullanarak psql komutunu çalıştırın. psql documentation bölümünde açıklandığı gibi bu komut, SQL çalıştırıp sonuçları görüntüleyebileceğiniz etkileşimli bir istem sağlar. Aşağıdaki hazırlık adımlarını, article_claim_demo tablosunun henüz bulunmadığı bir deneme veritabanında bir kez çalıştırın. Bu adımlar mevcut bir tabloyu değiştirmez.
SHOW server_version;
SHOW transaction_isolation;
CREATE TABLE article_claim_demo (
id integer PRIMARY KEY,
title text NOT NULL,
state text NOT NULL DEFAULT 'ready'
CHECK (state IN ('ready', 'processing', 'done')),
rendered_title text
);
INSERT INTO article_claim_demo (id, title) VALUES
(1, 'Paper shapes'),
(2, 'Desk arrangements'),
(3, 'Color combinations');
Örnekler, Read Committed yalıtım düzeyini ve ek tetikleyicileri olmayan bu basit tabloyu varsayar. İki oturumlu testi bitirene kadar üç örnek satırı değiştirmeyin.
Tek bir SQL ifadesiyle bir makale üstlenin
Bir işçinin makaleye ihtiyaç duyduğu her seferde bu ifadeyi çalıştırın. Aday sorgusu kullanılabilir bir satırı kilitler; güncelleme ise satırın üstlenildiğini kaydeder ve girdi alanlarını döndürür. PostgreSQL’in UPDATE documentation belgesi, bu CTE ve güncelleme örüntüsünü açıklar ve RETURNING değerinin gerçekten güncellenen satırları bildirdiğini doğrular.
WITH candidate AS (
SELECT id
FROM article_claim_demo
WHERE state = 'ready'
ORDER BY id
LIMIT 1
FOR UPDATE SKIP LOCKED
)
UPDATE article_claim_demo AS a
SET state = 'processing'
FROM candidate AS c
WHERE a.id = c.id
AND a.state = 'ready'
RETURNING a.id, a.title;
Kilit aday CTE’sinin içinde yer almalıdır. SKIP LOCKED, başka bir işçinin hemen kilitleyemediği adayı atlamasını sağlar. LIMIT 1 her üstlenme işlemine bir satır sınırı koyar; birincil anahtarla yapılan birleştirme ise tek bir güncelleme hedefi belirler. PostgreSQL, WITH sorgusu içindeki kilitlemeyi ve atlanan satırların kuyruk benzeri kullanımını SELECT: The Locking Clause bölümünde açıkça belgeler.
Açık bir BEGIN kullanılmazsa PostgreSQL ifadeyi kendi işlemi içinde çalıştırır. Başarılı ifade, seçimle bağlantılı güncellemeyi ve durum değişikliğini sonraki işlemler başlamadan önce kaydeder. Kayıttan sonra satır kilidi kalkar, ancak state = 'ready' bu makalenin yeniden seçilmesini önler. Bkz. Transactions.
Yalnızca üstlenme ifadesinin döndürdüğü makaleyi işleyin. Boş sonuç, bu denemede makale alınamadığı anlamına gelir; tüm işlerin tamamlandığını göstermez. Başka işçiler şu anda uygun satırları tutuyor olabilir. Ayrıca SKIP LOCKED satır kilitlerini beklemeyi önler, ancak tablo düzeyindeki kilitler yine de beklemeye yol açabilir. Bu sınırlar SELECT belgesinde açıklanmıştır.
İki Terminal oturumunda üstlenme davranışını doğrulayın
Aynı veritabanına psql üzerinden bağlanmış iki Terminal penceresi kullanın. Başlangıçta üç özgün ready satırını bulundurun. Aşağıdaki kontrollü sıra, çakışmayı doğrudan gözlemleyebilmeniz için her işlemi açık tutar.
- A oturumunda
BEGIN ISOLATION LEVEL READ COMMITTED;komutunu çalıştırın, ardından yukarıdaki üstlenme ifadesinin tamamını yapıştırın. İşlemi açık bırakın. Örnek veriler değiştirilmediyse döndürülen kimlik1olmalıdır. - A hâlâ
1kimliğini tutarken B oturumundaBEGIN ISOLATION LEVEL READ COMMITTED;komutunu çalıştırın ve aynı üstlenme ifadesini yapıştırın. B oturumu2kimliğini döndürmelidir. - B oturumunda
ROLLBACK;komutunu, ardından A oturumundaROLLBACK;komutunu çalıştırın. - Herhangi bir oturumda aşağıdaki inceleme sorgusunu çalıştırın. Her iki üstlenme işlemi de geri alındığı için üç makalenin tümü yeniden
readydurumunda olmalıdır.
SELECT id, title, state, rendered_title
FROM article_claim_demo
ORDER BY id;
Bunlar, raporlanmış bir veritabanı test çalıştırmasının sonuçları değil, belgelenmiş kilitleme ve geri alma davranışından çıkarılan beklenen sonuçlardır. Geri alma işlemi her durum güncellemesini iptal eder ve kilitleri serbest bırakır. Bunun yerine COMMIT kullanırsanız bu makaleler processing durumunda kalır ve sonraki üstlenme işlemlerinde seçilmez. Bkz. Explicit Locking ve Transactions.
Bu test, yararlı iki gözlemi birbirinden ayırır: B oturumu şu anda kilitli olan makaleyi atlayarak ilerler ve kaydedilmiş durum değişikliği makalenin sonraki üstlenme işlemlerine girmesini önler. Mevcut bir işçiye uyarlarken her iki özelliği de test edin.
node-postgres ile üç işçi çalıştırın
İki test işlemini de geri aldıktan sonra Terminal’de yeni bir yerel proje dizini oluşturun. Paketi, belgelenmiş node-postgres installation command ile yükleyin:
mkdir article-claim-demo
cd article-claim-demo
npm init -y
npm install pg
Aşağıdaki eksiksiz programı workers.mjs olarak kaydedin. Program, psql ile aynı veritabanı bağlantı ortamını kullanır; node-postgres ortak bağlantı değişkenlerini Connecting bölümünde belgeler. Her işçi bir kez satır üstlenmeyi dener, sentetik başlığı büyük harfe dönüştürür ve sonucu kaydeder.
import pg from 'pg';
const { Pool } = pg;
const pool = new Pool({ max: 3 });
const claimSQL = `
WITH candidate AS (
SELECT id
FROM article_claim_demo
WHERE state = 'ready'
ORDER BY id
LIMIT 1
FOR UPDATE SKIP LOCKED
)
UPDATE article_claim_demo AS a
SET state = 'processing'
FROM candidate AS c
WHERE a.id = c.id AND a.state = 'ready'
RETURNING a.id, a.title;
`;
async function worker(label) {
const claimed = await pool.query(claimSQL);
if (claimed.rows.length === 0) {
console.log(`${label}: no row available now`);
return;
}
const article = claimed.rows[0];
const renderedTitle = article.title.toUpperCase();
const completed = await pool.query(
`UPDATE article_claim_demo
SET rendered_title = $2, state = 'done'
WHERE id = $1 AND state = 'processing'
RETURNING id`,
[article.id, renderedTitle]
);
if (completed.rowCount !== 1) {
throw new Error(`${label}: completion did not update one row`);
}
console.log(`${label}: completed article ${article.id}`);
}
try {
const results = await Promise.allSettled([
worker('A'),
worker('B'),
worker('C')
]);
for (const result of results) {
if (result.status === 'rejected') {
console.error(result.reason);
process.exitCode = 1;
}
}
} finally {
await pool.end();
}
node workers.mjs komutunu çalıştırın, ardından psql içinde inceleme sorgusunu yeniden çalıştırın. Değiştirilmemiş üç hazır makale ve başka bir etkinlik yoksa üç durumun da done olması, rendered_title alanındaki değerlerin ise büyük harfle yazılması beklenir. İşçi etiketleri ve konsol çıktısının sırası, makale sırasıyla aynı olmak zorunda değildir.
Burada pool.query kullanmak uygundur, çünkü her üstlenme tek bir tam ifade, her tamamlama ise ayrı bir ifadedir. Havuz, bağlantıların alınmasını ve geri verilmesini yönetir; denemeler tamamlandıktan sonra pool.end() havuzu kapatır. Bkz. Pooling.
Tamamlama sorgusu, Queries belgesindeki örüntüyü izleyerek değerleri $1 ve $2 üzerinden ayrı ayrı iletir. Program, boş bir üstlenme olup olmadığını anlamak için döndürülen rows dizisini; güncellenen tamamlama satırlarının sayısını belirlemek için de pg.Result ile uyumlu olarak rowCount değerini denetler.
JavaScript, hazırlık sırasında sözdizimi denetiminden geçirildi. Araştırma ortamında PostgreSQL çalıştırılabilir dosyaları bulunmadığından SQL ve eşzamanlı veritabanı davranışı belgelere göre incelendi, ancak burada çalıştırılmadı.
Doğru işlem sınırını seçin ve tamamlanmamış işleri ele alın
Örnek, başlık dönüştürme işlemini yapmadan önce kısa bir üstlenme işlemini kaydeder. Bu, kilidin kısa süre tutulmasını sağlar ancak bilerek bir kurtarma sınırı oluşturur: Bir işçi makaleyi üstlendikten sonra tamamlamayı kaydetmeden durursa makale processing durumunda kalır. Programı yeniden çalıştırmak makaleyi tekrar üstlenmez.
Bu masaüstü örneği için tüm işçileri durdurun ve yeniden uygun hâle getirmeden önce processing durumundaki makaleleri inceleyin. Önceki bir işçi hâlâ tamamlayabilecek durumdaysa bu makaleleri otomatik olarak sıfırlamayın: örnekte, önceki denemeyi sonraki denemeden ayırt edecek bir üstlenme nesli alanı yoktur. Yinelenen üstlenmeleri önlemek tek başına otomatik kurtarma veya tam olarak bir kez işleme garantisi sağlamaz.
Örneği uyarlarken şu karar tablosunu kullanın:
| İşiniz | İşlem yaklaşımı | Sonuç |
|---|---|---|
| Bir makaleyi üstlenip ardından uygulamada daha uzun süre çalışmak | Tek ifadeli üstlenme işlemini önce kaydedin | Görünür bir processing durumu tutun ve kurtarmayı ayrıca tasarlayın |
| Küçük bir dönüşümü tamamen PostgreSQL’de gerçekleştirmek | Tek işlem içinde satırı üstlenip tamamlayın | Veritabanı değişiklikleri birlikte kaydedilebilir veya geri alınabilir |
| Node.js’te birden fazla SQL ifadesini tek işlem olarak çalıştırmak | Bir istemciyi havuzdan alın ve işlem boyunca onu kullanın | BEGIN, ifadeler ve COMMIT veya ROLLBACK boyunca aynı bağlantıyı kullanın |
Son yaklaşımda node-postgres, işlem içindeki her ifadenin aynı havuzdan alınmış istemciyi kullanmasını gerektirir. Ayrı pool.query çağrıları farklı bağlantılar kullanabilir; dolayısıyla bu çağrılar tek bir işlem oluşturamaz. İşiniz bittiğinde veya geri aldığınızda istemciyi havuza geri bırakın. Bkz. Transactions ve Pooling.
Gösterilen yalıtım varsayımlarını açıkça belirtin. Repeatable Read ve Serializable düzeylerinde, kilitlenecek bir satır işlem başladıktan sonra değiştiyse hatalar oluşabilir; bu düzeyleri kullanan uygulamaların işlem yeniden deneme mantığına ihtiyacı vardır. PostgreSQL bu sınırı Explicit Locking bölümünde açıklar.
Bu sentetik makale görevi için temel sıra açıktır: uygun bir satırı seçip kilitleyin, aynı ifade içinde durumunu değiştirin ve yalnızca üstlenme başarılı olduktan sonra döndürülen makaleyi işleyin. İki oturumda çakışmayı doğrulayın, ardından işçisi tamamlandığını hiçbir zaman kaydetmeyen bir makalenin uygulamanızda nasıl ele alınacağına karar verin.
Kaynaklar
- SELECT — PostgreSQL Global Development Group.
- UPDATE — PostgreSQL Global Development Group.
- 13.3. Explicit Locking — PostgreSQL Global Development Group.
- 13.2. Transaction Isolation — PostgreSQL Global Development Group.
- 3.4. Transactions — PostgreSQL Global Development Group.
- Release 9.5 — PostgreSQL Global Development Group.
- psql — PostgreSQL Global Development Group.
- node-postgres: Introduction and Install — node-postgres.
- Connecting — node-postgres.
- Pooling — node-postgres.
- Transactions — node-postgres.
- Queries — node-postgres.
- pg.Result — node-postgres.