2 puan yazan GN⁺ 2024-07-30 | 1 yorum | WhatsApp'ta paylaş
  • İşbirlikçi yazılımlarda hiyerarşik yapılar aynı anda düzenlendiğinde, yinelenen düğümler, döngüler ve silinmiş ataların alt düğümlerinin taşınması gibi ağaç çakışmaları ortaya çıkar; Loro bunu taşınabilir ağaç CRDT’si olarak uygular
  • Martin Kleppmann ve diğerlerinin yaklaşımı, oluşturma-silme-taşıma işlemlerini Move t p m c ile birleştirir ve silmeyi TRASH düğümüne taşıma olarak ele alarak eşzamanlı alt düğüm taşımalarını korur
  • Küresel sıralama Lamport Timestamp ve Peer ID ile oluşturulur; uzak bir işlem mevcut sıranın ortasına girerse döngüleri önlemek için undo-do-redo kullanılır
  • Loro, kardeş düğümlerin sıralanması için Fractional Index ile birleştirir ve aynı konuma eşzamanlı eklemelerde indeks çakışırsa bunu PeerID, jitter ve indeks sıfırlama ile ele alır
  • Benchmark sonuçlarında Loro Movable Tree, 1000 düğüm oluşturduktan sonra 10000 rastgele taşıma işlemini 28 ms’de gerçekleştirerek gerçek zamanlı işbirliği ve geçmiş sürüm checkout’u için yeterli performans gösterir

İşbirlikçi ağaçlarda ortaya çıkan çakışmalar

  • Dağıtık sistemlerde ve işbirlikçi yazılımlarda hiyerarşik ilişkileri yönetirken taşımayı silme ve ekleme birleşimi olarak modellemek, kullanıcı beklentileri ile çakışma çözümleme biçiminin kolayca uyumsuz hale gelmesine neden olabilir
  • Aynı düğüm birden fazla replica’da aynı anda farklı ebeveynlere taşınırsa, bir düğüm iki kez silinip iki ebeveyn altında yeniden oluşturulabilir ve aynı içeriğe sahip yinelenen düğümler oluşabilir
  • Taşınabilir ağacın temel işlemleri üç tanedir: oluşturma, silme, taşıma
  • Senkronizasyon sırasında başlıca sorun çıkaran durumlar şunlardır
    • Aynı düğüm silinirken aynı anda taşınır
    • Aynı düğüm farklı ebeveynlerin altına taşınır
    • Farklı düğüm taşımaları birleşerek döngü oluşturur
    • Ata düğüm silinirken alt düğüm taşınır

Çakışma türlerine göre ele alma yöntemleri

  • Aynı düğümün silinmesi ile taşınması çakışırsa, dağıtık sistemin timestamp’ine ya da uygulama gereksinimlerine göre bir işlem uygulanıp diğeri yok sayılabilir
  • Aynı düğüm farklı ebeveynlerin altına taşındığında seçenekler uygulamaya göre değişir
    • Düğüm silinip ardından farklı ebeveynler altında kopyaları oluşturulur ve sonrasında bağımsız biçimde ele alınır
    • Bir düğümün iki ebeveyni işaret etmesine izin verilebilir, ancak bu ağaç yapısını bozduğu için genel olarak kabul edilmesi zordur
    • Tüm işlemler sıralanıp sırayla uygulanırsa tüm peer’lerde aynı sonuç üretilebilir
  • Farklı düğümlerin taşınması döngü oluşturduğunda, taşınabilir ağaçlarda çakışma çözümü özellikle karmaşık hale gelir
    • Matthew Weidner; hata işleme, “time-out” alanı render etme, sunucu tabanlı reddetme, topolojik sıralamadan sonra döngü oluşturan işlemleri atlama, render sırasında belirli edge’leri gizleme ve önceki ebeveyne geri döndürme gibi yöntemleri derler
  • Ata düğüm silinirken alt düğümün taşınması durumu da gözden kaçırılması kolay bir durumdur
    • Atanın tüm alt düğümleri hemen silinirse kullanıcı kendi verisinin kaybolduğunu sanabilir

