<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Raph Levien on Medium]]></title>
        <description><![CDATA[Stories by Raph Levien on Medium]]></description>
        <link>https://medium.com/@raphlinus?source=rss-c13af5dea630------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*oi8EuFuE7EpFsMOv.JPG</url>
            <title>Stories by Raph Levien on Medium</title>
            <link>https://medium.com/@raphlinus?source=rss-c13af5dea630------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 23:35:08 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@raphlinus/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Working code for Operational Transformation/CRDT hybrid]]></title>
            <link>https://medium.com/@raphlinus/working-code-for-operational-transformation-crdt-hybrid-9d04a57309da?source=rss-c13af5dea630------2</link>
            <guid isPermaLink="false">https://medium.com/p/9d04a57309da</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[crdt]]></category>
            <category><![CDATA[text-editor]]></category>
            <dc:creator><![CDATA[Raph Levien]]></dc:creator>
            <pubDate>Thu, 23 Feb 2017 01:31:41 GMT</pubDate>
            <atom:updated>2017-02-23T16:23:58.897Z</atom:updated>
            <content:encoded><![CDATA[<p>Last year I published an essay entitled <a href="https://medium.com/@raphlinus/towards-a-unified-theory-of-operational-transformation-and-crdt-70485876f72f">Towards a unified theory of Operational Transformation and CRDT</a>. In it, I proposed that a certain class of Operational Transformation algorithms (those satisfying the TP2 property) also satisfied the requirements for being Conflict-free Replicated Data Types, and had overall desirable properties. I have now released a <a href="https://github.com/google/ot-crdt-papers">prototype</a>, and in this follow-up I will explain it, at least in broad strokes.</p><p>I could have just implemented <a href="http://www.collide.info/Lehre/SeminarWS0405/SunEllis98.pdf">GOTO</a> (Generic Operational Transformation Optimized) with <a href="http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.230.6731&amp;rep=rep1&amp;type=pdf">Tombstone Transformation Functions</a>, and that that would have made a pretty good demonstration of collaborative editing with a small, simple codebase. That would have had most of the advantages I’m claiming, including the ability to work in arbitrary peer-to-peer network topologies rather than requiring a centralized server. In addition, since OT techniques represent the string as a string rather than a graph or tree of character nodes (as in <a href="https://hal.inria.fr/inria-00071240/document">WOOT</a> and similar CRDT’s), they are quite RAM-efficient. I don’t consider GOTO to be harmful.</p><p>But I went farther. In particular, my code has an efficient (balanced binary tree) data structure for representing sets of indices. Even so, the entire codebase, including admittedly very sparse UI, is 400 lines of JavaScript, probably one of the smallest and simplest collaborative editing prototypes ever shown.</p><h4>Efficient tombstones</h4><p>The tombstone approach represents the document as a sequence of all characters ever inserted, whether they were deleted or not. The traditional tombstone approach would be to store those characters in a string, alongside a set of deleted characters. To reconstruct the actual document, you’d apply a <em>read function</em> consisting of deleting the characters from that set (this is called viewToModel in the TTF paper).</p><p>However, it’s not actually necessary to store the deleted characters in the string, only to record the fact that the character has been deleted. The TTF paper (in section III.D) describes how to do this; you delete characters out of the string, and keep a data structure alongside that tracks deleted characters. There are two coordinate spaces, one indexing the string, and one indexing the (virtual) string that also contains the tombstones. The TTF paper suggests a straightforward linear-time algorithm for computing the transform between these two spaces.</p><p>My code stores the set of deleted indices in a balanced binary tree, and can compute both the forward (xi in the code) and inverse transformation (xi_inv) in O(log n) time. Operations to delete a single character (union_one) and increment all indices past an insertion point (xi_one) are also logarithmic. The data structure is about 150 lines of JavaScript, a non-trivial fraction of the total, but the efficiency improvement is substantial.</p><p>Using a set to represent a coordinate transform has some interesting mathematical properties. Here’s a mathematical description of the forward transformation of the index <em>i</em> through the set S<em>:</em></p><p>Ξ_{S}(i) = maxⱼ|{k | k&lt;j ∧ k ∉ S}| = i</p><p>And here’s a picture of the transformation, for a single character at index 1. Note the similarity to the traditional OT transformation for insertion:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/643/1*rwduC55M6-P8YS8PY5rz-A.png" /><figcaption>Example of xi transform for a singleton set</figcaption></figure><p>The forward transform is a strictly increasing function over the natural numbers. There is a one-to-one correspondence between sets and such functions; for example, the set of non-primes would represent the function giving the nth prime. The composition of such functions is associative, which is a valuable property for manipulating them more efficiently. (The JavaScript codebase doesn’t have a function for directly composing two such sets, but there is an <a href="https://github.com/google/xi-editor/blob/master/rust/rope/src/subset.rs#L183">implementation</a> in xi-editor, based on a run-length rather than balanced binary tree representation of sets). I don’t know of any existing mathematical literature that deals with or generalizes these concepts, but I wouldn’t be surprised to learn of it.</p><h4>Efficient merge using coordinate transformation</h4><p>The inner loop of GOTO’s merge operation strongly resembles an insertion sort; it does pairwise transposition of adjacent operations in the log until it’s ordered appropriately (which in this case means that the operations present in the incoming operation’s context all precede those not present in that context). That algorithm is, like insertion sort, O(n²), where here n is a measure of the number of concurrent operations in flight.</p><p>The traditional OT model requires consideration of one operation against only a single other operation. In that model, it’s hard to imagine doing better than quadratic. However, my code is not constrained to compute IT and ET transforms on pairs of operations. Rather, I use that same O(log n) data structure to represent the effect of jumping over a whole sequence of n operations. Thus, my merge algorithm (merge_op in the code) is O(n log n), a significant speedup from, as far as I know, any previous known OT algorithm.</p><p>If you look at the merge_op code, it’s fairly dense mathematically, even though not very many lines of code. For the brave and curious who want to dig into it, it will probably help to know that it computes exactly the same thing as Algorithm 2 in the GOTO paper, just more efficiently. The loop over transform_ins near the end of the function is pretty much just LIT from the GOTO paper.</p><h4>An invitation</h4><p>I believe the ideas in this code would make a pretty good academic paper. Among other things, the asymptotic complexity bounds are significantly improved from previous results. Further, while this implementation is obviously a toy, I think it could be the basis for a practical, efficient collaborative editing system.</p><p>Unfortunately, this work has been very much a side project, and it’s very hard for me to find the time to prepare a proper academic paper. There’s quite a bit to do, including making a convincing argument for the correctness of the whole thing. I personally believe it’s correct based on automated randomized testing of an earlier prototype, but an analytic argument would be better for an academic paper.</p><p>I think that taking this work to a publishable paper would make an excellent project, potentially at the scope of a Master’s thesis. If there’s anyone out there who is interested, I’d be interested in a collaboration.</p><p>In the meantime, I did want to get this code out there, so that people building the next generation of collaborative editing systems can see the value in adopting a hybrid approach that combines the best advantages of both Operational Transformation and CRDT.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9d04a57309da" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Inside the fastest font renderer in the world]]></title>
            <link>https://medium.com/@raphlinus/inside-the-fastest-font-renderer-in-the-world-75ae5270c445?source=rss-c13af5dea630------2</link>
            <guid isPermaLink="false">https://medium.com/p/75ae5270c445</guid>
            <category><![CDATA[rust]]></category>
            <category><![CDATA[fonts]]></category>
            <category><![CDATA[graphics]]></category>
            <dc:creator><![CDATA[Raph Levien]]></dc:creator>
            <pubDate>Tue, 02 Aug 2016 04:23:59 GMT</pubDate>
            <atom:updated>2016-08-02T04:23:59.826Z</atom:updated>
            <content:encoded><![CDATA[<p>Over the past couple of years on and off, I’ve been experimenting with pushing the limits of software font rendering performance. The result is <a href="https://github.com/google/font-rs">font-rs</a>, a font renderer written in Rust, almost an order of magnitude faster than the industry-standard FreeType. At this point, it’s more of a technology demo than a production-quality library, but it’s certainly far enough along to analyze its speed. In this post, I’ll talk about exactly how it achieves such remarkable performance.</p><h3>Performance results</h3><p>First, the numbers. This is a measurement of how long it takes to render “g” from Roboto Regular, at pixel/em settings between 1 and 200. Measurements were done on a 2.9 GHz Xeon E5–2690. The comparisons are FreeType 2.6.5, font-rs using only pure Rust, and a version of font-rs with SIMD speedup.</p><p>With the SIMD speedup, font-rs is approximately 7.6x faster than FreeType in larger sizes (keep in mind that 42 pixels/em is the default for xxhdpi Android devices). Even at a relatively small size of 12 pixels/em, it’s over 4x faster. The speedup is even bigger (over 6x at 12 pixels/em) when measured on a CJK typeface (the majority of CPU cycles spent rendering fonts in the world).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/640/0*q6EozbKxy4cGDkVA." /></figure><h3>Font rendering, where does the time go?</h3><p>There are basically 3 CPU-intensive components to font rendering: parsing the binary outline from the font, drawing into an accumulation buffer, and integrating the accumulation buffer, storing the results into an 8 bit per pixel bitmap.</p><p>During drawing, the accumulation buffer stores the finite difference (in scan-line order) of the actual signed area. All pixels fully inside or outside the glyph (not touched by the edge) are zero. More importantly, in the first difference, the sum of the signed area is just the sum of the contribution of each of the edges.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*eK0MPlbHURlFQ1ng." /></figure><p>For more details on the signed area approach, see <a href="http://nothings.org/gamedev/rasterize/">How the stb_truetype Anti-Aliased Software Rasterizer v2 Works</a>.</p><p>Here’s a graph showing the breakdown:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/640/0*4P4ADDlba23irIld." /></figure><p>One interesting observation is the contrast between asymptotic complexity and associated constant factors. The accumulation buffer is O(n²), so allocating it and filling it with zeros is also quadratic. But it’s such a trivial operation that it hardly contributes. Drawing the path is O(n), but with a large constant factor because it’s doing complicated exact-area calculations for each pixel crossed by the outline. Finally, integrating the accumulation buffer is also O(n²), but insignificant at sizes up to 60 pixels/em or so. For very large glyphs, the quadratic parts would dominate.</p><h3>Parsing: no allocation</h3><p>Font-rs takes about 460ns to parse the binary font data and convert it into a sequence of lines and quadratic Bezier curves. There are 30 such, so it’s about 15ns per path element.</p><p>Usually, binary parsers are written as a function that accesses the binary font data, and produces a data structure (often a vector) containing the result. However, just collecting the result into a vector per contour would add 870ns, almost tripling the parsing time. At normal text sizes (where total rendering time is ~5µs), this added time would be significant.</p><p>By contrast, in Rust it is very idiomatic to write the parser as an <a href="https://doc.rust-lang.org/book/iterators.html"><em>iterator</em></a><em>, </em>an object with a `next` method called repeatedly. The state for these iterators is usually allocated on the stack, rather than on the heap. Thus, parsing takes place with absolutely no heap allocations.</p><p>The font-rs parser is actually written as two iterators, composed together. The first parses the binary font data into a sequence of on-curve and off-curve points, and the second converts that into lines and beziers. It’s possible to write Rust-style iterators in other languages, but it’s not especially common to see. Note, though, that there is a <a href="https://github.com/ericniebler/range-v3">library</a> for C++, intended to eventually become part of the standard.</p><h3>Drawing into the accumulation buffer: dense and immediate</h3><p>Different renderers use different strategies of how to store the accumulation buffer, and when to sequence drawing operations into it. Many renderers (including FreeType) use a <em>sparse</em> representation, trying to exploit the fact that many entries remain zero. Further, many renderers try to order the rendering by scanlines, or at least stripes. This requires some reordering, as the outlines are not stored in scanline order.</p><p>The approach of font-rs is to represent the accumulation buffer as a simple array, one 4-byte float per pixel. Thus, addressing the buffer is one machine instruction, with no branches. In addition, all drawing operations come straight from the parser into the buffer.</p><p>Sparseness is always a tradeoff. At a certain point, not having to touch empty parts of the structure exceeds the overhead from the sparse representation itself. Where this tradeoff crosses over depends on the problem and the size of the datasets. However, dense representations have a huge advantage when data-parallelism is an option, as we’ll see in the next section.</p><h3>Final integration: SIMD</h3><p>The integration consists of four operations, applied to every pixel: cumulative sum, absolute value (to implement the nonzero winding rule), clamping to [0, 1], and conversion to an 8-bit pixel. There are <a href="http://stackoverflow.com/questions/10587598/simd-prefix-sum-on-intel-cpu">SIMD techniques for cumulative sum</a>, and the other three are embarrassingly parallel. In addition, since cumulative sum is usually latency-limited, interleaving the two can be even faster than doing them separately.</p><p>Another innovation in font-rs is to just run cumulative sum over the entire buffer, rather than restarting a loop at every line. Thus, the inner loop runs very “hot” with basically no overhead due to branch mis-prediction. In addition, the width doesn’t need to be aligned to any power-of-2 boundary.</p><p>I wrote the inner loop in about <a href="https://github.com/google/font-rs/blob/master/src/accumulate.c">15 lines of SSSE3 intrinsics</a>. Since Rust doesn’t (yet) have SIMD intrinsic support, I wrote those in C. Fortunately, dropping a C function into a Rust codebase is <a href="https://github.com/google/font-rs/search?utf8=%E2%9C%93&amp;q=accumulate_sse">pretty easy</a>. At larger sizes, the speedup is dramatic, more than double. At smaller sizes, the impact is not so great, dominated by the O(n) path drawing.</p><h3>What next?</h3><p>Possibly font-rs becomes production-ready. This would depend on community interest. Another definite possibility is to adapt the ideas into existing renderers such as FreeType. In any case, font-rs shows what performance is possible. It’s also a strong demonstration that Rust is an appealing language for creating “fastest in class” modules. It has essentially the same performance as C++ for low-level fragments of code, but better support in the language and standard library for stitching these low-level fragments into higher level constructs that match the semantics of the problem being solved.</p><p>The future of fast rendering may not be software. New advances in GPU-based rendering, particularly <a href="https://github.com/Chlumsky/msdfgen">Multi-channel signed distance fields</a>, may yield even higher performance. However, those techniques usually require a time-consuming preprocessing step, and nontrivial memory to hold the intermediate bitmaps. For the task of going from a binary font to rendered pixel data, software renderers such as font-rs still deliver the absolute lowest end-to-end latency.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=75ae5270c445" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Towards a unified theory of Operational Transformation and CRDT]]></title>
            <link>https://medium.com/@raphlinus/towards-a-unified-theory-of-operational-transformation-and-crdt-70485876f72f?source=rss-c13af5dea630------2</link>
            <guid isPermaLink="false">https://medium.com/p/70485876f72f</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[crdt]]></category>
            <category><![CDATA[text-editor]]></category>
            <category><![CDATA[concurrency]]></category>
            <dc:creator><![CDATA[Raph Levien]]></dc:creator>
            <pubDate>Thu, 07 Jul 2016 04:33:19 GMT</pubDate>
            <atom:updated>2017-03-02T17:40:16.066Z</atom:updated>
            <cc:license>http://creativecommons.org/licenses/by/4.0/</cc:license>
            <content:encoded><![CDATA[<p><strong>Update (2 Mar 2017):</strong> there is now <a href="https://hackernoon.com/working-code-for-operational-transformation-crdt-hybrid-9d04a57309da">working code</a> for the ideas in this post, with additional optimizations.</p><p>Lately I’ve been diving deep into the literature on collaborative text editing. There are basically two opposing camps: Operational Transformation, which dates back to the 1989 <a href="https://www.lri.fr/~mbl/ENS/CSCW/2012/papers/Ellis-SIGMOD89.pdf">Grove</a> system and is the basis of most collaborative editors today, and the much newer Conflict-free Replicated Data Types (CRDT) approach. However, I’ve come to realize that there is a common theory which unifies at least a subclass of OT and CRDT’s. I believe the two camps have potentially much to learn from each other.</p><p>The so-called Transaction Property 2 (TP2) is something of a faultline running through the OT literature. A landmark 1996 paper by <a href="http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.27.3253&amp;rep=rep1&amp;type=pdf">Ressel et al</a> introduced the property, proved that it was necessary and sufficient for correctness of their system, then proceeded to propose a set of operations for editing strings that, in hindsight, did not satisfy TP2. Oops. For a decade or so, TP2 was something of a philosopher’s stone, with <a href="http://webloria.loria.fr/~rusi/pub/ecscw03.pdf">several</a> <a href="http://dl.acm.org/citation.cfm?id=267369">alchemists</a> <a href="http://dl.acm.org/citation.cfm?id=1031683">of</a> OT claiming that they had found a set of transforms satisfying TP2, only for counterexamples to <a href="https://arxiv.org/pdf/1302.3292.pdf">emerge</a> later. Only with the 2006 publication of <a href="http://www.loria.fr/~urso/uploads/Main/oster06collcom.pdf">Tombstone Transformation Functions</a> was actual gold struck, but at a cost; instead of doing insert and delete operations directly on the string as in the original OT vision, the document state is augmented with <em>tombstones</em> that record the locations where deletions would happen. A separate <em>read function</em> is required to erase the tombstones and yield the user-visible string.</p><p>Incidentally, the authors of the tombstone paper were, at the same time, working on <a href="https://hal.inria.fr/inria-00071240/document">WOOT</a> (“WithOut Operational Transformation”), which is now seen as an early instance of CRDT. The CRDT approach uses a simple merge function, basically a generalization of set union (more formally, monotonic operations over a semi-lattice, which has a commutative, associative, and idempotent <em>join</em> operator), and an even more complex read function to reconstruct the string from the document state. Even so, it is appealing because the simple merge function means that it is very easy to reason about convergence.</p><p>Almost all practical implementations of OT forego TP2, and solve the problem by limiting concurrency in some way, generally requiring a central server to decide on a canonical ordering of the operations (but still using transformations to let clients apply operations out-of-order just a little bit). In fact, practical OT today is based firmly on the 1995 <a href="http://lively-kernel.org/repository/webwerkstatt/projects/Collaboration/paper/Jupiter.pdf">Jupiter</a> system, with approximately the last 21 years of academic work having essentially no impact. I should mention Google Wave here, which restricts concurrency even more (only allowing a single operation from each client in flight). This approach simplifies the construction of servers, but still has basically the same concurrency and operation model as Jupiter. Jupiter and Wave are based on a strict serialization concurrency model, while both CRDT’s and OT with TP2 are eventually consistent. A good paper is <a href="http://software.imdea.org/~gotsman/papers/editing-podc16.pdf"><em>Specification and Complexity of Collaborative Text Editing</em></a><em>,</em> which also characterizes the former as satisfying the <em>weak list specification,</em> while the latter satisfies the <em>strong</em> counterpart.</p><p>Adopting TP2 eliminates the need for the central server, allows peer-to-peer configurations, and can reduce latency (including eliminating the need for Wave-style “batching” of operations). It’s possible to implement OT with TP2 reasonably simply and efficiently, but this fact doesn’t seem to be much appreciated in the oral tradition of people actually building OT systems. I’d like to try to change that, a bit.</p><p>An OT system satisfying TP2 has properties very similar to CRDT-based text editing. I’ll argue that this is no coincidence, that a unified theory underlies both.</p><p>I’ll try to explain an idealized OT system, closely based on <a href="http://www.collide.info/Lehre/SeminarWS0405/SunEllis98.pdf">GOTO</a> (Generic Operational Transformation Optimized) and <a href="http://dl.acm.org/citation.cfm?id=267369">SOCT2</a> (Sérialisation des Opérations Concurrentes par Transposition). This will necessarily be a high-level overview, and for a full understanding readers are directed to those papers (with considerable sympathy from me, the OT literature as a whole is difficult).</p><p>This OT system represents the document state as a <em>history log, </em>which is at heart a sequence of operations. The key to OT is that each operation has a <em>context,</em> basically the set of operations that precede it in the log, and that contexts always match up. When merging an operation from another node’s document state, the context in general won’t match up, so the operation needs to be <em>transformed.</em> The standard notation for this is IT(Oa, Ob), where Oa and Ob have the same context, yielding a transformed O’a. This transformed operation additionally has Ob in its context, but does the same thing as the original Oa. In this notation, IT stands for “Inclusion Transformation”. Early OT systems used just IT, but this approach requires keeping the original transformations, some explicit representation of context, and a lot of juggling.</p><p>Transformation of operations changes their contexts, but it also makes sense to track their <em>original context </em>(that is to say, not affected by the transformation). When one operation appears in the original context of a second, we say that the second is <em>causally dependent</em> on the first. When neither appears in the other, we say they are <em>concurrent.</em></p><p>SOCT2 and GOTO use a second transformation, often written ET (Exclusion Transform. It’s basically an inverse of IT, and <em>removes</em> one operation from an operation’s context. I prefer transpose(Oa, Ob), where the context of Ob is that of Oa plus Oa itself, yielding O’b and O’a in which the context relation is reversed. The context of O’a is that of O’b plus O’b itself. You can write transpose in terms of ET: transpose(Oa, Ob) = [ET(Ob, Oa), IT(Oa, ET(Ob, Oa))].</p><p>The idea of these two algorithms is that doing the transpose doesn’t affect the semantics of the history log as a whole. Also, you don’t need the original operations, so it can be done more simply and efficiently. The correctness of these algorithms relies on the fact that you only do the transpose on concurrent operations, that is to say all the permutation respects the partial order of original context. Of course, proving such a thing is not easy. Frankly, I didn’t really understand the proofs in those papers, and didn’t really trust that it was true until I did automated randomized testing on my own implementation.</p><p>It’s also important to note that the TP2 property is required for all this to work. Otherwise, you’ll find that while the transposes don’t affect the document, they do affect how an operation is transformed through a sequence of other operations. TP2 gives you exactly the guarantee you need: as long as the two operations are concurrent with respect to their original context, the transpose basically doesn’t change anything; there’s no way to observe the difference.</p><p>I’ll dive into a little more detail. Consider a peer-to-peer model where every node has its own copy of a document state. Each peer is continually sending updates to all its neighbors (the topology can be anything from a star to a spanning tree to a complete graph). The communication is assumed to be in-order, but of course is subject to unpredictable delays (essentially the same communication model of TCP/IP and WebSockets). To simplify, an update is considered appending one operation to the node’s document model.</p><p>When a node receives an update from another node, it’s already received all preceding updates (thanks to the in-order nature of the communication channel), and has merged these into its own document state. Its document state contains all operations of the other node’s document state, plus additional operations that the other node had not (yet) seen at the time it sent the update. If, in a lucky coincidence, these operations are clustered so all those in the second set come after those in the first, doing the transformations is easy, just do an IT transformation of the new operation through each of those in sequence. This figure shows it graphically:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*NBUotoqiF8oMtoJZ." /></figure><p>Here, the white squares represent operations common to both document states. The three light shaded squares are additional operations we have that the other node hasn’t seen. The dark shaded square is the new operation from the remote node that we want to integrate. As stated above, we just run this new operation through an IT transform of the three operations we have that the other node doesn’t. If you’re following along with the <a href="http://www.collide.info/Lehre/SeminarWS0405/SunEllis98.pdf">GOTO paper</a>, the sequential application of IT transforms is the LIT function, and the above picture corresponds to case 2 in their Algorithm 2.</p><p>The above is the case where these light-shaded operations are clustered together. Note that this can be enforced when there are only two concurrent users (basically the case that the original Grove system handled correctly). Things get more complex when there are three or more. The central idea of GOTO and SOCT2 is to <em>permute</em> the history log, using transpose operations as defined above, to establish that clustering. Without presenting the algorithm in detail (see the papers), the following figure should give a good idea what’s going on.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*CVsixghZ51K5WHj0." /></figure><p>Here, each point where two arrows cross is a transpose operation. If you’re following along with the <a href="http://www.collide.info/Lehre/SeminarWS0405/SunEllis98.pdf">GOTO paper</a>, each line in the above figure corresponds to one invocation of their LTranspose function, and the above picture corresponds to case 3a in their Algorithm 2.</p><p>The concept of transposing and permutation is also, I think, the key to analyzing these OT algorithms in the CRDT framework. Define an <em>equivalence relation: </em>two document states are equivalent iff one is a permutation of the other, and all swap operations comprising the permutation respect the partial order induced by original context. Under this equivalence relation, the merge operation on two document states satisfies the semilattice property required by CRDT; merge is commutative, associative, and idempotent. Proving that formally is an exercise for the reader, but it basically follows from the correctness proofs of GOTO and SOCT2 (again, the reader has my deepest sympathies).</p><p>There’s one small sleight-of-hand here. The equivalence relation above is defined on mathematical objects in which each operation contains its original context. Let’s say you’ve got two document states in which one user has typed “a” and the other user has typed “b”. If these operations happen concurrently, then the document states [Oa, O’b] and [Ob, O’a] are considered equivalent. However, if the second user waits for the first operation, then they’re not. The GOTO and SOCT2 algorithms don’t carry this extra metadata around in the document state. Even though that means they’re missing information needed to evaluate the equivalence relation, from a practical perspective I consider that a good thing. In fact, I’d say that one of the strongest criticisms of CRDT is that it generally requires a lot of metadata to build a tree or graph structure to make merging easier, which is then erased in the read function. Keeping that around requires more RAM and more communications bandwidth, compared to more OT-flavored approaches.</p><p>You could hand-wave and say that a theoretical model of GOTO/SOCT2 that includes the context metadata is a CRDT, and that erasing this data is merely an implementation detail, but to me that’s unsatisfying. If the definition of the equivalence relation includes the original context, then the document state of a GOTO/SOCT2 algorithm is not a CRDT under that definition.</p><p>There is an alternative, which I personally find more appealing. Instead of saying that two operations can only be transposed if they were in fact concurrent, loosen the definition. Basically, if the history can be explained by two concurrent operations, allow the swap. In the example above, let’s say that the tie-breaking rules place edits from user A before those of user B on concurrent insert. So “a” and “b” entered concurrently result in “ab”. Allow “ab” to be swapped whether the operations were in fact concurrent or not, as there is no difference in the observable state. There are no two <em>concurrent</em> operations that can yield “ba”, so that pair would still not be swappable.</p><p>More formally, consider the sequence [Oa, Ob] to be transposable iff IT(Oa, O’b) = O’a, and IT(O’b, O’a) = Ob, where [O’a, O’b] = transpose(Oa, Ob). In the “ba” example above, this would fail, as Oa and O’b would be concurrent inserts of “a” and “b” at the same location, and the tie-breaking rule in IT would resolve those to “ab”, not a match.</p><p>Since this new equivalence relation is defined entirely on information actually contained in the document state, the GOTO/SOCT2 merge operation can then rightfully be considered a CRDT (assuming no correctness mistakes, of course).</p><p>Further, I’d say that the looser equivalence relation is actually useful, not just a mathematical trick. For example, you can permute the document state so that it compresses better. If two users are concurrently typing in distant parts of the same document, then the original context induces nearly a total order (actually a total order if latency is fast enough), but in reality the edits of one user can be transposed with the edits of the other without harm. The looser equivalence relation lets you do that.</p><p>What of non-TP2 operational transformation? I’ve thought about ways of jamming that into the CRDT framework. In particular, you can attach a timestamp to each operation, then use a read function to play the operations back in timestamp order. In practice, while this might be a CRDT in some technical sense, it’s a really bad one. If you integrate an operation with an early timestamp relative to the rest of your buffer, then it can force replay of a lot of operations, which is both slow and can make weird changes to the document. In particular, situations like TP2 puzzles (see Fig. 3 of the <a href="http://www.loria.fr/~urso/uploads/Main/oster06collcom.pdf">tombstone paper</a> for a great explanation) can cause the reordering of big chunks of text, which would usually be a terrible experience. If these are your transformation functions, it’s better to rely on a central server to actually limit concurrency so that doesn’t happen.</p><p>Thus, I assert that the TP2-conforming subclass of OT can be considered an instance of CRDT, and so the OT and CRDT camps aren’t as far apart as maybe they first seemed. The OT camp offers efficient implementation techniques, in particular teaching that retaining graph-structure metadata is not absolutely required. In turn, the CRDT camp offers a sounder basis for mathematical reasoning (the track record of the OT literature certainly demonstrates that it is very difficult to reason about the correctness of OT transformations) and a potentially rich set of techniques for composing and combining CRDTs.</p><p>I hope this blog post suggests ways for the OT and CRDT communities to work together. I’d also encourage designers of collaborative editing systems (and academic researchers) hoping for the benefits of CRDT (in particular, peer-to-peer operation rather than requiring a centralized server) to take another look at OT. Just because there are so many flawed proposals in the literature is not a good reason to write off the entire field in favor of the seemingly greener pastures of CRDT.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=70485876f72f" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>