CouchDB'de işlem ve kilit yapabilir miyim?


81

İşlemler yapmam gerekiyor (başla, kaydet veya geri al), kilitler (güncelleme için seçin). Bunu bir belge modeli db'de nasıl yapabilirim?

Düzenle:

Durum şudur:

  • Bir müzayede sitesi işletmek istiyorum.
  • Ve satın almayı nasıl yönlendireceğimi de düşünüyorum.
  • Doğrudan satın alımda, kalem kaydındaki miktar alanını azaltmam gerekir, ancak yalnızca miktar sıfırdan büyükse. Bu yüzden kilitlere ve işlemlere ihtiyacım var.
  • Kilit ve / veya işlem olmadan bunu nasıl çözeceğimi bilmiyorum.

Bunu CouchDB ile çözebilir miyim?

Yanıtlar:


145

Hayır. CouchDB "iyimser eşzamanlılık" modeli kullanır. En basit ifadeyle, bu sadece güncellemenizle birlikte bir belge sürümü gönderdiğiniz anlamına gelir ve CouchDB, geçerli belge sürümü gönderdiğinizle eşleşmezse değişikliği reddeder.

Gerçekten aldatıcı derecede basit. CouchDB için birçok normal işlem tabanlı senaryoyu yeniden çerçeveleyebilirsiniz. Yine de, CouchDB'yi öğrenirken RDBMS alan bilginizi atmanız gerekir. Couch'ı SQL tabanlı bir dünyaya dönüştürmeye çalışmak yerine problemlere daha yüksek bir seviyeden yaklaşmak yararlıdır.

Envanter takibi

Anlattığınız sorun öncelikle bir envanter sorunudur. Bir öğeyi açıklayan bir belgeniz varsa ve "mevcut miktar" için bir alan içeriyorsa, eşzamanlılık sorunlarını şu şekilde halledebilirsiniz:

  1. Belgeyi alın, _revCouchDB'nin gönderdiği özelliği not alın
  2. Sıfırdan büyükse miktar alanını azaltın
  3. _revÖzelliği kullanarak güncellenmiş belgeyi geri gönderin
  4. Eğer _revşu anda depolanmış numarayı maçları yapılabilir!
  5. Bir çakışma varsa ( _reveşleşmediğinde), en yeni belge sürümünü alın

Bu durumda, üzerinde düşünülmesi gereken iki olası arıza senaryosu vardır. En son belge sürümünün miktarı 0 ise, bunu tıpkı bir RDBMS'de yaptığınız gibi ele alın ve kullanıcıyı satın almak istediklerini gerçekten satın alamayacakları konusunda uyarırsınız. En son belge sürümünün miktarı 0'dan büyükse, işlemi güncellenmiş verilerle tekrarlayın ve en baştan başlayın. Bu, sizi bir RDBMS'den biraz daha fazla iş yapmaya zorlar ve sık sık çakışan güncellemeler varsa biraz can sıkıcı olabilir.

Şimdi, az önce verdiğim cevap, bir RDBMS'de yaptığınız gibi CouchDB'de de şeyler yapacağınızı öngörüyor. Bu soruna biraz farklı yaklaşabilirim:

Tüm tanımlayıcı verileri (ad, resim, açıklama, fiyat, vb.) İçeren bir "ana ürün" belgesiyle başlayacağım. Daha sonra, her bir örnek için product_keyve alanları olan bir "envanter bileti" belgesi eklerdim claimed_by. Eğer çekiç bir model satan ve satış için 20 tanesi var ediyorsanız gibi tuşlarıyla da belgeler olabilir hammer-1, hammer-2mevcut her çekiç temsil etmek, vb.

Daha sonra, bana bir "toplam" görmeme izin veren azaltma işleviyle birlikte mevcut çekiçlerin bir listesini veren bir görünüm oluşturdum. Bunlar tamamen yanlıştır, ancak size çalışma görünümünün nasıl görüneceği konusunda bir fikir vermelidir.

Harita

function(doc) 
{ 
    if (doc.type == 'inventory_ticket' && doc.claimed_by == null ) { 
        emit(doc.product_key, { 'inventory_ticket' :doc.id, '_rev' : doc._rev }); 
    } 
}