Dropbox ve Figma’nın yaklaşımı

  • Dropbox başlangıçta dosya taşımayı, özgün konumdan silme ve yeni konumda oluşturma şeklinde iki adımda ele aldı
    • Silme ile oluşturma arasında elektrik kesintisi ya da sistem çökmesi yaşanırsa veri kaybı riski vardı
    • Günümüzde birden fazla kişi aynı dosyayı aynı anda taşımaya ve kaydetmeye çalıştığında çakışmayı algılar; genellikle özgün dosyanın bir sürümünü saklar ve bir kullanıcının değişikliği için “conflicted copy” oluşturur
  • Figma, ağaç yapısını işbirlikçi sistemin en karmaşık kısmı olarak görür ve her öğeye bir parent özelliği verir
    • Merkezi sunucu, birden çok kullanıcının güncellemelerini izler ve herhangi bir işlem döngü oluşturabilecekse o işlemi reddeder
    • Ağ gecikmesi nedeniyle sunucu reddetmeden önce geçici olarak döngü oluşabilir
    • Bu durumda Figma, döngüye dahil olan öğeleri geçici olarak gizler ve sunucu işlemi resmen reddedene kadar durumu korur
    • İlgili açıklamalar Figma’nın multiplayer technology yazısında görülebilir

Taşınabilir ağaç CRDT’lerine iki yaklaşım

  • Merkezi çözüm yerine, işbirlikçi ağaç yapıları CRDT ile de ele alınabilir
  • İlk dönem CRDT tabanlı ağaç algoritmalarının uygulanması zordu ve depolama ek yükü yüksekti; ancak optimizasyonlar ve iyileştirmelerle bazı üretim ortamlarına uygun ağaç senkronizasyon algoritmaları ortaya çıktı
  • Temsili iki CRDT tabanlı yaklaşım vardır

Kleppmann yaklaşımı: tüm işlemleri Move ile birleştirmek

  • A highly-available move operation for replicated trees, ağacın oluşturma-silme-taşıma işlemlerini tek bir move işlemi altında birleştirir
  • Move işlemi Move t p m c olmak üzere dört değerle tanımlanır
    • t: Lamport timestamp gibi benzersiz ve sıralanabilir bir timestamp
    • p: ebeveyn düğüm ID’si
    • m: düğümle ilişkili metadata
    • c: çocuk düğüm ID’si
  • Ağaçta c yoksa, ebeveyn p altında çocuk c oluşturan bir oluşturma işlemi olur
  • c zaten varsa, mevcut ebeveynden yeni ebeveyn p altına taşıyan bir taşıma işlemi olur
  • Silme, belirlenmiş TRASH düğümüne taşıma şeklinde ele alınır
    • TRASH altındaki tüm alt düğümler silinmiş kabul edilir
    • Ancak bellekte tutulurlar; böylece eşzamanlı düzenlemeler bu düğümü başka bir düğümün altına taşıyabilir
    • Bu, ata silme ile alt düğüm taşımanın aynı anda gerçekleştiği durumları ele almak için kullanılan bir mekanizmadır

Sıralama ve unsafe operation

  • Silme de move işlemi olarak tanımlandığı için, “aynı düğümün silinmesi ve taşınması” iki move işleminin çakışmasına dönüşür
  • Geriye kalan temel sorun iki tanedir
    • Aynı düğümü farklı ebeveynlerin altına taşımak
    • Farklı düğümleri taşıyıp döngü oluşturmak
  • Tüm işlemler Lamport timestamp ve Peer ID ile doğrusal sıraya dizilirse, aynı düğümün eşzamanlı taşınması da sıralı iki işlem olarak ifade edilir
  • Ağaç yalnızca move işlemleriyle modellendiğinde, eşzamanlı düzenlemenin istisnai durumları döngü üretmeye indirgenir
  • Döngü oluşturan işlemler unsafe operation olarak ele alınır
    • Algoritma tüm move işlemlerini timestamp sırasına göre sıralar
    • Her işlem uygulanmadan önce döngü tespit edilir
    • Döngü oluşturuyorsa ilgili unsafe operation yok sayılır ve doğru ağaç yapısı korunur

