- cola, Rust ile yazılmış, gerçek zamanlı ortak düzenleme için bir metin CRDT’sidir; merkezi bir koordinatör olmadan birden fazla eşin aynı anda düzenlediği belgeleri aynı duruma yakınsatmaya odaklanır
- Belge konumlarını basit ofsetler yerine
ReplicaId.nbiçimindeki Anchor ile sabitleyerek, eşzamanlı düzenleme sırasında konum anlamının kayması sorununu önler - Ekleme çakışmaları Lamport timestamp ve
ReplicaIdsıralamasıyla tutarlı biçimde çözülür; silme işlemleri ise gerçek kaldırma yerine tombstone ve version vector ile birleştirme koşullarını yönetir - Uygulama tarafında, Rust’ın güvenli sahiplik modeli içinde logaritmik zamanlı arama ve ekleme hedefiyle
EditRuntabanlı RLE ve Vec indeksleriyle ifade edilen bir G-tree kullanılır - Benchmark’larda cola,
diamond-types,automerge,yrsile karşılaştırıldı; upstream’dediamond-types’tan 1.4 ila 2 kat daha hızlıyken downstream’de de çok yüksek performans gösterdi
cola’nın ele aldığı ortak düzenleme problemi
- cola, Rust ile yazılmış bir metin CRDT’sidir ve gerçek zamanlı ortak düzenlemede merkezi bir otorite olmadan birden fazla kopyanın aynı duruma yakınsaması için tasarlanmıştır
- Ağ katmanında yalnızca, tüm düzenlemelerin eninde sonunda tüm eşlere ulaşacağı yönünde asgari bir varsayım yapılır
- Düzenlemeler birden fazla kez iletilebilir
- Düzenlemeler rastgele sırayla gelebilir
- Tüm eşler eninde sonunda tüm düzenlemeleri almalıdır
- Amaç, tüm düzenlemeler alındıktan sonra belge durumlarının aynı olması kadar, kullanıcı açısından ortaya çıkan belgenin de makul görünmesidir
Ofset yerine Anchor ile konumu sabitleme
insert "abc" at offset 8gibi ofset tabanlı düzenlemeler, başka bir eş ön taraftaki bölgeyi aynı anda düzenlediğinde anlam değiştirebilir ve kopyaların ayrışmasına yol açabilir- cola, belge konumlarını karakter içeriği ya da ofset ile değil, kararlı tanımlayıcılarla referans alır
ndeğeri yerel sayaç artışıyla benzersiz kılınabilir, ancak merkezi sunucu olmadanReplicaIdiçin küresel benzersizlik garantisi vermek zordur- cola, UUID benzeri şekilde çakışma olasılığı ihmal edilebilir kadar düşük olan büyük rastgele tamsayılar kullanılacağını varsayar
- Ekleme işlemi konumu tek bir Anchor ile belirtir; silme işlemi ise aralığı başlangıç ve bitiş olmak üzere iki Anchor ile belirtir
Ekleme çakışmalarını aynı sırada sıralama
- Aynı Anchor’a birden fazla ekleme gelirse cola, sıralamayı Lamport timestamp ile belirler
- Lamport saati şu kurallarla güncellenir
- Yerelde metin eklenirken saat 1 artırılır
- Uzak bir ekleme alındığında
max(current, remote_timestamp) + 1olarak ayarlanır
- Herhangi bir
Aeklemesi,Beklemesini zaten birleştirmiş bir ortamda üretildiyse,A’nın Lamport timestamp’iB’den büyük olur - Çakışan eklemeler, Lamport timestamp azalan sırayla sıralanır
- Anchor ve Lamport timestamp’in de aynı olduğu eşzamanlı eklemelerde, kullanıcı açısından daha doğru bir sıra bulunmadığından, eşler arası tutarlılık için
ReplicaIdartan sırayla sıralama yapılır
Silme işlemleri tombstone ve version vector ile ele alınıyor
- Silme işlemleri, başlangıç ve bitiş ofsetleri Anchor’a çevrildikten sonra diğer eşlere yayılır
- İki eş aynı bölgeyi aynı anda silerse, undo hesaba katılmadığında sonuç tek kez silinmiş olmasıyla aynıdır
- cola şu anda silme undo’sunu desteklemiyor
- Silme işlemlerinin ele alınmasında üç zorluk vardır
- Silinen metin tamamen kaldırılırsa, henüz ulaşmamış düzenlemelerin Anchor’ları o bölgenin içinde olabilir
- Uzak bir silme işlemi çok erken birleştirilirse, silmeyi üreten eşin gördüğü içerik ile alıcı eşin içeriği farklı olabilir ve kopyalar ayrışabilir
- Silme aralığı içinde olsa bile, silmeyi üreten eşin henüz görmediği karakterler silinmemelidir
- İlk sorun tombstone ile çözülür
- Silinen karakterler belgede kalır ancak silinmiş olarak işaretlenir
- Bu yaklaşım bellek kullanımını artırır
- Kalan sorunlar, silme mesajına version vector eklenerek çözülür
- Anahtar
ReplicaId, değer ise silmeyi üreten eşin o anda gördüğü son karakter timestamp’idir - Alıcı eşin version vector’ü, silme mesajındaki version vector’ün seviyesine ulaşana kadar silmenin birleştirilmesi bekletilir
- Silme version vector’ündeki timestamp’ten büyük karakterler silme hedefinden atlanır
- Anahtar
- cola’da version vector yerine version map adı kullanılır
Replica ile metin tamponunun ayrılması
- Her eşin yerel belge durumu cola’da
Replicaile ifade edilir - cola’nın CRDT algoritmasının gerçek dize içeriğini bilmesine gerek yoktur
- API işlevleri dizeyi argüman olarak almaz
- cola, belge içeriğiyle değil yalnızca sayısal bloklarla çalışır
- Bu tasarım, CRDT mekanizmasını gerçek metin tamponu uygulamasından ayırır
RLE ve EditRun ile meta veriyi azaltma
- Her karaktere meta veri eklemek yüksek performanslı bir uygulama geliştirmeyi zorlaştırdığı için cola, ardışık timestamp’lere sahip blokları run-length encoding ile birleştirir
- Örneğin Wikipedia’daki Manhattan Project sayfasının tamamı yapıştırıldığında 107.000 blok yerine tek bir blok olarak temsil edilebilir
- İmleç taşınmadan veya silme yapılmadan bir cümle karakter karakter yazıldığında da, her tuş vuruşu için ayrı blok oluşturmak yerine tek bir run olarak temsil edilebilir
- cola’da bu tür ardışık bloklara
EditRundenir- Bir kez bölünmüş bir
EditRun, belge ömrü boyunca sabit kalır ve artık genişlemez - Henüz bölünmemiş run’lar active durumdadır
- Bir kez bölünmüş bir
- Var olan bir
EditRun’ın ortasına metin eklenirse run ikiye bölünür ve yeni metin araya yerleştirilir - Metin silinirse ilgili bölüm run’dan ayrılır ve tombstone olarak işaretlenir
- Silinmiş run’lar da aynı şekilde RLE ile temsil edilebildiği için tombstone’un bellek yükü azaltılabilir
Bağlı listeden B-tree’ye
- Yerel düzenlemelerin upstream yolu, ofset tabanlı düzenlemeleri diğer eşlere gönderilebilir düzenlemelere dönüştürme sürecidir
- Hem ekleme hem silme için, ofseti içeren run bulunmalı ve Anchor oluşturulmalıdır
- Gerekirse run’lar eklemede en fazla 2, silmede ise en fazla 3 parçaya bölünür
- Bağlı listede run bölme
O(1)olsa da, ofseti içeren run’ı bulmak için baştan tarama gerektiğinden doğrusal zaman gerekir - Mevcut active run ve onun ofseti önbelleğe alınırsa aynı konumda tekrar eden düzenlemeler hızlı olabilir, ancak en kötü durum yine doğrusaldır
- cola, en kötü durumda logaritmik zaman performansı için B-tree yapısını değerlendirir
- Run’lar B-tree’nin yaprakları olarak ifade edilir
- inode’lar çocukları ve çocuk uzunluklarının toplamını saklar
- tombstone run’lar uzunluğa 0 olarak katkı verir
- root’un uzunluğu tüm belge uzunluğuna eşittir
- B-tree, arama ve eklemeyi
O(log n)sürede yapabildiği için yerel düzenlemeler logaritmik zamanda işlenebilir - Ancak uzak düzenlemelerde
Anchor -> rundönüşümünü verimli yapmak için Anchor’ın bulunduğu yaprağın bulunması gerekir
G-tree: Vec indeksleriyle ifade edilen B-tree
- Sıradan bir B-tree yaprağına giden işaretçiye sahip olmak, top-down ekleme sürecinde yaprağa nasıl inileceğini anlamak için yeterli değildir
- Bottom-up işlemlerde her düğüm için ebeveyn işaretçisi gerekir; Rust’ın güvenli sahiplik modelinde bu,
Rc<RefCell<_>>gibi yapılar gerektirebilir ve yavaşlayıp karmaşıklaşabilir - cola, tüm düğümleri
Vecgibi dinamik bir dizide tutar ve düğümler arası referansları işaretçi yerine indeks ile ifade eder- Vektör tüm düğümlerin sahibidir
- Her düğüm ebeveyn indeksini tutabilir
unsafekod kullanmadan çift yönlü gezinme mümkündür
- Bu yapı, düğüm indekslerinin değişmemesi varsayımına dayanır
- Yeni düğümler vektörün sonuna append edilir
- cola, run’ları kaldırmak yerine tombstone ile işaretlediği için indeks geçersizleşmesi yaşanmaz
- cola, vektör içindeki bu yalnızca büyüyen ağaç yapısına G-tree adını verir
- G-tree Rust kodu, B-tree’nin ebeveyn-çocuk yapısını korurken yalnızca bellek temsilini değiştiren bir biçimdedir
G-tree’nin oluşturduğu performans özellikleri
- G-tree, B-tree gibi top-down arama ve eklemeyi logaritmik zamanda gerçekleştirir
- Yaprağın vektör indeksi olan
LeafIdx, kararlı bir yaprak tanımlayıcısı olarak kullanılabilir - Mevcut active run, işaretçi yerine
LeafIdxile önbelleğe alınabilir- Aynı imleç konumunda tekrar eden düzenlemelerde active run genişletilir ve yalnızca ataların uzunlukları root’a kadar güncellenir
- Bu işlem, ağaç dolaşımı ve yeni tahsis gerektirmeden birkaç tamsayı karşılaştırması ve 2-4 tamsayı toplamasıyla yapılabilir
- cola’nın G-tree’si 32 dallanma faktörü kullanır ve inode ortalama doluluğu yaklaşık 20 çocuktur
- Yalnızca 4 seviye ile genellikle yaklaşık 160 bin farklı
EditRunsaklanabilir automerge-paperdüzenleme trace’i işlendiğinde cola’nın G-tree’si yaklaşık 15 binEditRuniçerir- Söz konusu trace 260 bin düzenleme içerir ve birkaç güne yayılarak kaydedilmiştir
- Yalnızca 4 seviye ile genellikle yaklaşık 160 bin farklı
- G-tree’de düğümler zaten doğrusal bellekte tutulduğu için serileştirme ve serileştirmeden geri yükleme de basittir
Anchor’ı LeafIdx’e dönüştürme
- Uzak düzenlemelerin downstream yolu,
insert 2.3..7 at 1.2,delete between 3.4 and 2.2gibi Anchor tabanlı düzenlemeleri yerel belge ofset düzenlemelerine dönüştürme sürecidir - G-tree yukarı ve aşağı yönde gezinebildiği için, bir Anchor’ı içeren run’ın
LeafIdxdeğeri biliniyorsa ilgili ofset hesaplanabilir - Kilit nokta Anchor -> LeafIdx dönüşümüdür
- Basit tasarımda yardımcı bir G-tree veya B-tree tutulur; yapraklarda
ReplicaId, zamansal aralık ve ana G-tree’dekiLeafIdxsaklanır- Yapraklar
ReplicaIdve zamansal aralık sırasına göre tam sıralıdır - Anchor temelinde ağaç aşağı inilerek istenen
LeafIdxbulunabilir - Arama ve ekleme işlemleri de
O(log n)’dir
- Yapraklar
- Gerçek cola kaynak kodu bu yardımcı G-tree yaklaşımını birebir kullanmaz
- Gerçek uygulamada arama ve ekleme
O(log f)’dir - Burada
f, Anchor’ı içerenEditRun’ın zaman içinde bölündüğü fragment sayısıdır fher zamann’den küçüktür ya da eşittir ve çoğu zaman çok daha küçüktür
- Gerçek uygulamada arama ve ekleme
Mevcut olgunluk seviyesi ve kalan işler
- cola’nın tasarımı, yakınsama, niyet koruma ve performansı hedefleyen bir temel sunar
- Üretime hazır hale gelmesi için undo/redo desteği ve birkaç ek çalışma daha gereklidir
Rust CRDT benchmark’ları
- cola, Rust tabanlı 3 CRDT ile karşılaştırıldı
- Benchmark’lar, gerçek character-by-character düzenleme trace’lerini işleme süresini ölçer
- Rust benchmark kütüphanesi criterion kullanılır
- Hem upstream hem downstream yönü ölçülür
- Benchmark kodu crdt-benches deposunda yer alır
- Grafikler, cola performansının 100 katını referans çizgisi olarak alır; cola’dan 100 kattan daha yavaş ölçümler gösterilmez
- Çalışma ortamı 2018 MacBook Pro, 2.2GHz 6-Core Intel Core i7’dir
- Farklı makinelerde sayılar değişebilir, ancak göreli performansın benzer olacağı düşünülür
Benchmark sonuçları
- Upstream yönünde
yrsveautomergereferans çizgisini aşıyor - Upstream’de cola,
diamond-types’tan 1.4 ila 2 kat daha hızlı - Downstream yönünde
diamond-types, tüm trace’lerde crash olduğu için ölçüm sonucu alınamıyor- Kütüphanenin yanlış kullanılmış olması durumunda sonuçların güncelleneceği notu eklenmiş
- Downstream’de cola önceye göre yaklaşık 2 kat daha yavaş
- Uzak düzenlemeleri birleştirmenin, genel olarak yerel düzenleme üretmekten daha maliyetli olması nedeniyle bu beklenen bir sonuç
- cola, her iki yönde de hızlı rope kütüphaneleriyle benzer ya da daha iyi düzeyde çalışıyor ve şu anda en hızlı metin CRDT uygulaması olarak değerlendiriliyor
1 yorum
Hacker News yorumları
G-tree’nin hâlâ ebeveyn işaretçisi eklenmiş bir B-tree olduğunu düşünüyorum
Dizide saklanması yalnızca bir temsil meselesi; yapıyı temelden değiştirmiyor
Hâlâ işaretçi saklıyor, sadece bayt birimleriyle değil düğüm boyutu birimleriyle; adres alanının başlangıcına göre değil, ilk dizi elemanına göre göreli konumlar olarak
Örneğin açık referanslar olmadan bir dizide saklanan tam ikili ağaç da, indeks x’in çocuklarının 2x + 1 ve 2x + 2’de olduğu örtük bir temsilden ibaret olsa da hâlâ ikili ağaç diye adlandırılır
Özellikle mutlak referanslar yerine göreli ve kendi kendini yöneten referanslara yönlendiren dillerde daha da öyle
İlginç bulduğum ağaç temsil biçimlerinden biri, düğümleri derinlik öncelikli dolaşım sırasıyla düz bir dizide saklamak
Salt okunur olduğunda ve okuyucu zaten derinlik öncelikli dolaşacaksa oldukça verimli olabilir
Aklıma S-expression ve HTML geliyor
API’de gözden kaçırmadıysam, bu kalın/italik gibi biçimlendirme aralıklarını desteklemiyor gibi görünüyor
Bildiğim kadarıyla zengin metin CRDT algoritmalarında hâlâ en ileri nokta Peritext https://www.inkandswitch.com/peritext/
Bu projenin Peritext algoritmasının zengin metin özelliklerini de alması iyi olurdu
Kullanıcının anlamsal olarak geçerli bir durumu tarif eden bir model veya şema tanımladığı bir yaklaşım
Örneğin JSON’u doğrudan birleştirirseniz sözdizimsel olarak geçerli, ama anlamsal olarak saçma bir durum ortaya çıkabilir
Peritext algoritmasının kalın/italik/alt çizginin eklemeli işlemler olduğunu, vurgulama renginin ise olmadığını bilmesi gibi; kullanıcının şemada
state: notStartedilecompletionDate: 2023-09-04değerlerinin bir arada olamayacağını ilan edebilmesi güzel olurdu diye düşünüyorumO hâlde HTML’de olduğu gibi biçimlendirmenin kendisini metnin içinde ifade etmek mümkün olmaz mı?
Diğer zengin metin temsillerini iyi bildiğim söylenemez, o yüzden emin değilim
Ayrıca yukarıda bağlantısı verilen Peritext belgesine bakınca tam da RTF’ye özgü bu zorlukları ele aldığını gördüm ve oldukça ilginç buldum
Automerge veya Y.js/Yrs ile karşılaştırıldığında performans ya da özellik farkları var mı?
Bu cola kütüphanesi işlem hızı açısından epey avantajlı görünüyor
Bellek kullanımı da merak konusu
Ian Piumarta’nın aynı adlı çalışmasıyla karıştırılmamalı
https://www.piumarta.com/software/cola/
https://citeseerx.ist.psu.edu/viewdoc/download;jsessionid=91...
İyi yapılmış bir çalışma, ama adil bir benchmark gibi görünmüyor
İşlemleri hesaplayıp saklamıyor, gerçek metni de saklamıyor
Bunun üzerine delta güncellemelerini destekleyen bir metin CRDT’si yapmak için kullanıcının ayrı bir yapıda
OpID => Textsaklaması gerekir; bunun maliyeti de düşük değilslotmapcrate’i (https://docs.rs/slotmap/latest/slotmap/) kullanılırsa indeks kayması ya da bir “indeksin” başka bir değeri göstermesi endişesi olmadan silme desteklenebilirslotmap’te buna anahtar deniyor ve anahtarın içinde sürüm numarası da bulunuyor
Sıralama belirlemek zor, sürüm de yalnızca yerelde anlamlı olabilir diye merak ediyorum
Etherpad ve Word’ün kaldıramadığı bir projede bunu denemeyi düşünüyorum
Bağlı tüm istemcilerin tüm düzenlemeleri aldığını varsayarsak, ofset ve ekle/sil/değiştir komutlarının önüne mevcut metin durumunun beklenen hash’ini koyamaz mıyız?
Böylece düzenlemenin yalnızca uygun duruma uygulanacağını neredeyse garanti edebiliriz; sonraki değişiklikler de “uygulanması beklenen veri durumunun hash’i” anahtar olarak kullanılan bir sözlükte biriktirilebilir
Elbette aynı veri üzerinde hash işlemini tekrar tekrar hesaplamak gerekeceği için maliyetli olur, ama anlaması ve uygulaması çok basit
Ben belgede yerel bir düzenleme yaparsam, siz de uzaktan bir düzenleme yaparsanız, sizin “beklenen durum” hash’iniz bundan sonra benim belge durumumla asla eşleşmez
Çünkü ben yerel değişikliği zaten yapmış olurum
CRDT’nin yakınsamayı garanti etmesi için tüm istemcilerin tüm düzenlemeleri alması gerektiği doğru; ama güncellemeleri belirli bir sırayla uygulamak zorunda olmama özelliği, gerçek dünyadaki dağıtık kullanım senaryolarında önemli
Tarayıcıda formlarda birlikte düzenlemeyi kolayca açmanın bir yolu olup olmadığını merak ediyorum
İki kişi aynı sayfayı veya formu açtığında, diğerinin şu anda hangi giriş alanını düzenlediği görülebilse ve metin giriş alanlarında metin CRDT’si kullanılsa güzel olurdu
Yjs ile benzer bir şey uygulamaya çalışmıştım ama oldukça zordu ve iyi çalışmadı
Bu kullanım için biçilmiş kaftan gibi görünüyor