Bu bana ürün anahtarına göre mevcut "biletlerin" bir listesini veriyor. Biri bir çekiç satın almak istediğinde bunlardan bir grup alabilirim, ardından başarılı bir şekilde bir tane talep edene kadar güncellemeleri göndererek ( idve kullanarak _rev) yineleyebilirim (önceden talep edilen biletler bir güncelleme hatasıyla sonuçlanacaktır).

Azalt

function (keys, values, combine) {
    return values.length;
}

Bu azaltma işlevi, yalnızca sahip olunmayan inventory_ticketöğelerin toplam sayısını döndürür , böylece satın alınabilecek kaç "çekiç" olduğunu anlayabilirsiniz.

Uyarılar

Bu çözüm, sunduğunuz belirli sorun için kabaca 3,5 dakikalık toplam düşünmeyi temsil eder. Bunu yapmanın daha iyi yolları olabilir! Bununla birlikte, çakışan güncellemeleri önemli ölçüde azaltır ve yeni bir güncellemeyle bir çatışmaya yanıt verme ihtiyacını azaltır. Bu model altında, birincil ürün girişindeki verileri değiştirmeye çalışan birden fazla kullanıcınız olmayacak. En kötüsü, tek bir bilet talep etmeye çalışan birden fazla kullanıcı olacak ve bunlardan birkaçını sizin görüşünüzden yakaladıysanız, bir sonraki bilete geçip tekrar deneyin.

Referans: https://wiki.apache.org/couchdb/Frequently_asked_questions#How_do_I_use_transactions_with_CouchDB.3F


4
Sırayla talep etmeye çalıştığınız 'biletlere' sahip olmanın, ana varlığı güncellemek için oku / değiştir / yazmayı yeniden denemeye kıyasla ne kadar önemli bir gelişme olduğu benim için açık değil. Kesinlikle, özellikle de büyük miktarlarda stoğunuz varsa, fazladan ek yüke değmez.
Nick Johnson

4
Benim bakış açıma göre, bilet konvansiyonunun oluşturulması "daha basit". Ana girişteki başarısız güncellemeler, belgeyi yeniden yüklemenizi, işleminizi yeniden gerçekleştirmenizi ve ardından kaydetmenizi gerektirir. Bilet olayı, daha fazla veri talep etmek zorunda kalmadan bir şeyi denemenizi ve "talep etmenizi" sağlar.
MrKurt

Ayrıca, ne tür bir ek yük için endişelendiğinize de bağlıdır. Ya artan çekişme ile savaşacaksınız ya da ek depolama gereksinimlerine sahip olacaksınız. Bir biletin satın alma kaydı olarak da ikiye katlanabileceği göz önüne alındığında, düşündüğünüz kadar depolama sorunu olacağını bilmiyorum.
MrKurt

2
Bir ürün belgesinin miktar alanını düzenliyorum. O zaman örneğin miktar = 2K ise binlerce "bilet" oluşturmam gerekir. Daha sonra miktarı azaltıyorum, bazı biletleri silmem gerekiyor. Bana tamamen huzursuz geliyor. Temel kullanım durumlarında çok fazla baş ağrısı. Belki bir şeyi kaçırıyorum, ama neden önceden kaldırılmış işlem davranışını geri getirmiyorsunuz, _bulk_docs? Reject_on_conflict = true gibi bir şeyle isteğe bağlı yapın. Tek ana konfigürasyonlarda oldukça kullanışlıdır.
Sam

3
@mehaase: Bunu okuyun: guide.couchdb.org/draft/recipes.html , cevap couchdb'nin dahili veri yapısına indirgeniyor "asla verileri değiştirmezsiniz, sadece yenisini eklersiniz ". Senaryonuzda bu, borç için hesaptan geçiş hesabına bir (atomik) işlem ve transit hesaptan ileri (veya geri) ikinci bir (atomik) işlem oluşturmak anlamına gelir. Gerçek bankalar bunu böyle yapar. Her adım her zaman belgelenir.
Fabian Zeindl

26