Lamport Timestamp ve uzak işlemlerin uygulanması

  • Lamport Timestamp, dağıtık sistem olaylarının nedensel sırasını belirlemeyi mümkün kılar
    • Her peer, 0’dan başlayan bir counter’a sahiptir
    • Yerel bir olay oluştuğunda counter 1 artırılır ve bu değer timestamp olarak kullanılır
    • A peer’i B’ye mesaj gönderdiğinde timestamp’i ekler
    • B, kendi mantıksal saati ile aldığı timestamp’i karşılaştırır ve daha büyük değere günceller
  • Küresel sıralamada önce Lamport Timestamp karşılaştırılır; değerler eşitse tie-breaker olarak peer’in benzersiz ID’si kullanılır
  • Uzak bir güncelleme mevcut sıralı işlem dizisinin ortasına girerse undo-do-redo gerekir
    • En son işlemler geri alınır
    • Yeni işlem araya eklenip uygulanır
    • Geri alınan işlemler yeniden uygulanır
  • Move işlemini hızlıca geri alabilmek için, her move uygulanmadan önce hedef düğümün old parent bilgisi cache’lenir
  • Unsafe operation’ların etkisi yok sayılsa bile kayıtları tutulmalıdır
    • İşlemin güvenli olup olmadığı dinamik olarak belirlenir
    • Daha sonra döngüye neden olan başka bir düğüm önce silinirse, daha önce unsafe olan işlem safe hale gelebilir
    • Undo sürecinde, en son etkili olan işlemin hedef ebeveynini bulmak için ineffective işaretlemesi gerekir

undo-do-redo örneği

  • Yeni bir işlem, yerelde bulunmayan bir işleme bağımlıysa ara sürüm güncellemesi henüz eksik olduğundan önce geçici olarak önbelleğe alınmalı, ardından eksik güncelleme alındıktan sonra uygulanmalıdır
  • Yeni opId, mevcut tüm işlemlerden büyükse hemen uygulanabilir
    • safe ise hedef düğümün mevcut ebeveyni old parent olarak kaydedilir ve move uygulanır
    • unsafe ise ineffective olarak işaretlenir ve etkisi yok sayılır
  • Yeni opId, mevcut sıralamanın ortasına giriyorsa sonraki işlemler tek tek çıkarılıp geri alınır, yeni işlem uygulandıktan sonra geri alınan işlemler sırayla yeniden uygulanır
  • Örnek akışta Peer1, yerelde C'yi B altına taşıdıktan sonra Peer0'ın B'yi C altına taşıyan işlemini alır
    • Lamport timestamp sıralamasında 0:3, 1:3'ten önce geldiği için önce 1:3 undo edilerek C, old parent A'ya geri döndürülür
    • Ardından 0:3 ile B, C altına taşınır
    • Sonra 1:3 redo edilerek C yeniden B altına taşınmaya çalışılır, ancak döngü tespit edildiği için uygulanmaz
    • Ağaç durumu değişmeden kalır ve undo-do-redo süreci tamamlanır

Evan Wallace yaklaşımı: geçmiş ebeveynleri izleme

  • Evan Wallace'ın CRDT: Mutable Tree Hierarchy yazısı, her düğümün geçmişteki tüm ebeveyn düğümlerini izlemesini sağlar
  • Kaydedilen her ebeveyne bir sayaç bağlanır
    • Yeni ebeveynin count değeri, ilgili düğümün tüm geçmiş ebeveyn count değerlerinden 1 daha büyüktür
    • En yüksek count değerine sahip ebeveyn, mevcut ebeveyn olur
  • Senkronizasyon sırasında ebeveyn kayıtları da birlikte senkronize edilir
  • Döngü oluşursa sezgisel algoritma, döngüye neden olan düğümü, en yakın geçmiş ebeveynler arasından döngü oluşturmayan ve root'a bağlı olan bir ebeveyne yeniden bağlar
  • Bu süreç, döngüdeki tüm düğümler yeniden ağaca bağlanana kadar tekrarlanarak replica'lar arasındaki ağaç yapısı senkronize edilir
  • Bu yaklaşım pahalı undo-do-redo prosedürüne ihtiyaç duymaz, ancak her uzak move alındığında tüm düğümlerin root'a bağlı olup olmadığı kontrol edilip döngü düğümlerinin yeniden bağlanması gerektiğinden düğüm sayısı arttığında performans kötüleşebilir
  • Performans karşılaştırması için ayrı bir benchmark hazırlanmıştır

Loro'nun Movable Tree uygulaması

  • Loro, Martin Kleppmann ve diğerlerinin A highly-available move operation for replicated trees algoritmasını uygular
  • Bu algoritma, çoğu gerçek dünya senaryosunda yüksek performans sunar
  • Temel olan undo-do-redo süreci, Loro'da Eg-walker'ın (Event Graph Walker) uzak güncellemeleri uygulama biçimine çok benzer
  • Yalnızca movable tree kullanmak, kardeş düğümlerin sıralanması sorununu çözmez
    • outline notes veya grafik tasarım yazılımlarında layer yönetimi için alt düğümlerin sıralanması gerekir
    • Kullanıcının düğüm sırasını ayarlayıp bunu diğer iş birlikçilere veya cihazlara senkronize etmesi gerekir
  • Loro, movable tree içindeki alt düğümlerin sıralanabilir olmasını sağlamak için Fractional Index algoritmasını entegre eder