MrKurt'un cevabını genişletiyor. Pek çok senaryo için, stok biletlerinin sırayla kullanılması gerekmez. İlk bileti seçmek yerine kalan biletlerden rastgele seçim yapabilirsiniz. Çok sayıda bilet ve çok sayıda eşzamanlı istek verildiğinde, ilk bileti almaya çalışan herkese karşı bu biletlerle ilgili çok daha az çekişme elde edeceksiniz.


21

Yeniden doldurulan işlemler için bir tasarım modeli, sistemde bir "gerilim" yaratmaktır. Bir banka hesabı işleminin popüler kullanım örneği için, ilgili her iki hesap için toplamı güncellediğinizden emin olmalısınız:

  • "11223 hesabından 88733 hesabına 10 ABD Dolarını transfer et" işlem belgesi oluşturun. Bu, sistemdeki gerilimi yaratır.
  • Tüm işlem belgeleri için herhangi bir gerginlik taramasını çözmek için ve
    • Kaynak hesap henüz güncellenmemişse, kaynak hesabı güncelleyin (-10 USD)
    • Kaynak hesap güncellenmişse ancak işlem belgesi bunu göstermiyorsa, işlem belgesini güncelleyin (örneğin, belgede "kaynak bir" işaretini ayarlayın)
    • Hedef hesap henüz güncellenmemişse, hedef hesabı güncelleyin (+10 USD)
    • Hedef hesap güncellendiyse ancak işlem belgesi bunu göstermiyorsa işlem belgesini güncelleyin
    • Her iki hesap da güncellendiyse, işlem belgesini silebilir veya denetim için saklayabilirsiniz.

Gerilim taraması, sistemdeki gerilim sürelerini kısa tutmak için tüm "gerilim belgeleri" için bir arka uç sürecinde yapılmalıdır. Yukarıdaki örnekte, ilk hesap güncellendiğinde ancak ikincisi henüz güncellenmediğinde kısa bir süre beklenen tutarsızlık olacaktır. Bu, Couchdb'niz dağıtılırsa nihai tutarlılıkla başa çıkacağınız şekilde dikkate alınmalıdır.

Bir başka olası uygulama, işlem ihtiyacını tamamen ortadan kaldırır: sadece gerilim belgelerini saklayın ve ilgili her gerilim belgesini değerlendirerek sisteminizin durumunu değerlendirin. Yukarıdaki örnekte bu, bir hesabın toplamının yalnızca bu hesabın dahil olduğu işlem belgelerindeki toplam değerler olarak belirlendiği anlamına gelir. Couchdb'de bunu bir harita / küçültme görünümü olarak çok güzel bir şekilde modelleyebilirsiniz.


5
Peki ya hesabın borçlandırıldığı ancak gerginlik belgesinin değişmediği durumlar? Bu iki nokta arasındaki herhangi bir başarısızlık senaryosu, atomik değilse kalıcı tutarsızlığa neden olur, değil mi? Süreçle ilgili bir şey atomik olmalı, işlemin amacı budur.
Ian Varley

Evet, haklısınız, bu durumda - gerginlik çözülmezken - tutarsızlık olacaktır. Ancak tutarsızlık, yalnızca bir sonraki gerilim belgeleri taraması bunu algılayana kadar geçicidir. Bu, zamanla ilgili bir tür nihai tutarlılığın değiş tokuşudur. Önce kaynak hesabını çözdüğünüz ve daha sonra hedef hesabı artırdığınız sürece bu kabul edilebilir. Ancak dikkatli olun: gerginlik belgeleri size REST'in yanı sıra ASİT işlemleri vermez. Ancak saf DİNLENME ve ASİT arasında iyi bir değiş tokuş olabilirler.
ordnungswidrig

4
Her gerginlik belgesinin bir zaman damgası olduğunu ve hesap belgelerinde 'son gerilim uygulanmış' bir alan veya uygulanan gerilimlerin bir listesi olduğunu hayal edin. Kaynak hesabı borçlandırdığınızda, 'son gerilim uygulanan' alanını da güncellersiniz. Bu iki işlem atomiktir çünkü aynı belgede yer alırlar. Hedef hesabın da benzer bir alanı vardır. Bu şekilde, sistem her zaman hangi gerilim belgelerinin hangi hesaplara uygulandığını söyleyebilir.
Jesse Hallett

1
Kaynak / hedef belgenin zaten güncel olup olmadığı nasıl tespit edilir? Ya 1. adımdan sonra başarısız olursa, yeniden çalıştırılır ve tekrar başarısız olursa ve bu şekilde devam ederse, kaynak hesabı düşmeye devam edersiniz?
wump

1
@wump: gerginlik belgesinin hesaba uygulandığını kaydetmeniz gerekecek. örneğin, gerginlik belgesi kimliğini herhangi bir hesabın liste özelliğine ekleyerek. gerginlik belgesinin dokunduğu tüm hesaplar güncellendiğinde, gerginlik belgesini "tamamlandı" olarak işaretleyin veya silin. Daha sonra belge kimliği tüm hesaplar için listeden çıkarılabilir.
ordnungswidrig

6

Hayır, CouchDB genellikle işlem uygulamaları için uygun değildir çünkü kümelenmiş / çoğaltılmış bir ortamda atomik işlemleri desteklemez.

CouchDB, işlem yeteneğini ölçeklenebilirlik adına feda etti. Atomik işlemlere sahip olmak için, ölçeklenebilirliğinizi sınırlayan merkezi bir koordinasyon sistemine ihtiyacınız var.

Yalnızca bir CouchDB örneğiniz olduğunu veya belirli bir belgeyi değiştiren herkesin aynı CouchDB örneğine bağlandığını garanti edebiliyorsanız, yukarıda açıklanan yöntemleri kullanarak bir tür atomiklik oluşturmak için çakışma algılama sistemini kullanabilirsiniz, ancak daha sonra bir kümeye ölçeklendirirseniz veya Cloudant gibi barındırılan bir hizmeti kullanın, bu bozulacak ve sistemin bu bölümünü yeniden yapmanız gerekecek.

Bu yüzden benim önerim hesap bakiyeleriniz için CouchDB dışında bir şey kullanmanız olacaktır, bu şekilde çok daha kolay olacaktır.


5

OP'nin sorununa bir yanıt olarak, Couch muhtemelen burada en iyi seçenek değil. Görünümleri kullanmak, envanteri takip etmenin harika bir yoludur, ancak 0'a sınırlamak aşağı yukarı imkansızdır. Sorun, bir görünümün sonucunu okuduğunuzda, bir "çekiç-1" öğesini kullanmaya karar verdiğinizde ve ardından onu kullanmak için bir belge yazarken yarış koşuludur. Sorun şu ki, eğer görüşün sonucu> 0 çekiç-1 varsa, sadece çekiç kullanmak için dokümanı yazmanın atomik bir yolu yoktur. 100 kullanıcının tümü aynı anda görünümü sorgulayıp 1 çekiç-1 görürse, hepsi çekiç 1 kullanmak için bir belge yazabilir ve sonuçta -99 çekiç-1 elde edilir. Uygulamada, yarış durumu oldukça küçük olacaktır - DB'niz localhost çalıştırıyorsa gerçekten küçük. Ancak ölçeklendirdiğinizde ve site dışı bir DB sunucusuna veya kümesine sahip olduğunuzda, sorun çok daha belirgin hale gelecektir.

MrKurt'un yanıtına yönelik bir güncelleme (yalnızca tarihli olabilir veya bazı CouchDB özelliklerinden habersiz olabilir)

Görünüm, CouchDB'deki bakiye / envanter gibi şeyleri ele almanın iyi bir yoludur.

Bir görünümde docid ve rev yayınlamanıza gerek yoktur. Görünüm sonuçlarını aldığınızda ikisini de ücretsiz olarak alırsınız. Bunları yaymak - özellikle sözlük gibi ayrıntılı bir biçimde - görüşünüzü gereksiz yere büyütür.

Envanter bakiyelerini izlemek için basit bir görünüm daha çok şuna benzemelidir (ayrıca aklıma gelmez)

function( doc )
{
    if( doc.InventoryChange != undefined ) {
        for( product_key in doc.InventoryChange ) {
            emit( product_key, 1 );
        }
    }
}

Ve azaltma işlevi daha da basittir

_sum