Fractional Index ve eşzamanlı ekleme çakışmaları

  • Fractional Index, her nesneye sıralanabilir bir değer atar
    • İki nesne arasına yeni bir öğe eklendiğinde yeni nesnenin Fractional Index değeri sol ve sağ değerlerin arasında olur
    • İlgili açıklamalar Figma blog ve Evan blog üzerinde görülebilir
  • Dağıtık ortamda birden fazla peer aynı konuma yeni düğüm eklerse, içeriği farklı düğümlere aynı Fractional Index atanabilir
  • Loro, aynı Fractional Index değerini korur ve aynı indeksler arasındaki göreli sıra, her peer'ın benzersiz kimliği olan PeerID ile belirlenir
  • Aynı Fractional Index her iki tarafta da varsa, bunların arasına yeni bir Fractional Index üretilemez
  • Loro bu sorunu iki yöntemle ele alır
    • Üretilen Fractional Index değerine belirli miktarda jitter ekleyerek aynı indeksin oluşma olasılığını büyük ölçüde azaltır
    • Örneğin 0 ile 1 arasındaki değer normalde 0.5 olacaksa, rastgele jitter ile 0.52712, 0.58312, 0.52834 gibi değerlere dönüşebilir
    • 0.7@A ile 0.7@B arasına ekleme yapılması gerekiyorsa, 0.7 ile 1 arasından yeni düğüme ve 0.7@B düğümüne ayrı ayrı yeni Fractional Index atanacak şekilde yeniden ayarlama yapılabilir

Kodlama boyutu ve jitter ayarı

  • Loro, drifting-in-space tarafından geliştirilen Vec<u8> tabanlı Fractional Index uygulamasını kullanır
  • Bu uygulama base 256 kullanır
    • Varsayılan ayarda, ileri veya geri yönde art arda 128 değer eklenmesi ancak Fractional Index byte boyutunu 1 artırır
  • En kötü depolama overhead'i, her seferinde yeni değerlerin dönüşümlü olarak eklenmesi durumunda ortaya çıkar
    • Örneğin ab içinde a ile b arasına c, sonra c ile b arasına d, ardından c ile d arasına e eklemek gibi
    • Bu durumda yeni bir işlem ek byte gerektirebilir, ancak bu oldukça nadir görülen bir durumdur
  • Loro, özgün uygulamaya basit bir jitter çözümü ekler
    • Fractional Index değerinin sonuna, jitter değeri uzunluğu kadar rastgele byte eklenir
    • JavaScript'te doc.setFractionalIndexJitter(number) içine pozitif bir sayı verilerek jitter etkinleştirilebilir
    • Kodlama boyutu bir miktar artar ve her Fractional Index için jitter byte'ları eklenir
  • Aynı konumda Fractional Index üretilirken çakışmanın %99 olasılıkla önlenmesi için gerekli jitter ile maksimum eşzamanlı düzenleme sayısı n arasındaki ilişki şöyledir
jitter Maksimum eşzamanlı düzenleme sayısı
1 3
2 37
3 582
  • Sıralanmış çok sayıdaki Fractional Index arasında ortak prefix sayısı artar
  • Loro, kodlama sırasında önceki değerle aynı olan prefix bit sayısını ve kalan byte'ları saklayan prefix optimizasyonu ile toplam kodlama boyutunu azaltır

İlgili çalışmalar ve seçim nedeni

  • Fractional Index dışında, ağaçtaki sibling düğümleri sıralayabilen movable list CRDT'leri de vardır
  • Martin Kleppmann'ın Moving Elements in List CRDTs çalışması, Loro'nun Movable List yapısında kullanılır
  • Fractional Index çözümü uygulama açısından daha basittir
  • Ağaç düğümleri modellenirken alt düğümler için stable position representation sağlanmazsa tüm ağaç yapısı aşırı karmaşık hale gelir
  • Fractional Index için interleaving sorunu vardır
    • Figma layer öğeleri veya çok seviyeli yer imleri gibi yalnızca göreli sıranın gerekli olduğu ve katı sıralı anlam bilgisinin gerekmediği durumlarda bu kabul edilebilir