Bu, yerleşik bir azaltma işlevi kullanır , yalnızca eşleşen anahtarlarla tüm satırların değerlerini toplayan .

Bu görünümde, herhangi bir doküman, product_key'leri toplam envanterindeki bir değişiklikle eşleştiren bir "Envanter Değişimi" üyesine sahip olabilir. yani.

{
    "_id": "abc123",
    "InventoryChange": {
         "hammer_1234": 10,
         "saw_4321": 25
     }
}

10 çekiç_1234 ve 25 testere_4321 ekler.

{
    "_id": "def456",
    "InventoryChange": {
        "hammer_1234": -5
    }
}

Envanterden 5 çekiç yakar.

Bu modelle, hiçbir veriyi asla güncellemiyorsunuz, sadece ekliyorsunuz. Bu, güncelleme çakışmaları için fırsat olmadığı anlamına gelir. Veri güncellemeyle ilgili tüm işlem sorunları ortadan kalkar :)

Bu modelle ilgili bir başka güzel şey de, DB'deki HERHANGİ bir belgenin envantere hem ekleyip hem de envanterden ürün çıkarabilmesidir. Bu belgelerde her türlü başka veri bulunabilir. Alınan tarih ve saat, depo, alıcı çalışan vb. Hakkında bir sürü veri içeren bir "Gönderi" belgeniz olabilir ve bu belge bir Envanter Değişikliği tanımladığı sürece envanteri güncelleyecektir. Bir "Satış" belgesi ve bir "Hasarlı Ürün" belgesi vb. Olabilir. Her belgeye bakıldığında, çok net bir şekilde okurlar. Ve görünüm tüm zor işi halleder.


İlginç strateji. Bir CouchDB yenisi olarak, mevcut çekiç sayısını hesaplamak için, şirketin çekiçler için envanter değişikliklerinin tüm geçmişinde bir harita yapmanız / küçültmeniz gerekir . Bu yıllarca değişiklik olabilir. CouchDB'nin bu performansa sahip olmasını sağlayacak yerleşik bir özelliği var mı?
chadrik

Evet, CouchDB'deki görünümler sürekli, kalıcı bir harita / küçültme gibidir. Bunu büyük bir veri kümesinde sıfırdan yapmanın yaş alacağı konusunda haklısınız, ancak yeni belgeler eklendiğinde yalnızca mevcut görünümü güncellerler, tüm görünümü yeniden hesaplaması gerekmez. Görünümler için hem alan hem de CPU gereksinimi olduğunu unutmayın. Ayrıca, en azından CouchDB ile profesyonel olarak çalıştığımda (birkaç yıl oldu), yalnızca yerleşik azaltma işlevlerini kullanmak oldukça önemliydi yani. _sum. Özel Javascript küçültme işlevleri son derece yavaştı
wallacer

3

Aslında bir şekilde yapabilirsiniz. HTTP Belge API'sına bir göz atın ve "Birden Çok Belgeyi Tek Bir İstekle Değiştirin" başlığına gidin.

Temel olarak, URI / {dbname} / _ bulk_docs için tek bir gönderi isteğinde bir grup belge oluşturabilir / güncelleyebilir / silebilirsiniz ve hepsi başarılı olur veya tümü başarısız olur. Belge, bu davranışın gelecekte değişebileceği konusunda uyarıyor.

DÜZENLEME: Beklendiği gibi, sürüm 0.9'dan itibaren toplu dokümanlar artık bu şekilde çalışmamaktadır.


Bu, tartışılan durumda, yani birden çok kullanıcıdan gelen tek dokümanlar üzerindeki çekişmede gerçekten yardımcı olmaz.
Kerr 04

3
CouchDB 0.9'dan başlayarak, toplu güncellemelerin anlamı değişti.
Barry Wark

0

İşlemler için sadece SQlite türü hafif bir çözüm kullanın ve işlem başarıyla tamamlandığında bunu çoğaltın ve SQLite'da çoğaltıldığını işaretleyin

SQLite tablosu

txn_id    , txn_attribute1, txn_attribute2,......,txn_status
dhwdhwu$sg1   x                    y               added/replicated

Başarıyla çoğaltılan işlemleri de silebilirsiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.