Benchmark sonuçları

  • Loro, Movable Tree uygulaması için rastgele düğüm taşıma, geçmiş sürümler arasında geçiş ve çok derin ağaç yapılarındaki uç koşul performansını benchmark etti
  • Sonuçlar, gerçek zamanlı iş birliğini ve sorunsuz geçmiş sürüm checkout'unu destekleyebilecek düzeyde çıktı
  • Test ortamı M2 Max CPU ve benchmark kodu tree.rs içinde yer alıyor
İşlem Süre Ayar
10000 rastgele taşıma 28ms Önce 1000 düğüm oluştur
Farklı sürümlere 1000 geçiş 153ms Önce 1000 düğüm oluştur, ardından 1000 taşıma yap
Derinliği 300 olan ağaçta farklı sürümlere 1000 geçiş 701ms Yeni düğüm, önceki düğümün çocuğu

Kullanım örneği ve demo

  • loro-crdt içindeki LoroTree; düğüm oluşturma, konum belirterek oluşturma, taşıma, root'a taşıma, başka bir düğümün önüne/arkasına taşıma, ebeveyn içindeki index sorgulama, Fractional Index sorgulama ve düğümün data map'ine erişim sunuyor
import { Loro, LoroTree, LoroTreeNode, LoroMap } from "loro-crdt";

let doc = new Loro();
let tree: LoroTree = doc.getTree("tree");
let root: LoroTreeNode = tree.createNode();
// By default, append to the end of the parent node's children list
let node = root.createNode();
// Specify the child's position
let node2 = root.createNode(0);
// Move `node2` to be the last child of `node`
node2.move(node);
// Move `node` to be the first child of `node2`
node.move(node2, 0);
// Move the node to become the root node
node.move();
// Move the node to be positioned after another node
node.moveAfter(node2);
// Move the node to be positioned before another node
node.moveBefore(node2);
// Retrieve the index of the node within its parent's children
let index = node.index();
// Get the `Fractional Index` of the node
let fractionalIndex = node.fractionalIndex();
// Access the associated data map container
let nodeData: LoroMap = node.data;
  • Loro ile birden çok peer arasında veri senkronizasyonunu simüle eden bir Todo uygulaması demosu oluşturuldu
    • Movable Tree, subtask ilişkilerini ifade ediyor
    • Map, task'ın çeşitli özelliklerini ifade ediyor
    • Text, task başlığını ifade ediyor
    • Oluşturma, taşıma, düzenleme ve silmenin yanı sıra Loro tabanlı sürüm geçişi de uygulanmış
    • Scrollbar sürüklenerek yürütülen tüm geçmiş sürümler arasında geçiş yapılabiliyor

Özet

  • Taşınabilir ağaç CRDT uygulaması; eşzamanlı taşıma, silme, döngü ve ata silme ile alt düğüm taşımanın birleşimi nedeniyle zor
  • Loro, Kleppmann ve diğerlerinin yüksek erişilebilirlik sağlayan move işlemi algoritmasıyla ağaç hiyerarşisindeki taşımayı uyguluyor
  • Çocuk düğümler arasındaki taşıma ve sıralama, drifting-in-space içindeki Fractional Index uygulaması entegre edilerek ele alınıyor
  • Bu kombinasyon, çeşitli iş birliğine dayalı uygulama senaryolarının gereksinimlerini karşılayabiliyor

1 yorum

 
GN⁺ 2024-07-30
Hacker News yorumları
  • Görevler/notlar için yeni bir çok oyunculu düzenleyici [1] geliştiriyorum; hem metin hem de outliner işlemlerini destekliyor.
    Dışarıdan düz bir metin belgesi gibi davranıyor, ancak outliner özellikleri nedeniyle içeride büyük bir ağaç yapısına dönüşüyor. Değişiklik eşitleme için yüksek erişilebilirlikli taşıma işlemlerine benzer bir yöntem kullanıyor. Ağacı değiştiren tek işlem insmov; istemci çevrim içiyse değişiklik kümesi C’yi sunucuyla eşitliyor. Sunucuda uzak değişiklikler varsa, son eşitlemeden bu yana yapılan tüm değişiklikleri R olarak küresel doğrusal sırayla geri döndürüyor; ardından yerel değişiklik C’deki insmov işlemlerini geri alıp R’yi ve henüz eşitlenmemiş yeni değişiklikleri yeniden uyguluyor.
    Kesirli indeks kullanmıyor; insmov demetinde ebeveyn P’nin yanı sıra önceki kardeş guid A da yer alıyor. Tüm ağaç işlemleri sonunda sunucunun belirlediği küresel doğrusal sıraya göre uygulandığından, sıralama insmov işleminin kendisiyle ele alınıyor. Çoğu zaman geri alma gerekmiyor; yalnızca sunucuda benim bilmediğim bir insmov değişikliği varken aynı anda ben yeni bir insmov gönderirsem doğru sırayla yeniden oynatma gerekiyor. Uzun bir uçuştan sonra Wi‑Fi’a yeniden bağlanırken bu olabilir; ancak çevrim içiyken WebSocket üzerinden gerçek zamanlı push alındığında daha az yaygın ve metin güncellemeleri gibi insmov olmayan işlemler için gerekli değil.
    [1] https://thymer.com

    • Bu yaklaşım, sunucunun küresel doğrusal sırasını mantıksal zaman damgası olarak kullanan RGA liste CRDT’si [1] ile eşdeğer görünüyor.
      Örneğin Lamport zaman damgası yerine sunucu sırasını kullanan bir biçim.
      [1] https://inria.hal.science/inria-00555588/
    • Dün tesadüfen eski bir başlığı[0] okurken Thymer hakkında bir yazı gördüm ve ilgimi çekti.
      HN’de Thymer’ı arayınca 2009’dan bir Show HN[1] çıktı; Thymer son 15 yıldır kapalı betadaymış gibi görünüyor.
      0. https://news.ycombinator.com/item?id=40786425
      1. https://news.ycombinator.com/item?id=518803
    • Hangi rich text’i kullandığını merak ediyorum.
  • Bu yazıyı mutlaka okumam gerekecek. Bir freelance müşteri işinde React Table Library’yi [0] açık kaynak olarak yayımladım ve ağaç işlemlerine odaklandım.
    Onlar 100 bin düğüm ölçeğinde klasör/dosya ağaç yapılarıyla uğraşıyor; klasör ve dosya taşıma, kopyalama, en üst ve iç içe seviyelerde lazy loading gibi şeyleri aynı tablo yapısı içinde ele alıyorlar. Projeyi bitirince Google Drive’ın neden aynı hiyerarşi seviyesinde yalnızca görüntüleme ve düzenlemeye izin verdiğini anladığımı düşündüm. Çok sayıda düğümü olan iç içe bir görünümde bunu uygulamak için dikkate alınması gereken çok fazla kısıt var.
    [0] https://react-table-library.com/

    • Güzel görünüyor; ne zaman tamamen headless olacağını merak ediyorum.
  • Tavsiye almak istiyorum. Çok oyunculu bir uygulama değil ama frontend’de kullanıcı profili olarak kullanılan büyük ve birbirine bağlı denormalize ağaçlar var.
    Kullanıcının karo ekleyip/silip/boyutlandırdığı, her karo yuvasına birden çok bileşen eklediği ve bu bileşenlerin de kendi profillerine sahip olduğu karo tabanlı bir yerleşim düşünün. Farklı karo dizilimlerine sahip birden fazla yerleşim olabilir; tekil karoların global durumun başka parçalarına referans vermesi ya da bunları paylaşması gibi karmaşıklıklar da var.
    Bunu sıradan REST ile güvenli biçimde güncellemek zor. Çünkü aynı kullanıcının iki sekme açıp sekme 1’de güncelleme yaptıktan sonra sekme 2’de tekrar güncelleme yaparak tüm profili geçersiz hâle getirmemesini garanti etmek gerekiyor. Genel olarak sıra da önemli. İstemcide düzgün uygulanmış güncellemeler sunucuda atlanırsa yapı bozulabilir.
    Çok basit bir çözüm olarak, belirli bir durum parçasını tamamen üzerine yazabilecek minimum veriyi gönderip kuyruğun arkasına ekleme yöntemini kullandım. Genelde sorun olmuyor ama gerçek değişiklik yalnızca birkaç baytken 50 KB göndermek gibi israfın olduğu durumlar olabiliyor.
    Genel olarak CRDT gerektiren nedenler bizde yok; ama tek kullanıcıda bile durum yönetimini çok daha kolaylaştıracak gibi görünüyor. Öncelikle kullanıcının tarayıcı sekmeleri arasında eşitleme olur; daha da önemlisi frontend durumunu basitçe değiştirdiğimde CRDT’nin sunucuyla uygun biçimde uzlaştıracağına güvenebilirim. Artık bunu kendim yönetmek zorunda kalmam. Bunun mantıklı olup olmadığını, yoksa çok oyunculu ve local-first gerekmeyen bir durumda Yjs gibi bir şeyi eklemenin getirdiği ek yükün buna değip değmediğini merak ediyorum.

    • Uygulama birden fazla sekmeyi aktif biçimde kullanıyorsa YJS gibi bir şey kullanmak mantıklı olabilir. Çünkü bu tür problemleri çözmede çok etkili.
      Ancak profil düzenleme tek kullanıcıya özelse CRDT getirmek fazla gibi görünüyor. Görünüşe göre en büyük hata kaynağı iki sekmenin açık olduğu senaryo; bu nedenle BroadcastChannel ile güncelleme olaylarını diğer tüm sekmelere bildirme yöntemi kullanılabilir.
    • Bu kullanım durumu için CRDT uygun görünüyor.
      Paylaşılan durumu, sunucu durumunun bir kısmının üzerine yazan REST çağrılarıyla korumak gerçekten kırılgan ve yalnızca düz veri kayıtlarındaki alanların üzerine yazma düzeyine uygun. Ayrıca sunucu-istemci durum uzlaştırmasını her zaman dikkatle düşünmek gerekiyor; olağan akış dışındaki yollarda eşitleme kolayca sapabiliyor.
      Söylediğiniz gibi, güncellemelerin nasıl birleştirileceğini açıkça tanımlayan bir CRDT oluşturmak bilişsel yükü ciddi biçimde azaltacaktır.
  • Google Docs veya Zoho Writer gibi zengin metin içeriklerinde bir liste öğesini aşağı taşımak ya da yeni bir sütun eklemek gibi işlemler; tablo/liste işlemleri özünde ağaç manipülasyon işlemleridir
    Bu tür durumlarda eşzamanlı çakışmaların, bağlama özel işlem yapılmadan yakınsatılmasının zor olduğu kötü bir şöhrete sahip [1]. Bu uygulamanın bu tür kullanım senaryoları için de genelleştirilmiş bir çözüm sunup sunmadığını merak ediyorum
    Muhtemelen yaprak düğümlerde, yani metin bloklarında liste veya dize CRDT’si kullanıp; yapısal düğümler olan listeler ve tablolarda bu ağaç CRDT’sini birleştirmek mümkün olabilir. Ama o zaman tüm işlemlere 2 boyutlu bir adres (parent-id, index_offset_into_that_parent) eklemek gerekir
    [1] https://github.com/inkandswitch/peritext/issues/27

    • Ben de hep böyle hayal etmiştim. Rich text, düz metne iki şey eklenmiş hâlidir: kalın gösterilen aralıklar gibi açıklama aralıkları ve tablo ya da gömülü görsel gibi metin dışı öğeler
      Metin CRDT’si temelde karakter verisi tutan bir liste CRDT’sinden ibarettir. Bu yüzden gömülü öğeler, dizenin diğer öğeleri gibi boyutu 1 olan özel öğeler, yani gömülü alt düğümler olarak kolayca modellenebilir. Doğru yaklaşım kullanılırsa, ihtiyaca göre ağacın içinde farklı CRDT’ler karıştırılıp kullanılabilir. Örneğin rich text içinde bir tablo, o tablonun hücrelerinden birinde de bir görsel olması gibi
      Her işleme bir parent-crdt-id alanı eklemek üzücü ama kaçınılması zor görünüyor. Neyse ki gerçek kullanım senaryolarının çoğunda ardışık işlemlerin aynı ebeveyn CRDT’yi paylaşması çok yaygın olacak gibi; bu yüzden bu tür ID alanları run-length encoding ile iyi sıkıştırılabilir
    • Uygulama tarafında birden fazla türde CRDT gerçekten birleştirilebiliyor. Loro’nun iç uygulamasında her işlemin ebeveyn ID’sini saklaması gerekiyor
      Ancak Seph’in söylediği gibi, aynı ebeveyn altındaki ardışık işlemler etkili biçimde sıkıştırılabildiğinden, bu ebeveyn ID’sinin amortize ek yükü genellikle büyük olmuyor
  • Görsel pikselleri veya 3D modeller gibi veri yoğunluğu yüksek uygulamalar için pratik bir CRDT olup olmadığını merak ediyorum

    • İşbirlikli uygulamalarda önce kullanıcının yapacağı düzenlemeler için kavramsal çerçeveyi ve bu düzenlemeler asenkron gerçekleştiğinde kullanıcının niyetini ve sonuç belgenin tutarlılığını en iyi nasıl koruyacağınızı belirlemek gerekir
      Belgenin somut temsili veri yoğun olsa bile, kullanıcının tekil düzenlemelerini ve işlemlerini kodlama biçimi hâlâ küçük olabilir
      Photoshop gibi bir görüntü düzenleyici yaptığınızı düşünelim: kanal başına 16 bit renk derinliğine sahip, sıkıştırılmamış 102 milyon piksellik bir görüntü; örneğin bir Fujifilm GFX100 fotoğrafı TIFF olarak yaklaşık 610 MB tutar. Her pikseli ayrı bir son-yazma-kazanır register’ı olarak ifade ederseniz ek yük büyük olur; ama böyle bir temsil, kullanıcının niyetini korumak için aslında uygun değildir. Kullanıcının yapacağı düzenlemeler “görüntü kontrastını %15 artır” veya “Q fırçası ve #000 rengiyle [(0,0), (1500, 1500)] spline’ını boya” gibi şeylerdir. Her pikseli Lamport zaman damgasıyla senkronize ederseniz, kullanıcı 1’in kontrast değişikliği kullanıcı 2’nin boyadığı pikseller dışındaki tüm piksellere uygulanır ve üstüne boyanmış pikseller garip görünebilir
      Bunun yerine kullanıcı niyetini bir düzenleme işlemleri listesi olarak ifade etmek daha iyidir. Bu, 102 MB’lık piksel ızgarasının tamamından çok daha küçüktür. CRDT veri yapısı, bu tür kullanıcı niyetini senkronize etmek için olası teknik mekanizmalardan biridir; fakat yapı, çıktının somut veri düzenine değil kullanıcı niyetinin semantiğine göre seçilmelidir
      Yine de "add new layer named bgbelow layerfgwith pixelsdata:(10mb of pixels) at (1500, 1500)" gibi büyük miktarda veri içeren düzenleme işlemleri ortaya çıkabilir. Ancak bu tür düzenleme komutlarının senkronizasyon ek yükü çok düşüktür; boyutu O(1)’dir, düzenleme komutunun içindeki piksel sayısıyla orantılı O(pixels) değildir
    • Tam olarak aynı şey değil ama Figma’nın eşzamanlı düzenlemeyi desteklediğini ve CRDT’ye benzer bir yaklaşım kullandığını biliyorum (https://www.figma.com/blog/how-figmas-multiplayer-technology...)
    • Görüntü düzenlemede tüm çakışan düzenlemeler son yazar kazanır yöntemiyle kolayca çözülebildiği için CRDT’nin gerçekten gerekli olup olmadığından emin değilim
      3D modeller ayrı bir mesele; piyasada işbirlikli bir 3D modelleme aracı olup olmadığını görmedim. Aktif olarak araştırmış da değilim
    • Performanslı bir piksel tabanlı CRDT’nin nasıl görünebileceğini büyük CRDT yazımda taslak olarak anlatmıştım: http://archagon.net/blog/2018/03/24/data-laced-with-history/...
      Kendim yapmadım ve pratikte kullanışlı olup olmayacağından da emin değilim. Yine de en azından belgenin tüm geçmişini koruyabilir
    • Gördüğüm güzel bir örnek, raster grafikler için yıkıcı olmayan bir düzenleyici olan Modyfi
      Verileri Yjs ile temsil ediyor ama ham pikselleri saklamak yerine dönüşümlerin tüm geçmişini saklıyor
      https://digest.browsertech.com/archive/browsertech-digest-ho...
  • Yazının GPT ile kontrol edilip edilmediğini merak ediyorum. İlk paragrafta güçlü bir ChatGPT üslubu hissediliyor

    • Öyle görünmüyor. Şu tür dilbilgisi hataları ChatGPT’ye pek benzemez:
      This article introduces the implementation difficulties and challenges of Movable Tree CRDTs when collaboration, and how Loro implements it and sorts child nodes.