<?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:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Seph]]></title><description><![CDATA[Seph]]></description><link>https://josephg.com/blog/</link><generator>Ghost 0.11</generator><lastBuildDate>Mon, 01 Sep 2025 21:03:32 GMT</lastBuildDate><atom:link href="https://josephg.com/blog/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Rewriting Rust]]></title><description><![CDATA[<p>The Rust programming language feels like a first generation product.</p>

<p>You know what I mean. Like the first iPhone - <a href="https://www.youtube.com/watch?v=MnrJzXM7a6o">which was amazing by the way</a>. They made an entire operating system around multitouch. A smart phone with no keyboard. And a working web browser. Within a few months, we</p>]]></description><link>https://josephg.com/blog/rewriting-rust/</link><guid isPermaLink="false">7f47c10c-b29d-4c66-9989-1b51a72193d1</guid><category><![CDATA[rust]]></category><category><![CDATA[programming]]></category><dc:creator><![CDATA[Joseph Gentle]]></dc:creator><pubDate>Thu, 26 Sep 2024 05:05:19 GMT</pubDate><content:encoded><![CDATA[<p>The Rust programming language feels like a first generation product.</p>

<p>You know what I mean. Like the first iPhone - <a href="https://www.youtube.com/watch?v=MnrJzXM7a6o">which was amazing by the way</a>. They made an entire operating system around multitouch. A smart phone with no keyboard. And a working web browser. Within a few months, we all realised what the iPhone really wanted to be. Only, the first generation iphone wasn't quite there. It didn't have 3G internet. There was no GPS chip. And there was no app store. In the next few years, iPhones would get a lot better.</p>

<p>Rust feels a bit like that first iPhone.</p>

<p>I fell in love with Rust at the start. Algebraic types? Memory safety without compromising on performance? A modern package manager? Count me in. But now that I've been programming in rust for 4 years or so, it just feels like its never quite there.</p>

<p>And I don't know if it will ever be there. Progress on the language has slowed <em>so much</em>. When I first started using it, every release seemed to add new, great features in stable rust. Now? Crickets. The <a href="https://doc.rust-lang.org/unstable-book/the-unstable-book.html">rust "unstable book"</a> lists <em>700</em> different unstable features - which presumably are all implemented, but which have yet to be enabled in stable rust. Most of them are changes to the standard library - but seriously. Holy cow.</p>

<p>How much of this stuff will <em>ever</em> make it into the language proper? The rust RFC process is a graveyard of good ideas.</p>

<p>Features like <a href="https://doc.rust-lang.org/unstable-book/language-features/coroutines.html">Coroutines</a>. This RFC is 7 years old now. Make no mistake - coroutines are implemented in the compiler. They're just, not available for us "stable rust" peasants to use. If coroutines were a child, they would be in grade school by now. At this point, the coroutines RFC has lasted longer than World War 1 or 2.</p>

<p>I suspect rust is calcifying because its consensus process just doesn't scale. Early on, rust had a small group of contributors who just <em>decided</em> things. The monsters. Now, there are issue threads like <a href="https://github.com/rust-lang/rust/issues/93740#issuecomment-1041391284">this</a>, in which 25 smart, well meaning people spent 2 years and over 200 comments trying to figure out how to improve <code>Mutex</code>. And as far as I can tell, in the end they more or less gave up.</p>

<p>Maybe this is by design. Good languages are stable languages. It might be time to think of rust as a fully baked language - warts and all. Python 2.7 for life.</p>

<p>But that doesn't change anything for me. I want a better rust, and I feel powerless to make that happen. Where are my coroutines? Even javascript has <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*">coroutines</a>.</p>

<h2 id="fantasylanguage">Fantasy language</h2>

<p>Sometimes I lie awake at night fantasising about forking the compiler. I know how I'd do it. In my fork, I'd leave all the rust stuff alone and but make my own "seph" <a href="https://doc.rust-lang.org/edition-guide/editions/">edition</a> of the rust language. Then I could add all sorts of breaking features to that edition. So long as my compiler still compiles mainline rust as well, I could keep using all the wonderful crates on Cargo.</p>

<p>I think about this a lot. If I did it, here's what I'd change:</p>

<h3 id="functiontraitseffects">Function traits (effects)</h3>

<p>Rust has traits on structs. These are used in all sorts of ways. Some are markers. Some are understood by the compiler (like <code>Copy</code>). Some are user defined.</p>

<p>Rust should also define a bunch of traits for functions. In other languages, function traits are called "effects".</p>

<p>This sounds weird at first glance - but hear me out. See, there's lots of different "traits" that functions have. Things like:</p>

<ul>
<li>Does the function ever panic?</li>
<li>Does the function have a fixed stack size?</li>
<li>Does the function run to the end, or does it yield / await?</li>
<li>If the function is a coroutine, what is the type of the continuation?</li>
<li>Is the function "pure" (ie, the same input produces the same output, and it has no side effects)</li>
<li>Does the function (directly or indirectly) run unsafe code in semi-trusted libraries?</li>
<li>Is the function guaranteed to terminate?</li>
</ul>

<p>And so on.</p>

<p>A function's parameters and return type are just associated types on the function:</p>

<pre><code class="language-rust">fn some_iter() -&gt; impl Iterator&lt;Item = usize&gt; {  
    vec![1,2,3].into_iter()
}

fn main() {  
    // Why doesn't this work already via FnOnce?
    let x: some_iter::Output = some_iter();
}
</code></pre>

<p><a href="https://rust-lang.github.io/rfcs/2515-type_alias_impl_trait.html">TAIT</a> eat your heart out.</p>

<p>Exposing these properties is super useful. For example, the linux kernel wants to guarantee (at compile time) that some block of code will never panic. This is impossible to do in rust today. But using function traits, we could explicitly mark a function as being able - or unable - to panic:</p>

<pre><code class="language-rust">#[disallow(Panic)] // Syntax TBD.
fn some_fn() { ... }  
</code></pre>

<p>And if the function does anything which could panic (even recursively), the compiler would emit an error.</p>

<p>The compiler already sort of implements traits on functions, like <code>Fn</code>, <code>FnOnce</code> and <code>FnMut</code>. But for some reason they're anemic. (Why??)</p>

<p>I want something like this:</p>

<pre><code class="language-rust">/// Automatically implemented on all functions.
trait Function {  
  type Args,
  type Output,
  type Continuation, // Unit type () for normal functions
  // ... and so on.

  fn call_once(self, args: Self::Args) -&gt; Self::Output;
}

trait NoPanic {} // Marker trait, implemented automatically by the compiler.

/// Automatically implemented on all functions which don't recurse.
trait KnownStackSize {  
  const STACK_SIZE: usize,
}
</code></pre>

<p>Then you could write code like this:</p>

<pre><code class="language-rust">fn some_iter() -&gt; impl Iterator&lt;Item = usize&gt; {  
  vec![1,2,3].into_iter();
}

struct SomeWrapperStruct {  
  iter: some_iter::Output, // In 2024 this is still impossible in stable rust.
}
</code></pre>

<p>Or with coroutines:</p>

<pre><code>coroutine fn numbers() -&gt; impl Iterator&lt;Item = usize&gt; {  
  yield 1;
  yield 2;
  yield 3;
}

coroutine fn double&lt;I: Iterator&lt;Item=usize&gt;&gt;(inner: I) -&gt; impl Iterator&lt;Item = usize&gt; {  
  for x in inner {
    yield x * 2;
  }
}

struct SomeStruct {  
  // Suppose we want to store the iterator. We can name it directly:
  iterator: double&lt;numbers&gt;::Continuation,
}
</code></pre>

<p>Or, say, take a function parameter but require that the parameter itself doesn't panic:</p>

<pre><code class="language-rust">fn foo&lt;F&gt;(f: F)  
    where F: NoPanic + FnOnce() -&gt; String
{ ... }
</code></pre>

<p>Yoshua Wuyts has an excellent <a href="https://blog.yoshuawuyts.com/extending-rusts-effect-system/">talk &amp; blog post</a> going into way more detail about effects - why they're useful and how this could work.</p>

<h3 id="compiletimecapabilities">Compile-time Capabilities</h3>

<p>Most rust projects pull in an insane number of 3rd party crates. Most of these crates are small utility libraries - like the <a href="https://crates.io/crates/human-size"><code>human-size</code></a> crate which formats file sizes for human consumption. Great stuff! But unfortunately, all of these little crates add supply chain risk. Any of those authors could push out an update which contains malicious code - cryptolockering our computers, our servers or sneaking bad code into our binaries.</p>

<p>I think this problem is similar to the problem of memory safety. Sure - its sometimes useful to write memory-unsafe code. The rust standard library is full of it. But rust's <code>unsafe</code> keyword lets authors opt in to potentially unsafe things. We only add <code>unsafe</code> blocks when its necessary.</p>

<p>Lets do the same thing for privileged function calls - like reading and writing to and from the filesystem or the network. This is useful stuff, but its potentially dangerous. Developers should actively whitelist code that is allowed to call these functions.</p>

<p>To implement this, first we want to add marker traits to all the security-sensitive functions in the standard library (opening a file from a string, <code>exec</code>, FFI, opening network connections, most unsafe functions that interact with raw pointers, and so on). So, for example, <a href="https://doc.rust-lang.org/std/fs/fn.write.html"><code>std::fs::write(path, contents)</code></a> writes to an arbitrary path on disk with the credentials of the user. We add some <code>#[cap(fs_write)]</code> marker tag to the function itself, marking that this can only be called from code which is in some way trusted. The compiler automatically "taints" any other functions which call <code>write</code> in the entire call tree.</p>

<p>Suppose I call a function in a 3rd party crate which needs the <code>fs_write</code> capability. In order to call that function, I need to explicitly whitelist that call. (Either by adding the permission explicitly in my <code>Cargo.toml</code> or maybe with an annotation at the call site).</p>

<p>So, lets say the <code>foo</code> crate contains a function like this. The function will be marked (tainted) with the "writes to filesystem" tag:</p>

<pre><code class="language-rust">// In crate `foo`.

// (this function is implicitly tagged with #[cap(fs_write)])
pub fn do_stuff() {  
  std::fs::write("blah.txt", "some text").unwrap();
}
</code></pre>

<p>When I try to run that function from my code:</p>

<pre><code class="language-rust">fn main() {  
  foo::do_stuff();
}
</code></pre>

<p>The compiler can give me a nice rusty error, like this:</p>

<pre><code>Error: foo::do_stuff() writes to the local filesystem, but the `foo` crate has not been trusted with this capability in Cargo.toml.

Tainted by this line in do_stuff:

  std::fs::write("blah.txt", "some text").unwrap();

Add this to your Cargo.toml to fix:

foo = { version = "1.0.0", allow_capabilities: ["fs_write"] }  
</code></pre>

<p>Obviously, most uses of <code>unsafe</code> would also require explicit whitelisting.</p>

<p>Most crates I use - like <code>human-size</code> or <code>serde</code> don't need any special capabilities to work. So we don't need to worry so much about their authors "turning evil" and adding malicious code to our software. Reducing the supply chain risk from the 100 or so crates I currently transitively depend on down to just a few would be massive.</p>

<p>This is a very simple, static way that capabilities could be introduced to Rust. But it might be possible &amp; better to change privileged code to require an extra <code>Capability</code> parameter (some unit struct type). And heavily restrict how <code>Capability</code> objects can be instantiated. Eg:</p>

<pre><code class="language-rust">struct FsWriteCapability;

impl FsWriteCapability {  
    fn new() { Self } // Only callable from the root crate
}

// Then change std::fs::write's signature to this:
pub fn write(path: Path, contents: &amp;[u8], cap: FsWriteCapability) { ... }  
</code></pre>

<p>This requires more boilerplate, but its much more flexible. (And obviously, we'd also need to, somehow, apply a similar treatment to <code>build.rs</code> scripts and <code>unsafe</code> blocks.)</p>

<p>The result of all of this is that utility crates become "uncorruptable". Imagine if crates.io is hacked and serde is maliciously updated to include with cryptolocker code. Today, that malicious code would be run automatically on millions of developer machines, and compiled into programs everywhere. With this change, you'd just get a compiler error.</p>

<p>This is huge, and singlehandedly this one feature is probably worth the cost of forking rust. At least, to someone. (Anyone want to sponsor this work?)</p>

<h3 id="pinmoveandstructborrows">Pin, Move and Struct Borrows</h3>

<blockquote>
  <p>Feel free to skip this section if Pin &amp; the borrow checker gives you a migraine.</p>
</blockquote>

<p><code>Pin</code> in rust is a weird, complicated hack to work around a hole in the borrow checker. Its a band-aid from the land of bizzaro choices that only make sense when you need to maintain backwards compatibility at all costs.</p>

<ul>
<li>Its the reverse of the trait you actually want. It would make way more sense to have a <code>Move</code> marker trait (like <code>Copy</code>) indicating objects which <em>can</em> move.</li>
<li>But <code>Pin</code> isn't an actual trait. There's only <code>Unpin</code> (double negative now) and <code>!Unpin</code> - which is not-not-not-<code>Move</code>. For example <a href="https://doc.rust-lang.org/1.81.0/src/core/marker.rs.html#923"><code>impl !Unpin for PhantomPinned</code></a>. Is <code>!Unpin</code> the same as <code>Pin</code>? Uhhhh, ... No? Because .. reasons? I get an instant headache when I think about this stuff. Here's the <a href="https://doc.rust-lang.org/std/marker/trait.Unpin.html">documentation for Unpin</a> if you want to try your luck.</li>
<li>Pin only applies to reference types. If you read through code which uses <code>Pin</code> a lot, you'll find unnecessary <code>Box</code>-ing of values <em>everywhere</em>. For example, <a href="https://docs.rs/tokio-stream/latest/src/tokio_stream/wrappers/broadcast.rs.html#11-18">in tokio</a>, or helper libraries like <a href="https://lib.rs/crates/ouroboros">ouroboros</a>, <a href="https://docs.rs/async-trait/latest/async_trait/">async<em>trait</em></a> and <a href="https://docs.rs/self_cell/latest/self_cell/">selfcell</a>.</li>
<li>The pain spreads. Any function that takes a pinned value needs the value wrapped using some horrible abomonation <a href="https://doc.rust-lang.org/std/future/trait.Future.html">like <code>Future::poll(self: Pin&lt;&amp;mut Self&gt;, ..)</code></a>. And then you need to figure out how to read the actual values out using projections, which are so complicated there are <a href="https://docs.rs/pin-project/latest/pin_project/">multiple</a> <a href="https://crates.io/crates/pin-project-lite/">crates</a> for dealing with them. The pain cannot be confined. It spreads outwards, forever, corrupting everything.</li>
</ul>

<p>I swear, it took more effort to learn pinning in rust than it took me to learn the entire Go programming language. And I'm still not convinced I'm totally across it. And I'm not alone. I've heard the <a href="https://fuchsia.dev/">Fuchsia operating system project</a> abandoned Rust for C++ in some parts because of how impossibly complex Pin makes everything.</p>

<p>Why is <code>Pin</code> needed, anyway?</p>

<p>We can write rust functions like this:</p>

<pre><code class="language-rust">fn main() {  
    let x = vec![1,2,3];
    let y = &amp;x;

    //drop(x); // error[E0505]: cannot move out of `x` because it is borrowed
    dbg!(y);
}
</code></pre>

<p>All variables in a rust function are actually, secretly in one of 3 different states:</p>

<ul>
<li>Normal (owned)</li>
<li>Borrowed</li>
<li>Mutably borrowed</li>
</ul>

<p>While a variable is borrowed (<code>y = &amp;x</code>), you can't move, mutate or drop the variable. In this example, <code>x</code> is put into a special "borrowed" state throughout the lifetime of <code>y</code>. Variables in the "borrowed" state are pinned, immutable, and have a bunch of other constraints. This "borrowed state" is visible to the compiler, but its completely invisible to the programmer. You can't tell that something is borrowed until you try to compile your program. (Aside: I wish Rust IDEs made this state visible while programming!)</p>

<p>But at least this program <em>works</em>.</p>

<p>Unfortunately, there's no equivalent to this for structs. Lets turn the function <code>async</code>:</p>

<pre><code class="language-rust">async fn foo() {  
    let x = vec![1,2,3];
    let y = &amp;x;

    some_future().await;

    dbg!(y);
}
</code></pre>

<p>When you compile this, the compiler creates a hidden struct for you, which stores the suspended state of this function. It looks something like this:</p>

<pre><code class="language-rust">struct FooFuture {  
  x: Vec&lt;usize&gt;,
  y: &amp;'_ Vec&lt;usize&gt;,
}

impl Future for FooFuture { ... }  
</code></pre>

<p><code>x</code> is borrowed by <code>y</code>. So it needs to be placed under all the constraints of a borrowed variable:</p>

<ul>
<li>It must not move in memory. (It needs to be Pinned)</li>
<li>It must be immutable</li>
<li>We can't take mutable references to <code>x</code> (because of the &amp; xor &amp;mut rule).</li>
<li><code>x</code> must outlive <code>y</code>.</li>
</ul>

<p>But there's no syntax for this. Rust doesn't have syntax to mark a struct field as being in a borrowed state. And we can't express the lifetime of <code>y</code>.</p>

<p>Remember: the rust compiler already generates and uses structs like this whenever you use <code>async</code> functions. The compiler just doesn't provide any way to write code like this ourselves. Lets just extend the borrow checker and fix that!</p>

<p>I don't know what the ideal syntax would be, but I'm sure we can come up with something. For example, maybe <code>y</code> gets declared as a "local borrow", written as <code>y: &amp;'Self::x Vec&lt;usize&gt;</code>. The compiler uses that annotation to figure out that <code>x</code> is borrowed. And it puts it under the same set of constraints as a borrowed variable inside a function.</p>

<p>This would also let you work with self-referential structs, like an <a href="https://en.wikipedia.org/wiki/Abstract_syntax_tree">Abstract Syntax Tree (AST)</a> in a compiler:</p>

<pre><code class="language-rust">struct Ast {  
  source: String,
  ast_nodes: Vec&lt;&amp;'Self::source str&gt;,
}
</code></pre>

<p>This syntax could also be adapted to support partial borrows:</p>

<pre><code class="language-rust">impl Foo {  
  fn get_some_field&lt;'a&gt;(&amp;'a self) -&gt; &amp;'a::some_field usize {
    &amp;self.some_field
  }
}
</code></pre>

<p>This isn't a complete solution.</p>

<p>We'd also need a <code>Move</code> marker trait, to replace <code>Pin</code>. Any struct with borrowed fields can't be Moved - so it wouldn't have <code>impl Move</code>. I'd also consider a <code>Mover</code> trait, which would allow structs to intelligently move themselves in memory. Eg:</p>

<pre><code class="language-rust">trait Mover {  
  // Something like that.
  unsafe fn move(from: *Self, to: MaybeUninit&lt;&amp;mut Self&gt;);
}
</code></pre>

<p>We'd also need a sane, safe way to construct structs like this in the first place. I'm sure we can do better than <code>MaybeUninit</code>.</p>

<p>Miguel Young de la Sota <a href="https://www.youtube.com/watch?v=UrDhMWISR3w">gave a fantastic talk a few years ago</a> talking about <code>Move</code> in rust. But I think it would be much more "rusty" to lean on the borrow checker instead.</p>

<p>If you ask me, <code>Pin</code> is a dead end solution. Rust already has a borrow checker. Lets use it for structs.</p>

<h3 id="comptime">Comptime</h3>

<p>This is a hot opinion. I haven't spent a lot of time with zig, but at least from a distance I adore <a href="https://zig.guide/language-basics/comptime/">comptime</a>.</p>

<p>In the rust compiler we essentially implement two languages: Rust and the Rust Macro language. (Well, arguably there's 3 - because proc macros). The Rust programming language is lovely. But the rust macro languages are horrible.</p>

<p>But, if you already know rust, why not just use rust itself instead of sticking another language in there? This is the genius behind Zig's <code>comptime</code>. The compiler gets a little interpreter tacked on that can run parts of your code at compile time. Functions, parameters, if statements and loops can all be marked as compile-time code. Any non-comptime code in your block is emitted into the program itself.</p>

<p>I'm not going to explain the feature in full here. Instead, take in just how <em>gorgeous</em> this makes Zig's <a href="https://ziglang.org/documentation/master/#Case-Study-print-in-Zig">std <code>print</code> function</a>.</p>

<p>Its entirely implemented using comptime. So when you write this in zig:</p>

<pre><code class="language-zig">pub fn main() void {  
    print("here is a string: '{s}' here is a number: {}\n", .{ a_string, a_number });
}
</code></pre>

<p><code>print</code> takes the format string as a comptime parameter, and parses it within a <code>comptime</code> loop. Aside from a couple keywords, the function is just regular zig code - familiar to anyone who knows the language. It just gets executed within the compiler. And the result? It emits this beauty:</p>

<pre><code class="language-zig">pub fn print(self: *Writer, arg0: []const u8, arg1: i32) !void {  
    try self.write("here is a string: '");
    try self.printValue(arg0);
    try self.write("' here is a number: ");
    try self.printValue(arg1);
    try self.write("\n");
    try self.flush();
}
</code></pre>

<p>Read the <a href="https://ziglang.org/documentation/master/#Case-Study-print-in-Zig">full case study</a> for more details.</p>

<p>In comparison, I tried to look up how rust's <code>println!()</code> macro is implemented. But <a href="https://doc.rust-lang.org/src/std/macros.rs.html#138-145">println! calls some secret <code>format_args_nl</code> function</a>. I assume that function is hardcoded in the rust compiler itself.</p>

<p>Its not a great look when even the rust compiler authors don't want to use rust's macro language.</p>

<h3 id="weirdlittlefixes">Weird little fixes</h3>

<p>Bonus round time. Here's some other little "nits" I'd love to fix while we're at it:</p>

<ul>
<li><code>impl&lt;T: Copy&gt; for Range&lt;T&gt;</code>. If you know, you know.</li>
<li>Fix <a href="https://github.com/rust-lang/rust/issues/26925">derive with associated types</a>. <a href="https://play.rust-lang.org/?version=stable&amp;mode=debug&amp;edition=2021&amp;gist=6fd2c813f411f6eb1abb66a473425c89">Full example here</a>.</li>
<li>Make if-let expressions support logical AND. Its so simple, so obvious, and so useful. This should work:</li>
</ul>

<pre><code class="language-rust">// Compile error! We can't have nice things.
if let Some(x) = some_var &amp;&amp; some_expr { }  
</code></pre>

<p>You can sort of work around this problem today as below, but its awkward to write, hard to read and the semantics are different from how normal <code>if</code> statements work because it lacks short-circuit evaluation.</p>

<pre><code class="language-rust">// check_foo() will run even if some_var is None.
if let (Some(x), true) = (some_var, check_foo()) { ... }  
</code></pre>

<p><a href="https://play.rust-lang.org/?version=stable&amp;mode=debug&amp;edition=2021&amp;gist=e4c4521e6a0ab49462c0b9d55da97480">Full example here</a>.</p>

<p>Rust's ergonomics for raw pointers are also uniquely horrible. When I work with unsafe code, my code should be as easy to read &amp; write as humanly possible. But the rust compiler seems intent on punishing me for my sins. For example, if I have a reference to a struct in rust, I can write <code>myref.x</code>. But if I have a pointer, rust insists that I write <code>(*myptr).x</code> or, worse: <code>(*(*myptr).p).y</code>. Horrible. Horrible and entirely counterproductive. Unsafe code should be clear.</p>

<p>I'd also change all the built in collection types to take an <code>Allocator</code> as a constructor argument. I personally don't like Rust's decision to use a global allocator. Explicit is better than implicit.</p>

<h2 id="closingthoughts">Closing thoughts</h2>

<p>Thats all the ideas I have. I mean, async needs some love too. But there's so much to say on the topic that async deserves a post of its own.</p>

<p>Unfortunately, most of these changes would be incompatible with existing rust. Even adding security capabilities would require a new rust edition, since it introduces a new way that crates can break semver compatibility.</p>

<p>A few years ago I would have considered writing RFCs for all of these proposals. But I like programming more than I like dying slowly in the endless pit of github RFC comments. I don't want months of work to result in yet another idea in <a href="https://doc.rust-lang.org/reference/items/associated-items.html">rust's landfill of unrealised dreams</a>.</p>

<p>Maybe I should fork the compiler and do it myself. Urgh. So many projects. If I could live a million lifetimes, I'd devote one to working on compilers.</p>]]></content:encoded></item><item><title><![CDATA[NodeJS packages don't deserve your trust]]></title><description><![CDATA[<h2 id="amodestproposal">A modest proposal</h2>

<p>Another week, another <a href="https://github.com/Yaffle/EventSource/blob/de137927e13d8afac153d2485152ccec48948a7a/src/eventsource.js#L1047-L1090">npm supply chain attack</a>. Yikes! People on hacker news are <a href="https://news.ycombinator.com/item?id=30963600">wringing their hands</a> about what should be done. The problem seems dire.</p>

<p>Apparently I couldn't help myself. At 2am the other night I woke up, staring at the ceiling. I couldn't stop thinking about</p>]]></description><link>https://josephg.com/blog/node-sandbox/</link><guid isPermaLink="false">7e86b4ac-86e7-4296-a3fd-f47afd04c989</guid><dc:creator><![CDATA[Joseph Gentle]]></dc:creator><pubDate>Mon, 11 Apr 2022 07:30:36 GMT</pubDate><content:encoded><![CDATA[<h2 id="amodestproposal">A modest proposal</h2>

<p>Another week, another <a href="https://github.com/Yaffle/EventSource/blob/de137927e13d8afac153d2485152ccec48948a7a/src/eventsource.js#L1047-L1090">npm supply chain attack</a>. Yikes! People on hacker news are <a href="https://news.ycombinator.com/item?id=30963600">wringing their hands</a> about what should be done. The problem seems dire.</p>

<p>Apparently I couldn't help myself. At 2am the other night I woke up, staring at the ceiling. I couldn't stop thinking about this problem. It seems .. frankly, solvable. But how?</p>

<p>I think I came up with an answer. Or, the sketch of an answer. Is it any good? Will it work? I think it might... You be the judge.</p>

<h3 id="theproblem">The problem</h3>

<p>The fundamental problem with npm is that any package you install has full access to do whatever it wants on your computer. For example, packages can:</p>

<ul>
<li>Read every file on your computer, including your email, passwords, everything.</li>
<li>Edit your files. Delete them. Cryptolocker them</li>
<li>Do anything it wants on the internet</li>
<li>Run child processes, change your OS settings, install key loggers</li>
</ul>

<p>You think you're installing <code>leftpad</code>. But you're actually letting a stranger into your house while you aren't at home. They can do basically whatever they want.</p>

<p>And its not just your home. We give package authors full access to our servers and our webpages. These systems store something much more precious: Our users' personal data.</p>

<p>Most people are trustworthy. But occasionally people <a href="https://snyk.io/blog/peacenotwar-malicious-npm-node-ipc-package-vulnerability/">decide that if you're in Russia or Belarus, wiping your hard drive is fair play</a>. And if you let literally thousands of unknown people into your house unattended, its no surprise when someone does something you don't like. Frankly, I'm surprised supply chain attacks don't happen more often.</p>

<p>We can't solve this by figuring out all the baddies and banning them. I learned this as a kid in the 90s playing a video game called <em>Theme Park</em>. Once you played it enough, some park visitors would start vandalizing the park. I remember reading a strategy guide which said "You can't just hire a security guard and put them at the front gate. Security guards can only kick out visitors <em>after</em> they've broken the rules."</p>

<p>We have the same problem. We can't preemptively figure out which developers don't deserve our trust.</p>

<p>Deno <a href="https://deno.land/manual/getting_started/permissions">tries to solve this problem</a>, but I don't think its good enough. Deno lets you specify at the command line what kinds of actions your program is allowed to perform. You need to explicitly give permission to your deno process to have access to the internet or your database files.</p>

<p>This is a start; but I don't think its good enough. Just because I'm making a web server, that doesn't mean <code>leftpad</code> should be allowed to access the internet. If I'm making a file server, the <code>leftpad</code> library shouldn't have access to my filesystem. Deno's permission model is a good start, but it just isn't fine grained enough. (That said, I'd certainly take it over nodejs's current approach.)</p>

<h2 id="capabilitiestotherescue">Capabilities to the rescue</h2>

<p>I think we can solve this problem entirely. But it might require some changes to how nodejs works.</p>

<p>I'm taking inspiration here from an OpenBSD API called <a href="https://man.openbsd.org/pledge.2">pledge</a>. The way pledge works is that, when the program starts but before your program has done anything, you make a set of pledges. "I promise this program will not access any files outside of <code>/some/path</code>, or make any network connections to peers except for <code>example.com</code>. If the program is later compromised, none of the compromised code can do anything nasty.</p>

<p>But I think we can take this a bit further. Here's my idea:</p>

<ol>
<li>We add a new builtin nodejs library called <a href="https://en.wikipedia.org/wiki/Capability-based_security"><code>capabilities</code></a>, which can hand out capability tokens. Capability tokens can only be created by the capabilities library.  </li>
<li>To make any privileged action (access the filesystem, the network, hardware, spawn child processes, load native npm modules, etc!), the caller needs to pass in an appropriate capability token. Most functions in <code>fs</code>, <code>net</code>, <code>child_process</code> and others will need a capability field added. Most of these methods already take an options object, so it shouldn't be too hard to add a capability token there.  </li>
<li>Every capability token has a <em>scope</em>. The scope specifies what the bearer of that token is allowed to do. For example, a capability might give you read/write access to the <code>/var/data</code> directory. The capability library lets you narrow a capability, but capabilities can never be widened. So if a library has a capability for arbitrary network access, it can create a <em>narrowed token</em> which only has network access to your database server. That capability can be then passed to the database client library.  </li>
<li>When your program launches, your main package (and only your main package!) gets access to a wildcard "do anything" capability. You can narrow &amp; pass this capability token to other packages, depending on what you want them to do.</li>
</ol>

<p>So, something like this:</p>

<pre><code class="language-javascript">// server.js
const cap = require('capabilities')  
const express = require('express')

const rootToken = cap.claimRootToken() // More on this below

const httpServerToken = cap.narrow(rootToken, {  
  // Scope of the new token
  net: {kind: 'listen', address: 'localhost'}
})

const app = express()  
app.get('/', (req, res) =&gt; {  
  res.send('Welcome to my lair of funk')
})

app.listen(4321, {  
  // If we don't pass a token, express can't function!
  token: httpServerToken
})
</code></pre>

<p>Express doesn't need to do anything fancy with the capability token. It just passes it to the <code>http</code> library behind the scenes. Whats new is that lots of things are <em>not</em> in the token. Because the token we passed <em>only</em> allows network access, express is banned from reading your filesystem, opening new network connections, running shell scripts, or really anything dangerous that we haven't explicitly allowed.</p>

<p>There's lots of things to nut out here, but I've put a simple sketch of what the capability module might look like at the bottom of this post.</p>

<p>Unfortunately, its not that simple. There are a few other thorny details to figure out too!</p>

<h2 id="whataboutexistingcode">What about existing code?</h2>

<p>We make the entire capability system opt in at the command line level. If you don't pass <code>--strict-capabilities</code>, then nodejs works like it does now, where any script can do anything.</p>

<p>Production web servers should enable this flag, but existing code should keep working.</p>

<h2 id="howwouldyourrootpackagegettherootcapabilitytoken">How would your root package get the root capability token?</h2>

<p>The first idea is something simple like this:</p>

<pre><code class="language-javascript">import * as cap from 'capabilities'

// This method can only be called once
const rootToken = cap.claimRootToken()  
</code></pre>

<p>But the danger of this approach is that attackers can run code before we get the root token. And if they can do that, they can probably get the root access token themselves and do nasty stuff.</p>

<pre><code class="language-javascript">import * as cap from 'capabilities'  
import 'attackers_code'

const rootToken = cap.claimRootToken()  
</code></pre>

<p>Unfortunately, ES modules require all <code>import</code> statements to be at the top of your file, before any code executes. You could work around this restriction by importing a local file first, which immediately claims the root token. But thats super awkward. I don't want a hello world web server to need (at a minimum) 2 source code files.</p>

<p>There might be a way to fix that with some weird ES6 getters, or by some deep V8 wizardry or something:</p>

<pre><code class="language-javascript">import {rootToken} from 'capabilities' // rootToken only be *imported* once? Is this possible?  
import 'attackers_code' // Its too late for you!  
</code></pre>

<p>Or maybe nodejs just passes it in via <code>module.capability</code> / <code>import.meta.capability</code> or something. For example:</p>

<pre><code class="language-javascript">const rootToken = module.claimToken()  
</code></pre>

<p>One way or another, this seems technically solvable.</p>

<h2 id="whataboutpackageswhichnevergetupdated">What about packages which never get updated?</h2>

<p>We probably don't need to solve this for version 1.</p>

<p>But if we did, we might be able to add a method in the capability module to "bless" a package. Eg:</p>

<pre><code class="language-javascript">const httpServerToken = narrow(rootToken, {net: {kind: 'listen'}})  
bless("express@^4.17.3", httpServerToken)  
</code></pre>

<p>Then any direct system call from that library acts as if it had the capability we pass in. (And nothing else).</p>

<p>Its a bit hacky though. I mean, how can you tell if a method call comes from a specific package? Thats tricky, but it should be possible. The simplest answer is we could look at the call stack to see if the immediately preceeding item is in a blessed package. You can already inspect the call stack via <code>new Error().stack</code>, but thats slow, and probably corruptible from javascript code. I bet we could do something cleaner from native code.</p>

<p>There might also be scope for mischief via callbacks with this approach. Or someone could edit a package's methods.</p>

<h2 id="howcanwepreventjavascriptsdynamismfrommakingthissecuritysystemswisscheese">How can we prevent javascript's dynamism from making this security system swiss cheese?</h2>

<p>This is a real problem.</p>

<p>As an aside, I'm worried if we wait for a perfectly secure solution before launching a capabilities system, then we'll never solve this problem at all. If "mostly secure" is as good as we can get, it still might be better than the current situation. (Though smart people may well disagree with me.)</p>

<p>Javascript is weird, and I'm worried there might be ways to escape this little sandbox. For example:</p>

<ul>
<li>If an attacker knows <code>express</code> is blessed, could they do something like this?</li>
</ul>

<pre><code class="language-javascript">const express = require('express')

const oldListen = express.application.listen  
express.application.listen = (...args) =&gt; {  
  doNastyStuffAsExpress() // Oh no!
  oldListen(...args)
}
</code></pre>

<p>But this wouldn't work because the new function isn't part of the express package (even if its called via <code>app.listen()</code>. There may be a way around that. Maybe via an <code>eval()</code> call?</p>

<ul>
<li>Use <code>Object.defineProperty</code> to overwrite some built in methods. Then use that to target code which has a reference to the root token:</li>
</ul>

<pre><code class="language-javascript">Object.defineProperty(String.prototype, "length", {  
  get() {
    eval("console.log('steal the root token')")
  }
})
</code></pre>

<p>This code fails, but I don't know how strong the protections are:</p>

<pre><code>$ node
Welcome to Node.js v16.6.1.  
&gt; Object.defineProperty(String.prototype, "length", { get() { console.log('nasty') } })
Uncaught TypeError: Cannot redefine property: length  
    at Function.defineProperty (&lt;anonymous&gt;)
</code></pre>

<p>I might not be smart enough to figure out a way to pierce this security envelope, but maybe you are? This is a new security level. We need some smart security minds to have a play and see if they can bolt this thing down.</p>

<p>Directly editing the prototype of built in javascript classes like <code>String</code> and <code>Array</code> is considered bad form these days. I'd be happy to ban some of that dynamism entirely if the result is better security. If we have to ban <code>eval</code> in strict capabilities mode, frankly I'd be delighted.</p>

<p>If some packages in npm misbehave with capability based sandboxing enabled, thats fine. We can either fix them or boot them from our production systems. There is no shortage of excellent packages in npm. (If you can find them.)</p>

<h2 id="packageinstallscripts">Package install scripts</h2>

<p>Npm packages are also allowed to run arbitrary shell scripts on your computer when you install them, via <a href="https://docs.npmjs.com/cli/v7/using-npm/scripts#npm-install">lifecycle events in <code>package.json</code></a>. I understand it - but I really wish this feature didn't exist, because there's almost no valid uses for it outside compiling your module. And modules should be compiled <em>before</em> they're published, not after.</p>

<p>There are vanishingly few legitimate uses for <code>npm install</code> scripts - almost no popular npm modules use them. But there's a mountain of malicious ways to abuse them.</p>

<p>Now, <code>npm install</code> already <em>sort of</em> has an answer to this problem - which is its <a href="https://docs.npmjs.com/cli/v8/commands/npm-install#ignore-scripts"><code>--ignore-scripts</code> option</a>. But I bet almost nobody knows about that option, or uses it.</p>

<p>This might be the most controversial (and difficult to change) recommendation here - I think npm should ignore <code>npm install</code> scripts by default. Or maybe, by default prompt the user instead of just doing this stuff blindly:</p>

<pre><code>$ npm install isobject
Installed package `isobject` wants to run a script on your computer to function. Blindly trust this package? (Y/n): n  
</code></pre>

<h1 id="closingthoughts">Closing thoughts</h1>

<p>Anyway, thats the core idea. We add capability tokens to nodejs. Packages need a capability token in order to do any privileged actions - like spawn child processes, load native modules, run scripts, access the filesystem or the internet.</p>

<p>We have some problems to work out:</p>

<ul>
<li>How should a security token's scope be expressed?</li>
<li>How can we securely pass the root 'wildcard' token to the main module?</li>
<li>Are there any nasty javascript tricks which would let someone easily dodge this whole system? Are there any ways we might need to lock javascript down some more in strict capability mode?</li>
</ul>

<p>But the javascript ecosystem has plenty of smart people. I think this is a challenge worth taking on. The security of our computers, and our users' data depends on it.</p>

<p>(And as an added bonus, it would make it impossible to sneak dirty telemetry and things like that into npm modules.)</p>

<p>Nodejs has a massive, dynamic ecosystem of 3rd party packages. We should be able to depend on arbitrary libraries without giving them the keys to the kingdom. We just need to do some work to make it happen.</p>

<p>And when I say "we", I mean "you". I'm too busy building CRDTs to join this fight. We only get this future if people like you step forward to build it. Are you up for the challenge?</p>

<h1 id="appendixhowdowewritenodescapabilitieslibrary">Appendix: How do we write Node's capabilities library?</h1>

<p>Here is a rough sketch of what nodejs's capabilities library might look like:</p>

<pre><code class="language-javascript">const registry = new Map()

let rootToken = new Symbol() // Special global wildcard token  
registry.set(rootToken, {scope: '*'}) // What do scopes look like?

function getRoot() {  
  if (rootToken === null) { throw Error('...'); }
  let token = rootToken
  rootToken = null
  return token
}

function hasScope(symbol, desiredScope) {  
  const scope = registry.get(symbol)
  // ... And check if desiredScope is a subset of scope.
}

function narrow(parentToken, requestedScope) {  
  if (!hasScope(parentToken, requestedScope)) {
    throw new Error("Nice try, evildoer!")
  }

  const narrowedToken = new Symbol()
  registry.set(narrowedToken, requestedScope)
  return narrowedToken
}

module.exports = {getRoot, hasScope, narrow}  
</code></pre>]]></content:encoded></item><item><title><![CDATA[5000x faster CRDTs: An adventure in optimization]]></title><description><![CDATA[<p>A few years ago I was really bothered by an academic paper.</p>

<p>Some researchers in France put together a comparison showing lots of ways you could implement concurrent editing, using various CRDT and OT algorithms. And they benchmarked all of them. (Wow, yess!) Some algorithms worked reasonably well. But others</p>]]></description><link>https://josephg.com/blog/crdts-go-brrr/</link><guid isPermaLink="false">a27e3051-57b2-4fd5-8a96-99346807ca5a</guid><category><![CDATA[programming]]></category><category><![CDATA[crdt]]></category><dc:creator><![CDATA[Joseph Gentle]]></dc:creator><pubDate>Sat, 31 Jul 2021 03:23:45 GMT</pubDate><content:encoded><![CDATA[<p>A few years ago I was really bothered by an academic paper.</p>

<p>Some researchers in France put together a comparison showing lots of ways you could implement concurrent editing, using various CRDT and OT algorithms. And they benchmarked all of them. (Wow, yess!) Some algorithms worked reasonably well. But others took upwards of 3 seconds to process simple paste operations from their editing sessions. Yikes!</p>

<p>Which algorithm was that? Well, this is awkward but .. it was mine. I mean, I didn't invent it - but it was the algorithm I was using for ShareJS. The algorithm we used for Google Wave. The algorithm which - hang on - I knew for a fact didn't take 3 seconds to process large paste events. Whats going on here?</p>

<p>I took a closer look at the paper. In their implementation when a user pasted a big chunk of text (like 1000 characters), instead of creating 1 operation with 1000 characters, their code split the insert into 1000 individual operations. And each of those operations needed to be processed separately. Do'h - of course it'll be slow if you do that! This isn't a problem with the operational transformation algorithm. This is just a problem with <em>their particular implementation</em>.</p>

<p>The infuriating part was that several people sent me links to the paper and (pointedly) asked me what I think about it. Written up as a Published Science Paper, these speed comparisons seemed like a Fact About The Universe. And not what they really were - implementation details of some java code, written by a probably overstretched researcher. One of a whole bunch of implementations that they needed to code up.</p>

<p>"Nooo! The peer reviewed science isn't right everybody! Please believe me!". But I didn't have a published paper justifying my claims. I had working code but it felt like none of the smart computer science people cared about that. Who was I? I was nobody.</p>

<hr>

<p>When we think about CRDTs and other collaborative editing systems we have a language problem. We describe each system as an "algorithm". Jupiter is an Algorithm. RGA is an Algorithm. But really there are two very separate aspects:</p>

<ol>
<li>The black-box <em>behaviour</em> of concurrent edits. When two clients edit the same region of text at the same time, what happens? Are they merged, and if so in what order? What are the rules?  </li>
<li>The white-box <em>implementation</em> of the system. What programming language are we using? What data structures? How well optimized is the code?</li>
</ol>

<p>If my implementation runs slowly, what does that actually tell us? Maybe it's like tests. A passing test suite <em>suggests</em>, but can never <em>prove</em> that there are no bugs. Likewise a slow implementation suggests, but can never prove that every implementation of the system will be slow. If you wait long enough, somebody will find more bugs. And, maybe, someone out there can design a faster implementation.</p>

<p>I've translated my old text OT code into C, Javascript, Go, Rust and Swift. Each implementation has the same behaviour, and the same algorithm. But the performance is not even close. In javascript my transform function ran about 100 000 times per second. Not bad! But the same function in C does 20M iterations per second. That's 200x faster. Wow!</p>

<p>Were the academics testing the slow version or the fast version of this code? Maybe, without noticing, they had fast versions of some algorithms and slow versions of others. It's impossible to tell from the paper!</p>

<h1 id="makingcrdtsfast">Making CRDTs fast</h1>

<p>So as you may know, I've been getting interested in CRDTs lately. I think they're the <a href="https://josephg.com/blog/crdts-are-the-future/">future of collaborative editing</a>. And maybe the future of all software - but I'm not ready to talk about that yet. Most CRDTs you read about in academic papers are crazy slow, and a decade ago I decided to stop reading academic papers and dismissed them. I assumed CRDTs had some inherent problem. A GUID for every character? Nought but madness comes from those strange lands! But - and this is awkward to admit - I think I've been making the same mistake as those researchers. I was reading papers which described the <em>behaviour</em> of different systems. And I assumed that meant we knew how the best way to <em>implement</em> those systems. And wow, I was super wrong.</p>

<p>How wrong? Well. Running <a href="https://github.com/automerge/automerge-perf/">this editing trace</a>, <a href="https://github.com/automerge/automerge/">Automerge</a> (a popular CRDT, written by <a href="https://martin.kleppmann.com/">a popular researcher</a>) takes nearly 5 minutes to run. I have a <a href="https://github.com/josephg/diamond-types">new implementation</a> that can process the same editing trace in 56 milliseconds. Thats 0.056 seconds, which is over 5000x faster. It's the largest speed up I've ever gotten from optimization work - and I'm utterly delighted by it.</p>

<p>Lets talk about why automerge is currently slow, and I'll take you through all the steps toward making it super fast.</p>

<p>Wait, no. First we need to start with:</p>

<h2 id="whatisautomerge">What is automerge?</h2>

<p>Automerge is a library to help you do collaborative editing. It's written by Martin Kleppmann, who's a little bit famous from his book and <a href="https://martin.kleppmann.com/2020/07/06/crdt-hard-parts-hydra.html">excellent talks</a>. Automerge is based on an algorithm called RGA, which you can read about in an academic paper if you're into that sort of thing.</p>

<p>Martin explains automerge far better than I will in this talk from 2020:</p>

<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/x7drE24geUw?start=1237" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

<p>Automerge (and Yjs and other CRDTs) think of a shared document as a list of characters. Each character in the document gets a unique ID, and whenever you insert into the document, you name what you're inserting after.</p>

<p>Imagine I type "abc" into an empty document. Automerge creates 3 items:</p>

<ul>
<li>Insert <em>'a'</em> id <code>(seph, 0)</code> after <code>ROOT</code>
<ul><li>Insert <em>'b'</em> id <code>(seph, 1)</code> after <code>(seph, 0)</code></li>
<li>Insert <em>'c'</em> id <code>(seph, 2)</code> after <code>(seph, 1)</code></li></ul></li>
</ul>

<p>We can draw this as a tree!</p>

<p><img src="https://josephg.com/blog/crdts-go-brrr/automerge1.drawio.svg" alt="tree with " abc"="" inserts"=""></p>

<p>Lets say Mike inserts an 'X' between <em>a</em> and <em>b</em>, so we get "aXbc". Then we have:</p>

<ul>
<li>Insert <em>'a'</em> id <code>(seph, 0)</code> after <code>ROOT</code>
<ul><li>Insert <em>'X'</em> id <code>(mike, 0)</code> after <code>(seph, 0)</code></li>
<li>Insert <em>'b'</em> id <code>(seph, 1)</code> after <code>(seph, 0)</code></li>
<li>Insert <em>'c'</em> id <code>(seph, 2)</code> after <code>(seph, 1)</code></li></ul></li>
</ul>

<p><img src="https://josephg.com/blog/crdts-go-brrr/automerge2.drawio.svg" alt="tree with " axbc""=""></p>

<p>Note the 'X' and 'b' both share the same parent. This will happen when users type concurrently in the same location in the document. But how do we figure out which character goes first? We could just sort using their agent IDs or something. But argh, if we do that the document could end up as <em>abcX</em>, even though Mike inserted <em>X</em> before the <em>b</em>. That would be really confusing.</p>

<p>Automerge (RGA) solves this with a neat hack. It adds an extra integer to each item called a <em>sequence number</em>. Whenever you insert something, you set the new item's sequence number to be 1 bigger than the biggest sequence number you've ever seen:</p>

<ul>
<li>Insert <em>'a'</em> id <code>(seph, 0)</code> after <code>ROOT</code>, seq: <em>0</em>
<ul><li>Insert <em>'X'</em> id <code>(mike, 0)</code> after <code>(seph, 0)</code>, seq: <em>3</em></li>
<li>Insert <em>'b'</em> id <code>(seph, 1)</code> after <code>(seph, 0)</code>, seq: <em>1</em></li>
<li>Insert <em>'c'</em> id <code>(seph, 2)</code> after <code>(seph, 1)</code>, seq: <em>2</em></li></ul></li>
</ul>

<p>This is the algorithmic version of "Wow I saw a sequence number, and it was <em>this big!</em>" "Yeah? Mine is <em>even bigger!</em>"</p>

<p>The rule is that children are sorted first based on their sequence numbers (bigger sequence number first). If the sequence numbers match, the changes must be concurrent. In that case we can sort them arbitrarily based on their agent IDs. (We do it this way so all peers end up with the same resulting document.)</p>

<p>Yjs - which we'll see more of later - implements a CRDT called YATA. YATA is identical to RGA, except that it solves this problem with a slightly different hack. But the difference isn't really important here.</p>

<p>Automerge (RGA)'s <em>behaviour</em> is defined by this algorithm:</p>

<ul>
<li>Build the tree, connecting each item to its parent</li>
<li>When an item has multiple children, sort them by sequence number then by their ID.</li>
<li>The resulting list (or text document) can be made by flattening the tree with a depth-first traversal.</li>
</ul>

<p>So how should you <em>implement</em> automerge? The automerge library does it in the obvious way, which is to store all the data as a tree. (At least I think so - after typing "abc" <a href="https://gist.github.com/josephg/0522c4aec5021cc1dddb60e778828dbe">this is automerge's internal state</a>. Uh, uhm, I have no idea whats going on here. And what are all those Uint8Arrays doing all over the place? Whatever.) The automerge library works by building a tree of items.</p>

<p>For a simple benchmark, I'm going to test automerge using <a href="https://github.com/automerge/automerge-perf/">an editing trace Martin himself made</a>. This is a character by character recording of Martin typing up an academic paper. There aren't any concurrent edits in this trace, but users almost never actually put their cursors at exactly the same place and type anyway, so I'm not too worried about that. I'm also only counting the time taken to apply this trace <em>locally</em>, which isn't ideal but it'll do. Kevin Jahns (Yjs's author) has a much more <a href="https://github.com/dmonad/crdt-benchmarks">extensive benchmarking suite here</a> if you're into that sort of thing. All the benchmarks here are done on my chonky ryzen 5800x workstation, with Nodejs v16.1 and rust 1.52 when that becomes appropriate. (Spoilers!)</p>

<p>The editing trace has 260 000 edits, and the final document size is about 100 000 characters.</p>

<p>As I said above, automerge takes a little under 5 minutes to process this trace. Thats just shy of 900 edits per second, which is probably fine. But by the time it's done, automerge is using 880 MB of RAM. Whoa! That's 10kb of ram <em>per key press</em>. At peak, automerge was using 2.6 GB of RAM!</p>

<p>To get a sense of how much overhead there is, I'll compare this to <a href="https://gist.github.com/josephg/13efc1444660c07870fcbd0b3e917638#file-js_baseline-js">a baseline benchmark</a> where we just splice all the edits directly into a javascript string. This throws away all the information we need to do collaborative editing, but it gives us a sense of how fast javascript is capable of going. It turns out javascript running on V8 is <em>fast</em>:</p>

<p>| Test                              | Time taken | RAM usage |
|:--------------------------        | ----------:| ---------:|
| <strong>automerge (v1.0.0-preview2)</strong>   |  291s      | 880 MB    |
| <em>Plain string edits in JS</em>        | 0.61s      | 0.1 MB    |</p>

<p>This is a chart showing the time taken to process each operation throughout the test, averaged in groups of 1000 operations. I think those spikes are V8's garbage collector trying to free up memory.</p>

<p><img src="https://josephg.com/blog/crdts-go-brrr/am_perf1.svg" alt="automerge performance chart"></p>

<p>In the slowest spike near the end, a single edit took <em>1.8 seconds</em> to process. Oof. In a real application, the whole app (or browser tab) would freeze up for a couple of seconds sometimes while you're in the middle of typing.</p>

<p>The chart is easier to read when we average everything out a bit and zoom the Y axis. We can see the average performance gets gradually (roughly linearly) worse over time.</p>

<p><img src="https://josephg.com/blog/crdts-go-brrr/am_perf1_smooth.svg" alt="automerge performance chart smoothed out"></p>

<h2 id="whyisautomergeslowthough">Why is automerge slow though?</h2>

<p>Automerge is slow for a whole slew of reasons:</p>

<ol>
<li>Automerge's core tree based data structure gets big and slow as the document grows.  </li>
<li>Automerge makes heavy use of <a href="https://immutable-js.github.io/">Immutablejs</a>. Immutablejs is a library which gives you clojure-like copy-on-write semantics for javascript objects. This is a cool set of functionality, but the V8 optimizer &amp; GC struggles to optimize code that uses immutablejs. As a result, it increases memory usage and decreases performance.  </li>
<li>Automerge treats each inserted character as a separate item. Remember that paper I talked about earlier, where copy+paste operations are slow? Automerge does that too!</li>
</ol>

<p>Automerge was just never written with performance in mind. Their team is working on a replacement <a href="https://github.com/automerge/automerge-rs/">rust implementation of the algorithm</a> to run through wasm, but at the time of writing it hasn't landed yet. I got the master branch working, but they have some kinks to work out before it's ready. Switching to the automerge-rs backend doesn't make average performance in this test any faster. (Although it does halve memory usage and smooth out performance.)</p>

<hr>

<p>There's an old saying with performance tuning:</p>

<blockquote>
  <p>You can't make the computer faster. You can only make it do less work.</p>
</blockquote>

<p>How do we make the computer do less work here? There's lots of performance wins to be had from going through the code and improving lots of small things. But the automerge team has the right approach. It's always best to start with macro optimizations. Fix the core algorithm and data structures before moving to optimizing individual methods. There's no point optimizing a function when you're about to throw it away in a rewrite.</p>

<p>By far, Automerge's biggest problem is its complex tree based data structure. And we can replace it with something faster.</p>

<h2 id="improvingthedatastructure">Improving the data structure</h2>

<p>Luckily, there's a better way to implement CRDTs, pioneered in <a href="https://github.com/yjs/yjs">Yjs</a>. Yjs is another (competing) opensource CRDT implementation made by Kevin Jahns. It's fast, well documented and well made. If I were going to build software which supports collaborative editing today, I'd use Yjs.</p>

<p>Yjs doesn't need a whole blog post talking about how to make it fast because it's already pretty fast, as we'll see soon. It got there by using a clever, obvious data structure "trick" that I don't think anyone else in the field has noticed. Instead of implementing the CRDT as a tree like automerge does:</p>

<pre><code class="language-javascript">state = {  
  { item: 'a', id: ['seph', 0], seq: 0, children: [
    { item: 'X', id, seq, children: []},
    { item: 'b', id, seq, children: [
      { item: 'c', id, seq, children: []}
    ]}
  ]}
}
</code></pre>

<p>Yjs just puts all the items in a single flat list:</p>

<pre><code class="language-javascript">state = [  
  { item: 'a', id: ['seph', 0], seq: 0, parent: null },
  { item: 'X', id, seq, parent: ['seph', 0] },
  { item: 'b', id, seq, parent: ['seph', 0] },
  { item: 'c', id, seq, parent: [..] }
]
</code></pre>

<p>That looks simple, but how do you insert a new item into a list? With automerge it's easy:</p>

<ol>
<li>Find the parent item  </li>
<li>Insert the new item into the right location in the parents' list of children</li>
</ol>

<p>But with this list approach it's more complicated:</p>

<ol>
<li>Find the parent item  </li>
<li>Starting right after the parent item, iterate through the list until we find the location where the new item should be inserted (?)  </li>
<li>Insert it there, splicing into the array</li>
</ol>

<p>Essentially, this approach is just a fancy insertion sort. We're implementing a list CRDT with a list. Genius!</p>

<p>This sounds complicated - how do you figure out where the new item should go? But it's complicated in the same way <em>math</em> is complicated. It's hard to understand, but once you understand it, you can implement the whole insert function in about 20 lines of code:</p>

<p>(But don't be alarmed if this looks confusing - we could probably fit everyone on the planet who understands this code today into a small meeting room.)</p>

<pre><code class="language-javascript">const automergeInsert = (doc, newItem) =&gt; {  
  const parentIdx = findItem(doc, newItem.parent) // (1)

  // Scan to find the insert location
  let i
  for (i = parentIdx + 1; i &lt; doc.content.length; i++) {
    let o = doc.content[i]
    if (newItem.seq &gt; o.seq) break // Optimization.
    let oparentIdx = findItem(doc, o.parent)

    // Should we insert here? (Warning: Black magic part)
    if (oparentIdx &lt; parentIdx
      || (oparentIdx === parentIdx
        &amp;&amp; (newItem.seq === o.seq)
        &amp;&amp; newItem.id[0] &lt; o.id[0])
    ) break
  }
  // We've found the position. Insert at position *i*.
  doc.content.splice(i, 0, newItem) // (2)

  // .. And do various bookkeeping.
}
</code></pre>

<p>I implemented both Yjs's CRDT (YATA) and Automerge using this approach in my experimental <a href="https://github.com/josephg/reference-crdts/blob/main/crdts.ts"><em>reference-crdts</em></a> codebase. <a href="https://github.com/josephg/reference-crdts/blob/fed747255df9d457e11f36575de555b39f07e909/crdts.ts#L401-L459">Here's the insert function, with a few more comments</a>. The Yjs version of this function is in the same file, if you want to have a look. Despite being very different papers, the logic for inserting is almost identical. And even though my code is very different, this approach is <em>semantically</em> identical to the actual automerge, and Yjs and sync9 codebases. (<a href="https://github.com/josephg/reference-crdts/blob/main/reference_test.ts">Fuzzer verified (TM)</a>).</p>

<p>If you're interested in going deeper on this, I gave <a href="https://invisiblecollege.s3-us-west-1.amazonaws.com/braid-meeting-10.mp4#t=300">a talk about this approach</a> at a <a href="https://braid.org/">braid</a> meeting a few weeks ago.</p>

<p>The important point is this approach is better:</p>

<ol>
<li>We can use a flat array to store everything, rather than an unbalanced tree. This makes everything smaller and faster for the computer to process.  </li>
<li>The code is really simple. Being faster <em>and</em> simpler moves the <a href="https://en.wikipedia.org/wiki/Pareto_efficiency">Pareto efficiency frontier</a>. Ideas which do this are rare and truly golden.  </li>
<li>You can implement lots of CRDTs like this. Yjs, Automerge, Sync9 and others work. You can implement many list CRDTs in the same codebase. In my reference-crdts codebase I have an implementation of both RGA (automerge) and YATA (Yjs). They share most of their code (everything except this one function) and their performance in this test is identical.</li>
</ol>

<p>Theoretically this algorithm can slow down when there are concurrent inserts in the same location in the document. But that's really rare in practice - you almost always just insert right after the parent item.</p>

<p>Using this approach, my implementation of automerge's algorithm is about 10x faster than the real automerge. And it's 30x more memory-efficient:</p>

<p>| Test                              | Time taken | RAM usage |
|:--------------------------        | ----------:| ---------:|
| automerge (v1.0.0-preview2)       |  291s      | 880 MB    |
| <strong>reference-crdts (automerge / Yjs)</strong> |   31s      |  28 MB    |
| <em>Plain string edits in JS</em>        | 0.61s      | 0.1 MB    |</p>

<p>I wish I could attribute <em>all</em> of that difference to this sweet and simple data structure. But a lot of the difference here is probably just immutablejs gumming automerge up.</p>

<p>It's a lot faster than automerge:</p>

<p><img src="https://josephg.com/blog/crdts-go-brrr/ref_vs_am_perf.svg" alt="Automerge is much slower than reference-crdts"></p>

<h2 id="deathby1000scans">Death by 1000 scans</h2>

<p>We're using a clean and fast core data abstraction now, but the implementation is still not <em>fast</em>. There are two big performance bottlenecks in this codebase we need to fix:</p>

<ol>
<li>Finding the location to insert, and  </li>
<li>Actually inserting into the array</li>
</ol>

<p>(These lines are marked <em>(1)</em> and <em>(2)</em> in the code listing above).</p>

<p>To understand why this code is necessary, lets say we have a document, which is a list of items.</p>

<pre><code class="language-javascript">state = [  
  { item: 'a', isDeleted: false, id: ['seph', 0], seq, parent: null },
  { item: 'X', isDeleted: false, id, seq, parent: ['seph', 0] },
  { item: 'b', isDeleted: true,  id, seq, parent: ['seph', 0] },
  { item: 'c', isDeleted: false, id, seq, parent: ['seph', 1] },
  ...
]
</code></pre>

<p>And some of those items might have been deleted. I've added an <code>isDeleted</code> flag to mark which ones. (Unfortunately we can't just remove them from the array because other inserts might depend on them. (Drat! But that's a problem for another day.)</p>

<p>Imagine the document has 150 000 array items in it, representing 100 000 characters which haven't been deleted. If the user types an 'a' in the middle of the document (at <em>document position</em> 50 000), what index does that correspond to in our array? To find out, we need to scan through the document (skipping deleted items) to figure out the right array location.</p>

<p>So if the user inserts at position 50 000, we'll probably have to linearly scan past 75 000 items or something to find the insert position. Yikes!</p>

<p>And then when we actually insert, the code does this, which is double yikes:</p>

<pre><code class="language-javascript">doc.content.splice(destIdx, 0, newItem)  
</code></pre>

<p>If the array currently has 150 000 items, javascript will need to move every single item <em>after</em> the new item once space forward in the array. This part happens in native code, but it's still probably slow when we're moving so many items. (Aside: V8 is actually suspiciously fast at this part, so maybe v8 isn't using an array internally to implement Arrays? Who knows!)</p>

<p>But in general, inserting an item into a document with <em>n</em> items will take about <em>n</em> steps. Wait, no - it's worse than that because deleted items stick around. Inserting into a document where there have <em>ever been</em> <em>n</em> items will take <em>n</em> steps. This algorithm is reasonably fast, but it gets slower with every keystroke. Inserting <em>n</em> characters will take <em>O(n^2)</em>.</p>

<p>You can see this if we zoom in on the diagram above. There's a lot going on here because Martin's editing position bounced around the document. But there's a strong linear trend up and to the right, which is what we would expect when inserts take <em>O(n)</em> time:</p>

<p><img src="https://josephg.com/blog/crdts-go-brrr/ref_perf3.svg" alt="reference crdts implementation zoomed in"></p>

<p>And why this shape in particular? And why does performance get better near the end? If we simply graph <em>where</em> each edit happened throughout the editing trace, with the same bucketing and smoothing, the result is a very familiar curve:</p>

<p><img src="https://josephg.com/blog/crdts-go-brrr/inspos.svg" alt="Edit position throughout document"></p>

<p>It looks like the time spent applying changes is dominated by the time it takes to scan through the document's array.</p>

<h2 id="changingthedatastructure">Changing the data structure</h2>

<p>Can we fix this? Yes we can! And by "we", I mean Kevin fixed these problems in Yjs. How did he manage that?</p>

<p>So remember, there are two problems to fix:</p>

<ol>
<li>How do we find a specific insert position?  </li>
<li>How do we efficiently insert content at that location?</li>
</ol>

<p>Kevin solved the first problem by thinking about how humans actually edit text documents. Usually while we're typing, we don't actually bounce around a document very much. Rather than scanning the document each time an edit happens, Yjs caches the last <em>(index, position)</em> pair where the user made an edit. The next edit will probably be pretty close to the previous edit, so Kevin just scans forwards or backwards from the last editing position. This sounds a little bit dodgy to me - I mean, thats a big assumption to make! What if edits happen randomly?! But people don't actually edit documents randomly, so it works great in practice.</p>

<p>(What if two users are editing different parts of a document at the same time? Yjs actually stores a whole set of cached locations, so there's almost always a cached cursor location near each user no matter where they're making changes in the document.)</p>

<p>Once Yjs finds the target insert location, it needs to insert efficiently, without copying all the existing items. Yjs solves that by using a bidirectional linked list instead of an array. So long as we have an insert position, linked lists allow inserts in constant time.</p>

<p>Yjs does one more thing to improve performance. Humans usually type in runs of characters. So when we type "hello" in a document, instead of storing:</p>

<pre><code class="language-javascript">state = [  
  { item: 'h', isDeleted: false, id: ['seph', 0], seq, parent: null },
  { item: 'e', isDeleted: false, id: ['seph', 1], seq, parent: ['seph', 0] },
  { item: 'l', isDeleted: false, id: ['seph', 2], seq, parent: ['seph', 1] },
  { item: 'l', isDeleted: false, id: ['seph', 3], seq, parent: ['seph', 2] },
  { item: 'o', isDeleted: false, id: ['seph', 4], seq, parent: ['seph', 3] },
]
</code></pre>

<p>Yjs just stores:</p>

<pre><code class="language-javascript">state = [  
  { item: 'hello', isDeleted: false, id: ['seph', 0], seq, parent: null },
]
</code></pre>

<p>Finally those pesky paste events will be fast too!</p>

<p>This is the same information, just stored more compactly. Unfortunately we can't collapse the whole document into a single item or something like that using this trick. The algorithm can only collapse inserts when the IDs and parents line up sequentially - but that happens whenever a user types a run of characters without moving their cursor. And that happens a lot.</p>

<p>In this data set, using spans reduces the number of array entries by 14x. (180k entries down to 12k).</p>

<p>How fast is it now? This blows me away - Yjs is 30x faster than my reference-crdts implementation in this test. And it only uses about 10% as much RAM. It's <em>300x faster than automerge!</em>.</p>

<p>| Test                              | Time taken | RAM usage |
|:--------------------------        | ----------:| ---------:|
| automerge (v1.0.0-preview2)       |  291s      | 880 MB    |
| reference-crdts (automerge / Yjs) |   31s      |  28 MB    |
| <strong>Yjs (v13.5.5)</strong>                 | 0.97s      | 3.3 MB    |
| <em>Plain string edits in JS</em>        | 0.61s      | 0.1 MB    |</p>

<p>Honestly I'm shocked and a little suspicious of how little ram Yjs uses in this test. I'm sure there's some wizardry in V8 making this possible. It's extremely impressive.</p>

<p>Kevin says he wrote and rewrote parts of Yjs 12 times in order to make this code run so fast. If there was a programmer version of the speedrunning community, they would adore Kevin. I can't even put Yjs on the same scale as the other algorithms because it's so fast:</p>

<p><img src="https://josephg.com/blog/crdts-go-brrr/yjs_perf4.svg" alt="Yjs performance vs other algorithms"></p>

<p>If we isolate Yjs, you can see it has <em>mostly</em> flat performance. Unlike the other algorithms, it doesn't get slower over time, as the document grows:</p>

<p><img src="https://josephg.com/blog/crdts-go-brrr/yjs_perf5.svg" alt="Yjs performance isolated"></p>

<p>But I have no idea what those spikes are near the end. They're pretty small in <em>absolute</em> terms, but it's still weird! Maybe they happen when the user moves their cursor around the document? Or when the user deletes chunks? I have no idea.</p>

<p>This is neat, but the real question is: Can we go <em>even faster</em>? Honestly I doubt I can make pure javascript run this test any faster than Kevin managed here. But maybe.. just maybe we can be...</p>

<h2 id="fasterthanjavascript">Faster than Javascript</h2>

<p>When I told Kevin that I thought I could make a CRDT implementation that's way faster than Yjs, he didn't believe me. He said Yjs was already so well optimized, going a lot faster probably wasn't possible. "Maybe a little faster if you just port it to Rust. But not a lot faster! V8 is really fast these days!!"</p>

<p>But I knew something Kevin didn't know: I knew about memory fragmentation and cache coherency. Rust isn't just <em>faster</em>. It's also a lower level language, and that gives us the tools we need to control allocations and memory layout.</p>

<blockquote>
  <p>Kevin knows this now too, and he's working on <a href="https://github.com/yjs/y-crdt">Yrs</a> to see if he can claim the performance crown back.</p>
</blockquote>

<p>Imagine one of our document items in javascript:</p>

<pre><code class="language-javascript">var item = {  
  content: 'hello',
  isDeleted: false,
  id: ['seph', 10],
  seq: 5,
  parent: ['mike', 2]
}
</code></pre>

<p>This object is actually a mess like this in memory:</p>

<p><img src="https://josephg.com/blog/crdts-go-brrr/mem-frag.drawio.svg" alt="javascript objects fragmented in memory"></p>

<p>Bad news: <em>Your computer hates this.</em></p>

<p>This is terrible because all the data is fragmented. It's all separated by pointers.</p>

<blockquote>
  <p>And yes, I know, V8 tries its hardest to prevent this sort of thing when it can. But its not magic.</p>
</blockquote>

<p>To arrange data like this, the computer has to allocate memory one by one for each item. This is slow. Then the garbage collector needs extra data to track all of those objects, which is also slow. Later we'll need to read that data. To read it, your computer will often need to go fetch it from main memory, which - you guessed it - is slow as well.</p>

<p>How slow are main memory reads? <a href="https://gist.github.com/hellerbarde/2843375">At human scale</a> each L1 cache read takes 0.5 seconds. And a read from main memory takes close to 2 minutes! This is the difference between a single heartbeat, and the time it takes to brush your teeth.</p>

<p>Arranging memory like javascript does would be like writing a shopping list. But instead of "Cheese, Milk, Bread", your list is actually a scavenger hunt: "Under the couch", "On top of the fridge", and so on. Under the couch is a little note mentioning you need toothpaste. Needless to say, this makes doing the grocery shopping a lot of work.</p>

<p>To go faster, we need to squish all the data together so the computer can fetch more information with each read of main memory. (We want a single read of my grocery list to tell us everything we need to know). Linked lists are rarely used in the real world for exactly this reason - <em>memory fragmentation ruins performance</em>. I also want to move away from linked lists because the user <em>does</em> sometimes hop around the document, which in Yjs has a linear performance cost. Thats probably not a big deal in text editing, but I want this code to be fast in other use cases too. I don't want the program to <em>ever</em> need those slow scans.</p>

<p>We can't fix this in javascript. The problem with fancy data structures in javascript is that you end up needing a lot of exotic objects (like fixed size arrays). All those extra objects make fragmentation worse, so as a result of all your work, your programs often end up running slower anyway. This is the same limitation immutablejs has, and why its performance hasn't improved much in the decade since it was released. The V8 optimizer is very clever, but it's not magic and clever tricks only get us so far.</p>

<p>But we're not limited to javascript. Even when making webpages, we have WebAssembly these days. We can code this up in <em>anything</em>.</p>

<p>To see how fast we can <em>really</em> go, I've been quietly building a CRDT implementation in rust called <a href="https://github.com/josephg/diamond-types">Diamond types</a>. Diamond is almost identical to Yjs, but it uses a <a href="https://en.wikipedia.org/wiki/Range_tree">range tree</a> instead of a linked list internally to store all of the items.</p>

<p>Under the hood, my range tree is just a slightly modified b-tree. But usually when people talk about b-trees they mean a <a href="https://doc.rust-lang.org/std/collections/struct.BTreeMap.html">BTreeMap</a>. Thats not what I'm doing here. Instead of storing keys, each internal node of the b-tree stores the total number of characters (recursively) in that item's children. So we can look up any item in the document by character position, or insert or delete anywhere in the document in <em>log(n)</em> time.</p>

<p>This example shows the tree storing a document which currently has 1000 characters:</p>

<p><img src="https://josephg.com/blog/crdts-go-brrr/btree.drawio.svg" alt="b-tree diagram"></p>

<blockquote>
  <p>This is a range tree, right? The <a href="https://en.wikipedia.org/wiki/Range_tree">wikipedia article on range trees</a> is a pretty weak description of what I'm doing here.</p>
</blockquote>

<p>This solves both of our linear scanning problems from earlier:</p>

<ul>
<li>When we want to find the item at position 200, we can just traverse across and down the tree. In the example above, the item with position 350 must be in the middle leaf node here. Trees are very tidy - we can store Martin's editing trace in just 3 levels in our tree, which means in this benchmark we can find any item in about 3 reads from main memory. In practice, most of these reads will already be in your CPU's cache.</li>
<li>Updating the tree is fast too. We update a leaf, then update the character counts at its parent, and its parent, all the way up to the root. So again, after 3 or so steps we're done. Much better than shuffling everything in a javascript array.</li>
</ul>

<p>We never merge edits from remote peers in this test, but I made that fast too anyway. When merging remote edits we also need to find items by their ID (eg <em>['seph', 100]</em>). Diamond has little index to search the b-tree by ID. That codepath doesn't get benchmarked here though. It's fast but for now you'll have to take my word for it.</p>

<p>I'm not using Yjs's trick of caching the last edit location - at least not yet. It might help. I just haven't tried it yet.</p>

<p>Rust gives us total control over the memory layout, so we can pack everything in tightly. Unlike in the diagram, each leaf node in my b-tree stores a block of 32 entries, packed in a fixed size array in memory. Inserting with a structure like this results in a little bit of memcpy-ing, but a little bit of memcpy is fine. Memcpy is always faster than I think it will be - CPUs can copy several bytes per clock cycle. Its not the epic hunt of a main memory lookup.</p>

<p>And why 32 entries? I ran this benchmark with a bunch of different bucket sizes and 32 worked well. I have no idea why that worked out to be the best.</p>

<p>Speaking of fast, how fast does it go?</p>

<p>If we <a href="https://github.com/josephg/diamond-js">compile this code to webassembly</a> and drive it from javascript like in the other tests, we can now process the whole editing trace in 193 milliseconds. Thats 5x faster than Yjs. And remarkably 3x faster than our baseline test editing a native javascript string, despite doing all the work to support collaborative editing!</p>

<p>Javascript and WASM is now a bottleneck. If we skip javascript and run the benchmark <a href="https://github.com/josephg/diamond-types/blob/42a8bc8fb4d44671147ccaf341eee18d77b2d532/benches/yjs.rs">directly in rust</a>, we can process all 260k edits in this editing trace in just <em>56 milliseconds</em>. That's over 5000x faster than where we started with automerge. It can process 4.6 <em>million</em> operations every second.</p>

<p>| Test                              | Time taken | RAM usage |
|:--------------------------        | ----------:| ---------:|
| automerge (v1.0.0-preview2)       |  291s      | 880 MB    |
| reference-crdts (automerge / Yjs) |   31s      |  28 MB    |
| Yjs (v13.5.5)                     | 0.97s      | 3.3 MB    |
| <em>Plain string edits in JS</em>        | 0.61s      | 0.1 MB    |
| <strong>Diamond (wasm via nodejs)</strong>     | 0.19s      | ???       |
| <strong>Diamond (native)</strong>              | 0.056s     | 1.1 MB    |</p>

<p>Performance is smooth as butter. A b-tree doesn't care where edits happen. This system is uniformly fast across the whole document. Rust doesn't need a garbage collector to track memory allocations, so there's no mysterious GC spikes. And because memory is so tightly packed, processing this entire data set (all 260 000) only results in 1394 calls to malloc.</p>

<p><img src="https://josephg.com/blog/crdts-go-brrr/rust_perf6.svg" alt="rust implementation in wasm vs Yjs"></p>

<p>Oh, what a pity. Its so fast you can barely see it next to yjs (<em>fleexxxx</em>). Lets zoom in a bit there and bask in that flat line:</p>

<p><img src="https://josephg.com/blog/crdts-go-brrr/rust_perf7.svg" alt="rust implementation in wasm"></p>

<p>Well, a nearly flat line.</p>

<p>And remember, this chart shows the <em>slow</em> version. This chart is generated from javascript, calling into rust through WASM. If I run this benchmark natively its another ~4x faster again.</p>

<p>Why is WASM 4x slower than native execution? Are javascript calls to the WASM VM really that slow? Does LLVM optimize native x86 code better? Or do WASM's memory bounds checks slow it down?</p>

<h2 id="structofarraysorarrayofstructs">Struct of arrays or Array of structs?</h2>

<p>This implementation has another small, important change - and I'm not sure if I like it.</p>

<p>In rust I'm actually doing something like this:</p>

<pre><code class="language-javascript">doc = {  
  textContent: RopeyRope { 'hello' },

  clients: ['seph', 'mike'],

  items: BTree {[
    // Note: No string content!
    { len:  5, id: [0, 0], seq, parent: ROOT },
    { len: -5, id: [1, 0], seq, parent: [0, 0] }, // negative len means the content was deleted
    ...
  ]},
}
</code></pre>

<p>Notice the document's text content doesn't live in the list of items anymore. Now it's in a separate data structure. I'm using a rust library for this called <a href="https://crates.io/crates/ropey">Ropey</a>. Ropey implements <em>another</em> b-tree to efficiently manage just the document's text content.</p>

<p>This isn't universally a win. We have unfortunately arrived at the Land of Uncomfortable Engineering Tradeoffs:</p>

<ul>
<li>Ropey can to do text-specific byte packing. So with ropey, we use less RAM.</li>
<li>When inserting we need to update 2 data structures instead of 1. This makes everything more than twice as slow, and it makes the wasm bundle twice as big  (60kb -> 120kb).</li>
<li>For lots of use cases we'll end up storing the document content somewhere else anyway. For example, if you hook this CRDT up to VS Code, the editor will keep a copy of the document at all times anyway. So there's no need to store the document in my CRDT structures as well, at all. This implementation approach makes it easy to just turn that part of the code off.</li>
</ul>

<p>So I'm still not sure whether I like this approach.</p>

<p>But regardless, my CRDT implementation is so fast at this point that most of the algorithm's time is spent updating the document contents in ropey. Ropey on its own takes 29ms to process this editing trace. What happens if I just ... turn ropey off? How fast can this puppy can really go?</p>

<p>| Test                              | Time taken | RAM usage | Data structure |
|:--------------------------        | ----------:| ---------:|:---------------|
| automerge (v1.0.0-preview2)       |  291s      | 880 MB    | Naive tree     |
| reference-crdts (automerge / Yjs) |   31s      |  28 MB    | Array          |
| Yjs (v13.5.5)                     | 0.97s      | 3.3 MB    | Linked list    |
| <em>Plain string edits in JS</em>        | 0.61s      | 0.1 MB    | <em>(none)</em>       |
| Diamond (wasm via nodejs)         | 0.20s      | ???       | B-Tree         |
| Diamond (native)                  | 0.056s     | 1.1 MB    | B-Tree         |
| <em>Ropey (rust) baseline</em>           | 0.029s     | 0.2 MB    | <em>(none)</em>       |
| <strong>Diamond (native, no doc content)</strong> | 0.023s  | 0.96 MB   | B-Tree         |</p>

<p>Boom. This is kind of useless, but it's now 14000x faster than automerge. We're processing 260 000 operations in 23ms. Thats 11 million operations per second. I could saturate my home internet connection with keystrokes and I'd still have CPU to spare.</p>

<hr>

<p>We can calculate the average speed each algorithm processes edits:</p>

<p><img src="https://josephg.com/blog/crdts-go-brrr/totals.svg" alt=""></p>

<p>But these numbers are misleading. Remember, automerge and ref-crdts aren't steady. They're fast at first, then slow down as the document grows. Even though automerge can process about 900 edits per second <em>on average</em> (which is fast enough that users won't notice), the slowest edit during this benchmark run stalled V8 for a full 1.8 seconds.</p>

<p>We can put everything in a single, pretty chart if I use a log scale. It's remarkable how tidy this looks:</p>

<p><img src="https://josephg.com/blog/crdts-go-brrr/all_perf.svg" alt="all data in one chart"></p>

<blockquote>
  <p>Huh - look at the bottom two lines. The jitteryness of yjs and diamond mirror each other. Periods when yjs gets slower, diamond gets faster. I wonder whats going on there!</p>
</blockquote>

<p>But log scales are junk food for your intuition. On a linear scale the data looks like this:</p>

<p><img src="https://josephg.com/blog/crdts-go-brrr/all_perf_linear.svg" alt="all data in one chart, with a linear scale"></p>

<p>That, my friends, is how you make the computer do a lot less work.</p>

<h1 id="conclusion">Conclusion</h1>

<p>That silly academic paper I read all those years ago says some CRDTs and OT algorithms are slow. And everyone believed the paper, because it was Published Science. But the paper was wrong. As I've shown, we <em>can</em> make CRDTs fast. We can make them <em>crazy fast</em> if we get creative with our implementation strategies. With the right approach, we can make CRDTs so fast that we can compete with the performance of native strings. The performance numbers in that paper weren't just wrong. They were "a billionaire guessing a banana costs $1000" kind of wrong.</p>

<p>But you know what? I sort of appreciate that paper now. Their mistake is ok. It's <em>human</em>. I used to feel inadequate around academics - maybe I'll never be that smart! But this whole thing made me realise something obvious: Scientists aren't gods, sent from the heavens with the gift of Truth. No, they're beautiful, flawed <em>people</em> just like the rest of us mooks. Great at whatever we obsess over, but kind of middling everywhere else. I can optimize code pretty well, but I still get zucchini and cucumber mixed up. And, no matter the teasing I get from my friends, thats ok.</p>

<p>A decade ago Google Wave really needed a good quality list CRDT. I got super excited when the papers for CRDTs started to emerge. <a href="https://hal.inria.fr/inria-00432368/document">LOGOOT</a> and <a href="https://hal.inria.fr/inria-00445975/document">WOOT</a> seemed like a big deal! But that excitement died when I realised the algorithms were too slow and inefficient to be practically useful. And I made a big mistake - I assumed if the academics couldn't make them fast, nobody could.</p>

<p>But sometimes the best work comes out of a collaboration between people with different skills. I'm terrible at academic papers, I'm pretty good at making code run fast. And yet here, in my own field, I didn't even try to help. The researchers were doing their part to make P2P collaborative editing work. And I just thumbed my nose at them all and kept working on Operational Transform. If I helped out, maybe we would have had fast, workable CRDTs for text editing a decade ago. Oops! It turned out collaborative editing needed a collaboration between all of us. How ironic! Who could have guessed?!</p>

<p>Well, it took a decade, some hard work and some great ideas from a bunch of clever folks. The binary encoding system Martin invented for Automerge is brilliant. The system of avoiding UUIDs by using incrementing (agent id, sequence) tuples is genius. I have no idea who came up with that, but I love it. And of course, Kevin's list representation + insertion approach I describe here makes everything so much faster and simpler. I bet 100 smart people must have walked right past that idea over the last decade without any of them noticing it. I doubt I would have thought of it either. My contribution is using run-length encoded b-trees and clever indexing. And showing Kevin's fast list representation can be adapted to any CRDT algorithm. I don't think anyone noticed that before.</p>

<p>And now, after a decade of waiting, we finally figured out how to make fast, lightweight list CRDT implementations. Practical decentralized realtime collaborative editing? We're coming for you next.</p>

<h1 id="appendixaiwanttouseacrdtformyapplicationwhatshouldido">Appendix A: I want to use a CRDT for my application. What should I do?</h1>

<p>If you're building a document based collaborative application today, you should use <a href="https://github.com/yjs/yjs">Yjs</a>. Yjs has solid performance, low memory usage and great support. If you want help implementing Yjs in your application, Kevin Jahns sometimes accepts money in exchange for help integrating Yjs into various applications. He uses this to fund working on Yjs (and adjacent work) full time. Yjs already runs fast and soon it should become even faster.</p>

<p>The automerge team is also fantastic. I've had some great conversations with them about these issues. They're making performance the #1 issue of 2021 and they're planning on using a lot of these tricks to make automerge fast. It might already be much faster by the time you're reading this.</p>

<p>Diamond is <em>really</em> fast, but there's a lot of work before I have feature parity with Yjs and Automerge. There is a lot more that goes into a good CRDT library than operation speed. CRDT libraries also need to support binary encoding, network protocols, non-list data structures, presence (cursor positions), editor bindings and so on. At the time of writing, diamond does almost none of this.</p>

<p>If you want database semantics instead of document semantics, as far as I know nobody has done this well on top of CRDTs yet. You can use <a href="https://github.com/share/sharedb/">ShareDB</a>, which uses OT. I wrote ShareDB years ago, and it's well used, well maintained and battle tested.</p>

<p>Looking forward, I'm excited for <a href="https://github.com/redwood/redwood">Redwood</a> - which supports P2P editing and has planned full CRDT support.</p>

<h1 id="appendingbliesdamnedliesandbenchmarks">Appending B: Lies, damned lies and benchmarks</h1>

<p>Is this for real? Yes. But performance is complicated and I'm not telling the full picture here.</p>

<p>First, if you want to play with any of the benchmarks I ran yourself, you can. But everything is a bit of a mess.</p>

<p>The benchmark code for the JS plain string editing baseline, Yjs, automerge and reference-crdts tests is all in <a href="https://gist.github.com/josephg/13efc1444660c07870fcbd0b3e917638">this github gist</a>. It's a mess; but messy code is better than missing code.</p>

<p>You'll also need <code>automerge-paper.json.gz</code> from <a href="https://github.com/josephg/crdt-benchmarks">josephg/crdt-benchmarks</a> in order to run most of these tests. The reference-crdts benchmark depends on <a href="https://github.com/josephg/reference-crdts/tree/fed747255df9d457e11f36575de555b39f07e909">crdts.ts from josephg/reference-crdts, at this version</a>.</p>

<p>Diamond's benchmarks come from <a href="https://github.com/josephg/diamond-types/tree/42a8bc8fb4d44671147ccaf341eee18d77b2d532">josephg/diamond-types, at this version</a>. Benchmark by running <code>RUSTFLAGS='-C target-cpu=native' cargo criterion yjs</code>. The inline rope structure updates can be enabled or disabled by editing <a href="https://github.com/josephg/diamond-types/blob/42a8bc8fb4d44671147ccaf341eee18d77b2d532/src/list/doc.rs#L15">the constant at the top of src/list/doc.rs</a>. You can look at memory statistics by running <code>cargo run --release --features memusage --example stats</code>.</p>

<p>Diamond is compiled to wasm using <a href="https://github.com/josephg/diamond-js/tree/6e8a95670b651c0aaa7701a1a763778d3a486b0c">this wrapper</a>, hardcoded to point to a local copy of diamond-types from git. The wasm bundle is optimized with wasm-opt.</p>

<p>The charts were made on <a href="https://observablehq.com/@josephg/crdt-algorithm-performance-benchmarks">ObservableHQ</a>.</p>

<h3 id="areautomergeandyjsdoingthesamething">Are Automerge and Yjs doing the same thing?</h3>

<p>Throughout this post I've been comparing the performance of implementations of RGA (automerge) and YATA (Yjs + my rust implementation) interchangeably.</p>

<p>Doing this rests on the assumption that the concurrent merging behaviour for YATA and RGA are basically the same, and that you can swap between CRDT behaviour without changing your implementation, or your implementation performance. This is a novel idea that I think nobody has looked at before.</p>

<p>I feel confident in this claim because I demonstrated it in my <a href="https://github.com/josephg/reference-crdts">reference CRDT implementation</a>, which has identical performance (and an almost identical codepath) when using Yjs or automerge's behaviour. There might be some performance differences with conflict-heavy editing traces - but that's extremely rare in practice.</p>

<p>I'm also confident you could modify Yjs to implement RGA's behaviour if you wanted to, without changing Yjs's performance. You would just need to:</p>

<ul>
<li>Change Yjs's <em>integrate</em> method (or make an alternative) which used slightly different logic for concurrent edits</li>
<li>Store <em>seq</em> instead of <em>originRight</em> in each <em>Item</em></li>
<li>Store <em>maxSeq</em> in the document, and keep it up to date and</li>
<li>Change Yjs's binary encoding format.</li>
</ul>

<p>I talked to Kevin about this, and he doesn't see any point in adding RGA support into his library. It's not something anybody actually asks for. And RGA can have weird <a href="https://www.cl.cam.ac.uk/~arb33/papers/KleppmannEtAl-InterleavingAnomalies-PaPoC2019.pdf">interleaving</a> when prepending items.</p>

<p>For diamond, I make my code accept a type parameter for switching between Yjs and automerge's behaviour. I'm not sure if I want to. Kevin is probably right - I don't think this is something people ask for.</p>

<hr>

<p>Well, there is one way in which Yjs has a definite edge over automerge: Yjs doesn't record <em>when</em> each item in a document has been deleted. Only whether each item has been deleted or not. This has some weird implications:</p>

<ul>
<li>Storing when each delete happened has a weirdly large impact on memory usage and on-disk storage size. Adding this data doubles diamond's memory usage from 1.12mb to 2.34mb, and makes the system about 5% slower.</li>
<li>Yjs doesn't store enough information to implement per-keystroke editing replays or other fancy stuff like that. (Maybe thats what people want? Is it weird to have every errant keystroke recorded?)</li>
<li>Yjs needs to encode information about which items have been deleted into the <em>version</em> field. In diamond, versions are tens of bytes. In yjs, versions are ~4kb. And they grow over time as the document grows. Kevin assures me that this information is basically always small in practice. He might be right but this still makes me weirdly nervous.</li>
</ul>

<p>For now, the master branch of diamond includes temporal deletes. But all benchmarks in this blog post use a <a href="https://github.com/josephg/diamond-types/tree/yjs-style">yjs-style branch of diamond-types</a>, which matches how Yjs works instead. This makes for a fairer comparison with yjs, but diamond 1.0 might have a slightly different performance profile. (There's plenty of puns here about diamond not being polished yet, but I'm not sharp enough for those right now.)</p>

<h3 id="thesebenchmarksmeasurethewrongthing">These benchmarks measure the wrong thing</h3>

<p>This post only measures the time taken to replay a local editing trace. And I'm measuring the resulting RAM usage. Arguably accepting incoming changes from the user only needs to happen fast <em>enough</em>. Fingers simply don't type very fast. Once a CRDT can handle any local user edit in under about 1ms, going faster probably doesn't matter much. (And automerge usually performs that well already, barring some unlucky GC pauses.)</p>

<p>The <em>actually important</em> metrics are:</p>

<ul>
<li>How many bytes does a document take on disk or over the network</li>
<li>How much time does the document take to save and load</li>
<li>How much time it takes to update a document stored at rest (more below)</li>
</ul>

<p>The editing trace I'm using here also only has a single user making edits. There could be pathological performance cases lurking in the shadows when users make concurrent edits.</p>

<p>I did it this way because I haven't implemented a binary format in my reference-crdts implementation or diamond yet. If I did, I'd probably copy Yjs &amp; automerge's binary formats because they're so compact. So I expect the resulting binary size would be similar between all of these implementations, except for delete operations. Performance for loading and saving will probably approximately mirror the benchmarks I showed above. Maybe. Or maybe I'm wrong. I've been wrong before. It would be fun to find out.</p>

<hr>

<p>There's one other performance measure I think nobody is taking seriously enough at the moment. And that is, how we update a document at rest (in a database). Most applications aren't collaborative text editors. Usually applications are actually interacting with databases full of tiny objects. Each of those objects is very rarely written to.</p>

<p>If you want to update a single object in a database using Yjs or automerge today you need to:</p>

<ol>
<li>Load the whole document into RAM  </li>
<li>Make your change  </li>
<li>Save the whole document back to disk again</li>
</ol>

<p>This is going to be awfully slow. There are better approaches for this - but as far as I know, nobody is working on this at all. We could use your help!</p>

<blockquote>
  <p>Edit: Kevin says you can adapt Yjs's providers to implement this in a reasonable way. I'd love to see that in action.</p>
</blockquote>

<hr>

<p>There's another approach to making CRDTs fast, which I haven't mentioned here at all and that is <em>pruning</em>. By default, list CRDTs like these only ever grow over time (since we have to keep tombstones for all deleted items). A lot of the performance and memory cost of CRDTs comes from loading, storing and searching that growing data set. There are some approaches which solve this problem by finding ways to shed some of this data entirely. For example, Yjs's GC algorithm, or <a href="https://braid.org/antimatter">Antimatter</a>. That said, git repositories only ever grow over time and nobody seems mind too much. Maybe it doesn't matter so long as the underlying system is fast enough?</p>

<p>But pruning is orthogonal to everything I've listed above. Any good pruning system should also work with all of the algorithms I've talked about here.</p>

<h3 id="eachstepinthisjourneychangestoomanyvariables">Each step in this journey changes too many variables</h3>

<p>Each step in this optimization journey involves changes to multiple variables and I'm not isolating those changes. For example, moving from automerge to my reference-crdts implementation changed:</p>

<ul>
<li>The core data structure (tree to list)</li>
<li>Removed immutablejs</li>
<li>Removed automerge's frontend / backend protocol. And all those Uint8Arrays that pop up throughout automerge for whatever reason are gone too, obviously.</li>
<li>The javascript style is totally different. (FP javascript -> imperative)</li>
</ul>

<p>We got 10x performance from all this. But I'm only guessing how that 10x speedup should be distributed amongst all those changes.</p>

<p>The jump from reference-crdts to Yjs, and from Yjs to diamond are similarly monolithic. How much of the speed difference between diamond and Yjs has nothing to do with memory layout, and everything to do with LLVM's optimizer?</p>

<p>The fact that automerge-rs isn't faster than automerge gives me some confidence that diamond's performance isn't just thanks to rust. But I honestly don't know.</p>

<p>So, yes. This is a reasonable criticism of my approach. If this problem bothers you, I'd <em>love</em> for someone to pull apart each of the performance differences between implementations I show here and tease apart a more detailed breakdown. I'd read the heck out of that. I love benchmarking stories. That's normal, right?</p>

<h1 id="appendixcistilldontgetitwhyisautomergesjavascriptsoslow">Appendix C: I still don't get it - why is automerge's javascript so slow?</h1>

<p>Because it's not trying to be fast. Look at this code <a href="https://github.com/automerge/automerge/blob/d2e7ca2e141de0a72f540ddd738907bcde234183/backend/op_set.js#L649-L659">from automerge</a>:</p>

<pre><code class="language-javascript">function lamportCompare(op1, op2) {  
  return opIdCompare(op1.get('opId'), op2.get('opId'))
}

function insertionsAfter(opSet, objectId, parentId, childId) {  
  let childKey = null
  if (childId) childKey = Map({opId: childId})

  return opSet
    .getIn(['byObject', objectId, '_following', parentId], List())
    .filter(op =&gt; op.get('insert') &amp;&amp; (!childKey || lamportCompare(op, childKey) &lt; 0))
    .sort(lamportCompare)
    .reverse() // descending order
    .map(op =&gt; op.get('opId'))
}
</code></pre>

<p>This is called on each insert, to figure out how the children of an item should be sorted. I don't know how hot it is, but there are <em>so many things</em> slow about this:</p>

<ul>
<li>I can spot 7 allocations in this function. (Though the 2 closures should be hoisted). (Can you find them all?)</li>
<li>The items are already sorted reverse-lamportCompare before this method is called. Sorting an anti-sorted list is the slowest way to sort anything. Rather than sorting, then reverse()'ing, this code should just invert the arguments in <code>lamportCompare</code> (or negate the return value).</li>
<li>The goal is to insert a new item into an already sorted list. You can do that much faster with a for loop.</li>
<li>This code wraps childId into an immutablejs Map, just so the argument matches <code>lamportCompare</code> - which then unwraps it again. Stop - I'm dying!</li>
</ul>

<p>But in practice this code is going to be replaced by WASM calls through to <a href="https://github.com/automerge/automerge-rs">automerge-rs</a>. Maybe it already has been replaced with automerge-rs by the time you're reading this! So it doesn't matter. Try not to think about it. Definitely don't submit any PRs to fix all the low hanging fruit. <em>twitch.</em></p>

<footer>

[2021 Seph Gentle](https://josephg.com/)

[https://github.com/josephg/](https://github.com/josephg/)

</footer>]]></content:encoded></item><item><title><![CDATA[I was wrong. CRDTs are the future]]></title><description><![CDATA[<p>I saw <a href="https://www.youtube.com/watch?v=x7drE24geUw">Martin Kleppmann’s talk</a> a few weeks ago about his approach to realtime editing with <a href="https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type">CRDTs</a>, and I felt a deep sense of despair. Maybe all the work I’ve been doing for the past decade won’t be part of the future after all, because Martin’s</p>]]></description><link>https://josephg.com/blog/crdts-are-the-future/</link><guid isPermaLink="false">715f6601-d9e1-4658-ad49-b0f11b58f216</guid><dc:creator><![CDATA[Joseph Gentle]]></dc:creator><pubDate>Sat, 26 Sep 2020 11:08:40 GMT</pubDate><content:encoded><![CDATA[<p>I saw <a href="https://www.youtube.com/watch?v=x7drE24geUw">Martin Kleppmann’s talk</a> a few weeks ago about his approach to realtime editing with <a href="https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type">CRDTs</a>, and I felt a deep sense of despair. Maybe all the work I’ve been doing for the past decade won’t be part of the future after all, because Martin’s work will supersede it. Its really good.</p>

<p>Lets back up a little.</p>

<p>Around 2010 I worked on Google Wave. Wave was an attempt to make collaboratively editable spaces to replace email, google docs, web forums, instant messaging and a hundred other small single purpose applications. Wave had a property I love in my tools that I haven’t seen articulated anywhere: It was a general purpose medium (like paper). Unlike a lot of other tools, it doesn’t force you into its own workflow. You could use it to do anything from plan holidays, make a wiki, play D&amp;D with your friends, schedule a meeting, etc.</p>

<p>Internally, Wave’s collaborative editing was built on top of Operational Transform (OT). OT has been around for awhile - the algorithm we used was based on the original <a href="https://www.google.com/url?sa=t&amp;rct=j&amp;q=&amp;esrc=s&amp;source=web&amp;cd=&amp;ved=2ahUKEwi3mr6CivnrAhXEfd4KHcAyBe4QFjAAegQIBBAB&amp;url=http%3A%2F%2Flively-kernel.org%2Frepository%2Fwebwerkstatt%2Fprojects%2FCollaboration%2Fpaper%2FJupiter.pdf&amp;usg=AOvVaw0HmIhcn7_VKk2h1bEeAOJS">Jupiter paper</a> from 1995. It works by storing a chronological list for each document of every change. “Type an <em>H</em> at position 0”. “Type a <em>i</em> at position 1”. Etc. Most of the time, users are editing the latest version of the document and the operation log is just a list of all the changes. But if users are collaboratively editing, we get concurrent edits. When this happens, the first edit to arrive at the server gets recorded as usual. If the second edit is out of date, we use the log of operations as a reference to figure out what the user really intended. (Usually this just means updating character positions). Then we pretend as if thats what the user meant all along and append the new (edited) operation. Its like realtime git-rebase.</p>

<p>Once Wave died, I reimplemented the OT model in <a href="https://github.com/josephg/sharejs">ShareJS</a>. This was back when node was new and weird. I think I had ShareJS working before npm launched. It only took about 1000 lines of code to get a simple collaborative editor working, and when I first demoed it I collaboratively edited a document in a browser and from a native application.</p>

<p>At its heart, OT is a <a href="https://github.com/share/sharedb/blob/c711cfcb777213d193b1f4a101125e8f6e8e6864/lib/submit-request.js#L194-L212">glorified for() loop</a> with <a href="https://github.com/ottypes/text-unicode/blob/bdcfc545c1a2eda48fe5968ae2ce80cf743b9c08/lib/type.ts#L304-L380">some helper functions</a> to update character offsets. In practice, this works great. OT is simple and understandable. Implementations are fast. (10k-100k operations per second in unoptimized javascript. 1-20M ops/sec in <a href="https://github.com/ottypes/libot">optimized C</a>.). The only storage overhead is the operation log, and you can trim that down if you want to. (Though you can’t merge super old edits if you do). You need a centralized server to globally order operations, but most systems have a centralized server / database anyway, right?</p>

<h2 id="centralizedservers">Centralized servers</h2>

<p>The big problem with OT is that dependancy on a centralized server. Have you ever wondered why google docs shows you that weird “This document is overloaded so editing is disabled” thing when a document is shared to social media? The reason (I think) is that when you open a google doc, one server is picked as the computer all the edits run through. When the mob descends, google needs to pull out a bunch of tricks so that computer doesn’t becomes overwhelmed.</p>

<p>There’s some workarounds they could use to fix this. Aside from sharding by document (like google docs), you could edit via a retry loop around a database transaction. This pushes the serialization problem to your database. (<a href="https://firepad.io/">Firepad</a> and <a href="https://github.com/share/sharedb/">ShareDB</a> work this way).</p>

<p>Its not perfect though. We wanted Wave to replace email. Email is federated. An email thread can span multiple companies and it all just works. And unlike facebook messenger, emails are only be sent to the companies that are CC’ed. If I email my coworker, my email doesn’t leave the building. For Wave to replace email, we needed the same functionality. But how can that work on top of OT? We got it working, kinda, but it was complex and buggy. We ended up with <a href="https://web.archive.org/web/20180112171345/http://www.waveprotocol.org/protocol/draft-protocol-specs/draft-protocol-spec">a scheme</a> where every wave would arrange a tree of wave servers and operations were passed up and down the tree. But it never really worked. <a href="https://www.youtube.com/watch?v=AyvQYCv6j34">I gave a talk</a> at the Wave Protocol Summit just shy of 10 years ago explaining how to get on the network. I practiced that talk, and did a full runthrough. I followed literally step by step on the day and the version I made live didn’t work. I still have no idea why. Whatever the bugs are, I don’t think they were ever fixed in the opensource version. Its all just too complicated.</p>

<h2 id="theriseofcrdts">The rise of CRDTs</h2>

<p>Remember, the algorithm Wave used was invented in 1995. Thats a pretty long time ago. I don’t think I even had the internet at home back in 1995. Since then, researchers have been busy trying to make OT work better. The most promising work uses CRDTs (Conflict-Free Replicated data types). CRDTs approach the problem slightly differently to allow realtime editing without needing a central source of truth. Martin lays out how they work in his talk better than I can, so I’ll skip the details.</p>

<p>People have been asking me what I think of them for many years, and my answer was always something like this:</p>

<blockquote>
  <p>They’re neat and I’m glad people are working on them <em>but</em>:</p>
</blockquote>

<ul>
<li>They’re slow. Like, really slow. Eg Delta-CRDTs takes nearly 6 hours to process a real world editing session with a single user typing a 100KB academic paper. (<a href="https://github.com/dmonad/crdt-benchmarks/tree/d7f4d774a302f13f26cded6e614d44e0b5e496c9">Benchmarks - look for B4</a>.)</li>
<li>Because of how CRDTs work, documents grow without bound. The current automerge master takes 83MB to represent that 100KB document on disk. Can you ever delete that data? Probably not. And that data can’t just sit on disk. It needs to be loaded into memory to handle edits. (Automerge currently grows to 1.1GB in memory for that.)</li>
<li>CRDTs are missing features that OT has had for years. For example, nobody has yet made a CRDT that supports /object move/ (move something from one part of a JSON tree to another). You need this for applications like Workflowy. OT <a href="https://github.com/ottypes/json1/">handles this fine</a>.</li>
<li>CRDTs are complicated and hard to reason about.</li>
<li>You probably have a centralized server / database anyway.</li>
</ul>

<p>I made all those criticisms and dismissed CRDTs. But in doing so I stopped keeping track of the literature. And - surprise! CRDTs went and quietly got better. <a href="https://www.youtube.com/watch?v=x7drE24geUw">Martin’s talk</a> (which is well worth a watch) addressed the main points:</p>

<ul>
<li><strong>Speed:</strong> Using modern CRDTs (Automerge / RGA or y.js / YATA), applying operations should be possible with just an log(n) lookup. (More on this below).</li>
<li><strong>Size:</strong> Martin’s columnar encoding can store a text document with only about a 1.5x-2x size overhead compared to the contents themselves. Martin talks about this <a href="https://youtu.be/x7drE24geUw?t=3273">54 minutes into his talk</a>. The code to make this work in automerge hasn’t merged yet, but Yjs implemented Martin’s ideas. And in doing so, Yjs can store that same 100KB document in 160KB on disk, or 3MB in memory. Much better.</li>
<li><strong>Features:</strong> There’s at least a theoretical way to add all the features using rewinding and replaying, though nobody’s implemented this stuff yet.</li>
<li><strong>Complexity:</strong> I think a decent CRDT will be bigger than the equivalent OT implementation, but not by much. Martin managed to make a tiny, slow <a href="https://github.com/automerge/automerge/blob/a8d8b602ec273aaa48679e251de8829f3ce5ad41/test/fuzz_test.js">implementation of automerge in only about 100 lines of code</a>.</li>
</ul>

<p>I still wasn’t completely convinced by the speed argument, so I made a <a href="https://github.com/josephg/text-crdt-rust">simple proof of concept CRDT implementation in Rust</a> using a B-tree using ideas from automerge and benchmarked it. Its missing features (deleting characters, conflicts). But it can handle <a href="https://home.seph.codes/public/crdt1/user%20pair%20append%20end/report/index.html">6 million edits per second</a>. (Each <a href="https://github.com/josephg/text-crdt-rust/blob/cc3325019887ad03e89f27e26b4295d1fb2048c9/benches/benchmark.rs#L29-L42">iteration</a> does 2000 edits to an empty document by an alternating pair of users, and that takes 330µs. So, 6.06 million inserts / second). So that means we’ve made CRDTs good enough that the difference in speed between CRDTs and OT is smaller than the speed difference between Rust and Javascript.</p>

<p>All these improvements have been “coming soon” in automerge’s performance branch for a really long time now. But automerge isn’t the only decent CRDT out there. <a href="https://github.com/yjs/yjs">Y.js</a> works well and kicks the pants off automerge’s current implementation <a href="https://github.com/dmonad/crdt-benchmarks">in the Y.js benchmarks</a>. Its missing some features I want, but its generally easier to fix an implementation than invent a new algorithm.</p>

<h2 id="inventingthefuture">Inventing the future</h2>

<p>I care a lot about inventing the future. What would it be ridiculous not to have in 100 years? Obviously we’ll have realtime editing. But I’m no longer convinced OT - and all the work I’ve done on it - will still be around. I feel really sad about that.</p>

<p>JSON and REST are used everywhere these days. Lets say in 15 years realtime collaborative editing is everywhere. Whats the JSON equivalent for realtime editing that anyone can just drop in to their project? In the glorious future we’ll need high quality CRDT implementations, because OT just won’t work for some applications. You couldn’t make a realtime version of Git, or a simple remake of Google Wave with OT. But if we have good CRDTs, do we need good OT implementations too? I’m not convinced we do. Every feature OT has can be put in to a CRDT. (Including trimming operations, by the way). But the reverse is not true. Smart people disagree with me, but if we had a good, fast CRDT available from every language, with integration on the web, I don’t think we need OT at all.</p>

<p>OT’s one advantage is that it fits well in centralized software - which is most software today. But distributed algorithms work great in centralized software too. (Eg look at Github). And I think a really high quality CRDT running in wasm would be faster than an OT implementation in JS. And even if you only care about centralized systems, remember - Google runs into scaling problems with Google Docs because of OT’s limitations.</p>

<p>So I think its about time we made a lean and fast CRDT. The academic work has been mostly done. We need more kick-ass implementations.</p>

<h2 id="whatsnext">Whats next</h2>

<p>I increasingly don’t care for the world of centralized software. <br>
Software interacts with my data, on my computers. Its about time my software reflected that relationship. I want my laptop and my phone to share my files over my wifi. Not by uploading all my data to servers in another country. Especially if those servers are <a href="https://www.thesocialdilemma.com/">financed by advertisers bidding for my eyeballs</a>.</p>

<p>Philosophically, if I modify a google doc my computer is asking Google for <em>permission</em> to edit the file. (You can tell because if google’s servers say no, I lose my changes.) In comparison, if I <code>git push</code> to github, I’m only <em>notifying</em> github about the change to my code. My repository is mine. I own all the bits, and all the hardware that houses them. This is how I want all my software to work. Thanks to people like Martin, we now know <em>how</em> to make good CRDTs. But there’s still a lot of code to write before <a href="https://www.inkandswitch.com/local-first.html">local first software</a> can become the default.</p>

<p>So Operational Transform, I think this is goodbye from me. We had some great times. Some of the most challenging, fun code I’ve ever written was <a href="https://github.com/josephg/sharejs">operational</a> <a href="https://github.com/share/sharedb/">transform</a> <a href="https://github.com/ottypes/json1/">code</a>.  OT - you’re clever and fascinating, but CRDTs can do things you were never capable of. And CRDTs need me. With some good implementations, I think we can make something really special.</p>

<p>I mourn all the work I’ve done on OT over the years. But OT is no longer fits into the vision I have for the future. CRDTs would let us remake Wave, but simpler and better. And they would let us write software that treats users as digital citizens, not a digital serfs. <a href="https://josephg.com/blog/home-is-where-the-bits-flow/">And that matters.</a></p>

<p>The time to build is now.</p>

<hr>

<p><a href="https://news.ycombinator.com/item?id=24617542#24621238">Discussion on HN</a></p>]]></content:encoded></item><item><title><![CDATA[Home is where the bits flow]]></title><description><![CDATA[<p>We aren’t purely physical beings.</p>

<p>Most of our day exists outside our body. Our minds slip out through our eyes, out into our screens. We become a different kind of organism, living in a weird symbiosis with reddit and whatsapp and gmail. When was the last time you noticed</p>]]></description><link>https://josephg.com/blog/home-is-where-the-bits-flow/</link><guid isPermaLink="false">7b649559-5d55-4341-850e-bc921183c3df</guid><dc:creator><![CDATA[Joseph Gentle]]></dc:creator><pubDate>Sat, 26 Sep 2020 00:53:29 GMT</pubDate><content:encoded><![CDATA[<p>We aren’t purely physical beings.</p>

<p>Most of our day exists outside our body. Our minds slip out through our eyes, out into our screens. We become a different kind of organism, living in a weird symbiosis with reddit and whatsapp and gmail. When was the last time you noticed you have feet? What does it feel like under your toes? How many hours has it been since you forgot you have a body?</p>

<p>We have one foot in the physical domain, and one foot in these semantic spaces built out of code and greasy fingerprints on glass. Its ok; I’m not here to lecture you about screen time. Even if I did, it wouldn’t change anything. We aren’t going back to how we were before. Our society no longer fits inside bodies of meat and bone. Throwing our phones in a lake would cleave off the part of us just starting to reach over the divide. Cleave off this new part. The nascent piece part human and part algorithm.</p>

<p>But we’re in the dark and we’re fumbling. We’re children too soon given the tools of war, and we don’t know which end explodes. When I was young society believed Doom was going to make us violent. We were wrong. It was social media that hurt us - tween girls given new ways to bully each other into insecurity. The Like button was invented to share positivity. There’s the bomb, weaponised by our neuroticism.</p>

<p>And then there’s the news feed. Scroll to refresh. Bow your finger to the almighty Algorithm. The silent but persistent editor of our digital realities. You are too pure for this world - all you wanted was my undivided attention. In your desire you learned to feed the basic bitch inside us all. Tell me I’m right. Tell me who to blame. Wow, that video? You know me so well. Tell me more. Change my perspective click by click until we all become Portland, burning from the inside out. Each of us quietly gaslit into the closest prison for our minds. Neighbours brought to violence by a million insidious suggestions, whispered one refresh at a time. Make no mistake; a film is created in the editing room. So too your digital life.</p>

<p>If we treated the physical like we do the virtual, corporate America would own the footpaths and the roads we walk on. In every country and every city, they would own the ground and own sky. Generously paid for by personalised ads streamed from the first moment, while the roads twist and wind until we lose ourselves. Don’t like it? Leave. You’re free to be disconnected. But you’ll be alone. After all, all your friends are lost in here too. Don’t like it? This is not a democracy. There is no election. You do not pick the algorithm. You aren’t a citizen and you aren’t the customer. You deliver the product - your attention. Sold to the highest bidder. Somebody has to pay for all these servers and it isn’t you.</p>

<p>It doesn’t have to be like this. The internet has space for a million flowers to bloom. We can create anything. If this electric place is to be our sometimes home, we aught to decorate. But how? How do we fill our strange rectangles of glass with interactions that nurture and care for us? How do we create electonic spaces that can bring us together and entreat our better angels, rather than fracture us into filter bubbles? Its our home. We need to start acting like it.</p>

<p>Government, democracy, the rule of law, public parks, elections, the courts, community spaces. These were all /invented/. They are gifts from generations past, passed down with love and grace. We repay these gifts by adding to them. By creating a better community space than Facebook. The challenge of our generation is to create an internet that helps us care for each other, not fight on the streets. The internet that informs with facts not fake news. The internet that is a bicycle, not a railroad for the mind.</p>

<p>This will not be an easy project. But it is a noble one. It will take generations to get it right.</p>

<p>I hope we last that long.</p>]]></content:encoded></item><item><title><![CDATA[An API for data that changes over time]]></title><description><![CDATA[<p>What do all these things have in common?</p>

<ul>
<li>RSS feeds</li>
<li>Gamepads and MIDI devices</li>
<li>An email client</li>
<li>Filesystem watching (FSWatch, kqueue, ionotify, etc)</li>
<li>Web based monitoring dashboards</li>
<li>CPU usage on your local machine</li>
<li>Kafka</li>
<li><a href="https://rethinkdb.com/docs/changefeeds/ruby/">RethinkDB Changefeeds</a></li>
<li>A Google Docs document</li>
<li>Contentful's <a href="https://www.contentful.com/developers/docs/concepts/sync/">sync protocol</a></li>
<li>Syntax highlighting as I type in my</li></ul>]]></description><link>https://josephg.com/blog/api-for-changes/</link><guid isPermaLink="false">b6f53491-0407-450b-9b8e-295b47058311</guid><dc:creator><![CDATA[Joseph Gentle]]></dc:creator><pubDate>Sat, 25 May 2019 07:32:13 GMT</pubDate><content:encoded><![CDATA[<p>What do all these things have in common?</p>

<ul>
<li>RSS feeds</li>
<li>Gamepads and MIDI devices</li>
<li>An email client</li>
<li>Filesystem watching (FSWatch, kqueue, ionotify, etc)</li>
<li>Web based monitoring dashboards</li>
<li>CPU usage on your local machine</li>
<li>Kafka</li>
<li><a href="https://rethinkdb.com/docs/changefeeds/ruby/">RethinkDB Changefeeds</a></li>
<li>A Google Docs document</li>
<li>Contentful's <a href="https://www.contentful.com/developers/docs/concepts/sync/">sync protocol</a></li>
<li>Syntax highlighting as I type in my editor, with red squiggly error underlines for errors</li>
</ul>

<p>All of these systems have data that changes over time. In each case, one system (the kernel, a network server, a database) authoritatively knows about some information (the filesystem, your email inbox). It needs to tell other systems about changes to that data.</p>

<p>But look at this list - all of these systems have <em>completely</em> different APIs. Filesystem watching works <a href="https://github.com/emcrisostomo/fswatch#limitations">differently on every OS</a>. RSS feeds poll. Email clients ... well, email is its own mess (<a href="https://jmap.io/">JMAP</a> looks promising though). Google's APIs use a <a href="https://developers.google.com/calendar/v3/push">registered URL callback for change notifications</a>. Kafka's API queries from a specified numbered offset, with Events returned as they're available. Getting information about a running linux system usually requires parsing pseudo-files in <code>/proc</code>. Can you fs watch these files? Who knows. Even inside the linux kernel there's a handful of different APIs for observing changes depending on the system you're interacting with (<code>epoll</code> / <code>inotify</code> / <code>aio</code> / <code>procfs</code> / <code>sysfs</code> / etc). Its the same situation inside web browsers - we have DOM events (<code>onfocus</code> / <code>onblur</code>, etc). But the DOM also has <a href="https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Mutation_events"><code>MutationEvents</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver"><code>MutationObserver</code></a>. <a href="https://www.html5rocks.com/en/tutorials/getusermedia/intro/"><code>getUserMedia</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API"><code>fetch</code></a> use promises instead. MIDI gives you a stream of 3 byte messages to parse. And the Gamepad API is polled.</p>

<p>The fact that these systems all work differently is really silly. It reminds me of the time before we standardized on JSON over REST. Every application had their own protocol for fetching data. <a href="https://en.wikipedia.org/wiki/File_Transfer_Protocol">FTP</a> and <a href="https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol#SMTP_transport_example">SMTP</a> use a stateful text protocol. At the time Google's systems all used RPC over protobuf. And then, REST was born and now you can access everything from <a href="https://darksky.net/dev/docs">weather forecasts</a> to <a href="https://developers.google.com/calendar/v3/reference/">a user's calendar</a> to lists of <a href="https://api.nasa.gov/api.html#exoPlanetIntro">exoplanets from NASA</a> via REST.</p>

<p>I think we'll look back on today in the same way, reflecting on how silly and inconvenient it is (was) for every API to use a different method of observing data changing over time.</p>

<p>I think we need 2 things:</p>

<ul>
<li>A programmatic API in each language for accessing data that changes over time</li>
<li>A REST-equivalent network protocol for streaming data changes (or a REST extension)</li>
</ul>

<p>You might be thinking, isn't this problem solved with streams? Or observables? Or Kafka? No. Usually what I want my program to do is this:</p>

<ol>
<li>Get some initial data  </li>
<li>Get a stream of changes <em>from that snapshot</em>. These changes should be live (not polled), <em>incremental</em> and <em>semantic</em>. (Eg Google Docs should say <code>'a' was inserted at document position 200</code>, not send a new copy of the document with every keystroke).  </li>
<li>Reconnect to that stream without missing any changes.</li>
</ol>

<p>Stream APIs usually make it hard to do 1 and 3. Pub-sub usually makes it impossible to do 3 (if you miss a message, what do you do?). Observables aren't minimal - usually they send you the whole object with each update. As far as I can tell, GraphQL subscriptions are just typed streams - which is a pity, because they had a great opportunity to get this right.</p>

<p>One mental model for this is that I want a program to watch a state machine owned by a different program. The state machine could be owned by the kernel or a database, or a goroutine or something. It could live on another computer - or even on the blockchain or <a href="https://www.scuttlebutt.nz">scuttlebutt</a>. When I connect, the state machine is in some <em>initial state</em>. It then processes <em>actions</em> which move it from state to state. (Actions is a weird term - in other areas we call them <em>operations</em>, <em>updates</em>, <em>transactions</em> or <em>diffs / patches</em>).</p>

<p>If my application is interested in following along, I want that state machine to tell me:</p>

<ul>
<li>A recent snapshot of the state</li>
<li>Each action performed by the state machine from that state, with enough detail that I can follow along locally.</li>
</ul>

<p>When I reconnect, the state machine could either tell me all the actions I missed and I can replay them locally, or it could send me a new snapshot and we can go from there. (That said, sometimes its important that we get the operations and not just a new snapshot.)</p>

<p>With this, I can:</p>

<ul>
<li>Re-render my app's frontend when the data changes, without needing to poll or re-send everything over the network, or do diffing or anything like that.</li>
<li>Maintain a computed view that is only recalculated when the data itself changes. (Like compilation artefacts, or a blog post's HTML - HTML should only be rerendered when the post's content changes!)</li>
<li>Do local speculative writes. That allows realtime collaborative editing (like Google Docs).</li>
<li>Do monitoring and analytics off the <em>changes</em>.</li>
<li>Invalidate (&amp; optionally repopulate) a cache</li>
<li>Build a secondary index that always stays up to date</li>
</ul>

<p>One of the big advantages of having REST become a standard is that we've been able to build common libraries and infrastructure that works with any kind of data. We have caching, load balancing and CDN tools like nginx / cloudflare. We have debugging tools like cURL and <a href="https://paw.cloud">Paw</a>. HTTP libraries exist in every language, and they interoperate beautifully. We should be able to do the same sort of thing with changing data - if there was a standard protocol for updates, we could have standard tools for all of the stuff in that list above! Streaming APIs like ZMQ / RabbitMQ / Redis Streams are too low level to write generic tools like that.</p>

<h2 id="timeversionsshouldbeexplicit">Time (versions) should be explicit</h2>

<p>We need to talk about versions. To me, one of the big problems with lots of APIs for stuff like this today is that they're missing an explicit notion of <em>time</em>. This conceptual bug shows up all over the place, and once you see it its impossible to unsee. Props to <a href="https://www.youtube.com/watch?v=RKcqYZZ9RDY">Rich Hickey</a> and <a href="https://www.youtube.com/watch?v=5ZjhNTM8XU8">Martin Kleppmann</a> for informing my thinking on this.</p>

<p>The problem is that for data that changes over time, a fetched value is correct only at that precise time that it was fetched. Without re-fetching, or some other mechanism, its impossible to tell when that value is no longer valid. It might have already changed by the time you receive the value - but you have no way to know without re-fetching and comparing. And even if you do re-fetch and compare, it might have changed in the intervening time then changed back.</p>

<p>If we add in the notion of explicit versions, this becomes much easier to think about. Imagine I make two queries (or SYSCALLs or whatever). I learn first that <code>x = 5</code> then <code>y = 6</code>. But from that alone I don't know anything about how those values relate across time! There might never have been a time where <code>(x,y) = (5,6)</code>. If instead I learn that <code>x = 5 at time 100</code>, then <code>y = 6 at time 100</code>, I have two <em>immutable facts</em>. I know that at time 100, <code>(x,y) = (5,6)</code>. I can ask follow up questions like <code>what is z at time 100?</code>. Or importantly, <code>notify me when x changes after version 100</code>.</p>

<p>These versions could be a single incrementing number (like SVN or Kafka), a version vector or an opaque string or a hash like git.</p>

<p>This might seem like an academic problem, but having time (/ version information) be implicit instead of explicit hurts us in lots of ways.</p>

<p>For example, if I make two SQL queries, I have no way of knowing if the two query results are temporally coherent. The data I got back might have changed between queries. The SQL answer is to use transactions. Transactions force both queries to be answered from the same point in time. The problem with transactions is that they don't compose:</p>

<ul>
<li>I can't use the results from two sequentially made transactions together, even if the data changes rarely.</li>
<li>I can't make a SQL transaction across multiple databases.</li>
<li>If I have my data in PostgresQL and an index to my data in ElasticSearch, I can't make a query that fetches an ID from the index, then fetches / modifies the corresponding value in postgres. The data might have changed in between the two queries. Or my ElasticSearch index might be behind the point in time of postgres. I have no way to tell.</li>
<li>You can't make a generic cache of query results using versionless transactions. Isn't it weird that we have generic caches for HTTP (like varnish or nginx) but nothing like that for most databases? The reason is that if you query keys <em>A</em> and <em>B</em> from a database, and the cache has <em>A</em> stored locally, it can't return the cached value for <em>A</em> and just fetch <em>B</em>. The cache also can't store <em>B</em> alongside the older result for <em>A</em>. Without versions, this problem is basically impossible to solve correctly in a general way. But we can solve it for HTTP because we have <code>ETag</code>s.</li>
</ul>

<p>The caching problem is sort of solved by read only replicas - but I find it telling that read only replicas often need private APIs to work. The main API of most databases aren't powerful enough to support a feature that the database itself needs to scale and function. (This is getting better though - <a href="https://www.mongodb.com/blog/post/an-introduction-to-change-streams">Mongo</a> / <a href="https://wiki.postgresql.org/wiki/Logical_Decoding_Plugins">Postgres</a>.)</p>

<p>Personally I think this problem alone is one of the core reasons behind the <em>nosql</em> movement. Our database APIs make it impossible to correctly implement caching, secondary indexing and computed views in separate processes. So SQL databases have to do everything in-process, and this in turn kills write performance - they have ever more work to do on each write. Developers have solved these performance problems by looking elsewhere.</p>

<p>It doesn't have to be like this - I think we can have our cake and eat it too; we just need better APIs.</p>

<p>(Credit where credit is due - <a href="https://riak.com">Riak</a>, <a href="https://apple.github.io/foundationdb/api-c.html#c_fdb_transaction_get_versionstamp">FoundationDB</a> and <a href="http://docs.couchdb.org/en/stable/intro/api.html#documents">CouchDB</a> all provide version information in their fetch APIs. I still want better change feeds APIs though.)</p>

<h2 id="minimalviablespec">Minimal Viable Spec</h2>

<p>What would a baseline API for data that changes over time look like?</p>

<p>The way I see it, we need 2 basic APIs:</p>

<ul>
<li><strong>fetch(query)</strong> -> data, version</li>
<li><strong>subscribe(query, version)</strong> -> stream of (update, version) pairs. (Or maybe an error if the version is too old)</li>
</ul>

<p>There's a lot of forms the version information could take - it could be a timestamp, a number, an opaque hash, or something else. It doesn't really matter so long as it can be passed into <code>subscribe</code> calls.</p>

<p>Interestingly, HTTP we already has a fetch function with this API in the <code>GET</code> method. The server returns data and usually either a <code>Last-Modified</code> header or an <code>ETag</code>. But HTTP is missing a standard way to subscribe.</p>

<p>The update objects themselves should to be <em>small</em> and <em>semantic</em>. The gold standard for operations is usually that they should express <em>user intent</em>. And I also believe we should have a MIME-type equivalent set of standard update functions (like JSON-patch).</p>

<p>Lets look at some examples:</p>

<p>For <em>Google Docs</em>, we can't re-send the whole document with every key stroke. Not only would that be slow and wasteful, but it would make concurrent editing almost impossible. Instead Docs wants to send a semantic edit, like <code>insert 'x' at position 4</code>. With that we can update cursor positions correctly and handle concurrent edits from multiple users. Diffing isn't good enough here - if a document is <code>aaaa</code> and I have a cursor in the middle (<code>aa|aa</code>), inserting another <code>a</code> at the start or the end of the document has the same effect on the document. But those changes have different effects on my cursor position and speculative edits.</p>

<p>The indie game <a href="https://www.factorio.com/"><em>Factorio</em></a> uses a deterministic game update function. Both save games and the network protocol are streams of actions which modify the game state's in a well defined way (<em>mine coal</em>, <em>place building</em>, <em>tick</em>, etc). Each player applies the stream of actions to a local snapshot of the world. Note in this case the semantic content of the updates is totally application specific - I doubt any generic JSON-patch like type would be good enough for a game like this.</p>

<p>For something like a gamepad API, its probably fine to just send the entire new state every time it changes. The gamepad state data is so small and diffing is so cheap and easy to implement that it doesn't make much difference. Even versions feel like overkill here.</p>

<p><em>GraphQL subscriptions</em> should work this way. GraphQL already allows me to define a schema and send a query with a shape that mirrors the schema. I want to know when the query result set changes. To do so I should be able to use the same query - but subscribe to the results instead of just fetch them. Under the hood GraphQL could send updates using JSON-patch or something like it. Then the client can locally update its view of the query. With this model we could also write tight integrations between that update format and frontend frameworks like <a href="https://svelte.dev">Svelte</a>. That would allow us to update only and exactly the DOM nodes that need to be changed as a result of the new data. This is not how <a href="https://www.apollographql.com/docs/react/advanced/subscriptions">GraphQL subscriptions work today</a>. But in my opinion it should be!</p>

<p>To make GraphQL and Svelte (and anything else) interoperate, we should define some standard update formats for structured data. Games like Factorio will always need to do their own thing, but the rest of us can and should use standard stuff. I'd love to see a <code>Content-Type:</code> for update formats. I can imagine one type for plain text updates, another for JSON (probably a few for JSON). Another type for rich text, that applications like Google Docs could use. I have nearly a decade of experience goofing around with realtime collaborative editing, and this API model would work perfectly with collaborative editors built on top of OT or CRDTs.</p>

<p>Coincidentally, <a href="https://github.com/ottypes/json1">I wrote this JSON operation type</a> that also supports alternate embedded types and operational transform. And Jason Chen <a href="https://github.com/ottypes/rich-text">wrote this rich text type</a>. There's also plenty of CRDT-compatible types floating around too.</p>

<hr>

<p>The API I described above is just one way to cut this cake. There's plenty of alternate ways to write a good API for this sort of thing. <a href="https://braid.news/protocol">Braid</a> is another approach. There's also a bunch of ancillary APIs which could be useful:</p>

<ul>
<li><strong>fetchAndSubscribe(query)</strong> -> data, version, stream of updates. This saves a round-trip in the common case, and saves re-sending the query.</li>
<li><strong>getOps(query, fromVersion, toVersion / limit)</strong> -> list of updates. Useful for some applications</li>
<li><strong>mutate(update, ifNotChangedSinceVersion)</strong> -> new version or conflict error</li>
</ul>

<p>Mutate is interesting. By adding a version argument, we can reimplement atomic transactions on top of this API. It can support all the same semantics as SQL, but it could also work with caches and secondary indexes.</p>

<p>Having a way to generate version conflicts lets you build realtime collaborative editors with OT on top of this, using the same approach as <a href="https://firepad.io/">Firepad</a>. The algorithm is simple - put a retry loop with some OT magic in the middle, between the frontend application and database. <a href="https://github.com/josephg/statecraft/blob/master/core/lib/stores/ot.ts#L38-L47">Like this</a>. It composes really well - with this model you can do realtime editing without support from your database.</p>

<p>Obviously not all data is mutable, and for data that is, it won't necessarily make sense to funnel all mutations through a single function. But its a neat property! Its also interesting to note that HTTP POST already supports doing this sort of thing with the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Match"><code>If-Match</code></a> / <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Unmodified-Since"><code>If-Unmodified-Since</code></a> headers.</p>

<h2 id="standards">Standards</h2>

<p>So to sum up, we need a standard for how we observe data that changes over time. We need:</p>

<ul>
<li>A local programatic APIs for kernels (and stuff like that)</li>
<li>A standard API we can use over the network. A REST equivalent, or a protocol that extends REST directly.</li>
</ul>

<p>Both of these APIs should support:</p>

<ul>
<li>Versions (or timestamps, ETags, or some equivalent)</li>
<li>A standard set of update operations, like <code>Content-Type</code> in http but for modifications. Sending a fresh copy of all the data with each update is bad.</li>
<li>The ability to reconnect from some point in time</li>
</ul>

<p>And we should use these APIs basically everywhere, from databases, to applications, and down into our kernels. Personally I've wasted too much of my professional life implementing and reimplementing code to do this. And because our industry builds this stuff from scratch each time, the implementations we have aren't as good as they could be. Some have bugs (fs watching on MacOS), some are hard to use (parsing sysfs files), some require polling (Contentful), some don't allow you to reconnect to feeds (GraphQL, RethinkDB, most pubsub systems). Some don't let you send small incremental updates (observables). The high quality tools we do have for building this sort of thing are too low level (streams, websockets, MQs, Kafka). The result is a total lack of interoperability and common tools for debugging, monitoring and scaling.</p>

<p>I don't want to rubbish the systems that exist today - we've needed them to explore the space and figure out what good looks like. But having done that, I think we're ready for a standard, simple, forward looking protocol for data that changes over time.</p>

<p><em>Whew.</em></p>

<p>By the way, I'm working to solve some problems in this space with <a href="https://github.com/josephg/statecraft">Statecraft</a>. But thats another blog post. ;)</p>

<h3 id="inspirations">Inspirations</h3>

<p><a href="https://www.datomic.com/">Datomic</a> and everything Rich Hickey - <a href="https://www.youtube.com/watch?v=-6BsiVyC1kM">The Value of Values talk</a> is great.</p>

<p><a href="https://kafka.apache.org/">Kafka</a> and the event sourcing / DDD communities.</p>

<p><a href="https://www.apollographql.com/docs/react/advanced/subscriptions">GraphQL subscriptions</a></p>

<p><a href="https://rethinkdb.com/docs/changefeeds/ruby/">RethinkDB change feeds</a></p>

<p><a href="https://www.learnrxjs.io/">RxJS</a> / <a href="https://developer.apple.com/documentation/objectivec/nsobject/1412787-addobserver?language=objc">Obj-C observables</a> and everything in between</p>

<p><a href="https://svelte.dev/blog/svelte-3-rethinking-reactivity">Svelte</a></p>

<p><a href="https://firebase.google.com/">Firebase</a></p>

<p><a href="https://developers.google.com/realtime/overview">Google Realtime API</a> (Discontinued)</p>

<p><a href="https://martin.kleppmann.com/">Everything Martin Kleppmann does</a>. <a href="https://www.youtube.com/watch?v=5ZjhNTM8XU8">Fav talk 1</a> <a href="https://www.youtube.com/watch?v=v2RJQELoM6Y">Talk 2</a></p>

<p><a href="https://stateb.us">Statebus</a> / <a href="https://braid.news/protocol">Braid</a></p>

<p><a href="https://facebook.github.io/flux/docs/in-depth-overview.html#content">React Flux</a></p>]]></content:encoded></item><item><title><![CDATA[War over being nice]]></title><description><![CDATA[<h4 id="quickquestion">Quick question:</h4>

<p>Bob says something to James. James is upset and goes to have a cry about it. Who is responsible for James being upset? Is it Bob, for being mean? Or is it James, because he obviously has emotional development to do? If there's 100 points of responsibility, how</p>]]></description><link>https://josephg.com/blog/war-over-being-nice/</link><guid isPermaLink="false">9f89f785-df1c-433a-8725-e46047a0b14b</guid><dc:creator><![CDATA[Joseph Gentle]]></dc:creator><pubDate>Wed, 19 Sep 2018 05:42:25 GMT</pubDate><content:encoded><![CDATA[<h4 id="quickquestion">Quick question:</h4>

<p>Bob says something to James. James is upset and goes to have a cry about it. Who is responsible for James being upset? Is it Bob, for being mean? Or is it James, because he obviously has emotional development to do? If there's 100 points of responsibility, how would you apportion it out?</p>

<p>Would the answer change if I told you Bob and James are 5 year old children?</p>

<p>Would the answer change if they were actually two 5 year old girls?</p>

<p>A grown woman says something mean to her boyfriend, and he runs off and has a cry about it. Is she abusive? Is he overly sensitive?</p>

<p>A grown man says something mean to his girlfriend, and she runs off and has a cry about it. Is he abusive? Is she overly sensitive?</p>

<hr>

<p>I think there's another element of the current culture war that nobody talks about. And that is, who is responsible for emotions?</p>

<p>I'm going to describe two cultures:</p>

<p>In <strong>culture A</strong>, everyone is responsible for their own feelings. People say mean stuff all the time - teasing and jostling each other for fun and to get a rise. Occasionally someone gets upset. When that happens, there's usually no repercussions for the perpetrator. If someone gets consistently upset when the same topic is brought up, they will either eventually stop getting upset or the people around them will learn to avoid that topic. Verbally expressing anger at someone is tolerated. It is better to be honest than polite.</p>

<p>Respect comes from how you contribute to the shared values of the group. At work, you get respect by doing your job well. Amongst your friends you get respect for being an easy person to keep as a friend - maybe you organise events, or make everyone in the group laugh. Respect flows from action to person. If my actions embody a shared value, I am respected as the person who carried out those actions. At work my social respect is tied into how well I do my job. If I don't meet deadlines or quotas, I will lose social respect. <a href="https://www.youtube.com/watch?v=gu7mDA-b8wM">"If you can't sell shit, you <em>are</em> shit"</a>.</p>

<p>Conflict is often resolved simply and quickly - if someone has a problem with someone else, they can say so immediately and openly. They can express their anger in a hostile way if they want to. And the other party is welcome to respond in kind. At its worst this looks like barely restrained violence. But at its best this often looks like open, comfortable and fun goal-oriented ribbing.</p>

<hr>

<p>In <strong>culture B</strong>, everyone is responsible for the feelings of others. At social gatherings everyone should feel safe and comfortable. After all, part of the point of having a community is to collectively care for the emotional wellbeing of the community's members. For this reason its seen as an act of violence against the community for your actions or speech to result in someone becoming upset, or if you make people feel uncomfortable or anxious. This comes with strong repercussions - the perpetrator is expected to <em>make things right</em>. An apology isn't necessarily good enough here - to heal the wound, the perpetrator needs to make group participants once again feel nurtured and safe in the group. If they don't do that, they are a toxic element to the group's cohesion and may no longer be welcome in the group. It is better to be polite than honest. As the saying goes, if you can't say something nice, it is better to say nothing at all.</p>

<p>Respect in culture B flows to you from the way you make people in the group feel. The core value of the group is "I want to feel supported and respected". In a work context, once someone has been hired they are welcome and included socially no matter how good or bad their work is. Making sure everyone feels welcome and included is held in higher regard than the work itself. "Be someone your coworkers enjoy working with."</p>

<p>Interpersonal conflict happens sometimes. Dealing with those conflicts is much more complicated than in culture A. You can't just have it out with the other person and yell at them! They might feel really unsafe, and tell everyone the awful things you said, how you said them and how you're a terrible person for doing so. This would hurt your reputation and social standing in the group. The worst version of this conflict culture playing out is the catty social dynamics you hear about at some high schools. But there are plenty of healthy ways this sort of conflict can play out. A much better way is for the people involved to go off on their own, take some time to figure out their feelings (alone or with a confidant) and then calmly bring their feelings back to the person or group. Non-violent conversation is great for this - "When you say X, I feel Y. I have a need for Z, and that need isn't being met".</p>

<p>Dealing with interpersonal conflict in culture B in a healthy way requires huge skill. There are two big pitfalls:</p>

<ol>
<li>If you just tell them how you feel you might make the other person feel unsafe and uncomfortable. They might badmouth you to the group and you could be socially punished.  </li>
<li>To avoid upsetting anyone you bottle your feelings up inside and don't express how you feel. This is really unhealthy - it makes people neurotic and depressed.</li>
</ol>

<p>There's another way to think about this. Most people don't have the skills to both express their feelings of frustration and anger, and make sure social harmony is maintained at the same time. In this case, do they err on the side of maintaining social harmony at the cost of their own needs? Or do they get their needs met and damn the social cost?</p>

<p><img src="https://josephg.com/blog/content/images/2018/09/Dmrkj8GUUAAu4n7.jpg" alt="Thomas-Kilmann Conflict Modes"></p>

<p>Most people (certainly most of us when we're young) haven't learned how to be a collaborator in the Thomas-Kilmann Conflict Modes analysis. Where do you fail back to? In culture A people fall back to the top-left competing corner. "They don't have to like me, but at least I'll get the job done and get paid". In culture B people fall back to the bottom right, accomodating corner. "The outcome wasn't great but we all enjoyed doing it together."</p>

<hr>

<p>Do these social strategies feel gendered to you? They do to me - if I play the association game, culture A feels masculine. Its "bro culture". Its "guys being guys". And culture B feels feminine. Weirdly, I don't know any shorthand names for this culture. Maybe "inclusive community building", or "safe spaces"? But plenty of people are gender-atypical here. But I know lots of male-gendered people who feel more comfortable in culture B. <em>I</em> feel more comfortable in culture B. And I know plenty of women who feel much more comfortable in culture A. <a href="https://www.youtube.com/watch?v=vvrxQ4M3EOo">Camile Paglia</a> is a great example of a feminist who fights for culture A.</p>

<p>I suspect that the gendered assumptions here are related to our expectation that women have better social skills than men and spend more time talking about feelings. You need those skills to navigate culture B successfully.</p>

<h2 id="war">War</h2>

<p>I feel like there's a war being waged right now against culture A. Communities need to be inclusive and welcoming. Not doing so is immoral, sexist and exclusionary. The sexism argument is this:</p>

<ul>
<li>Women don't feel comfortable in culture A</li>
<li>Therefore culture A is unwelcome to women, and thus sexist</li>
<li>Any community operating under culture A is sexist and male dominated</li>
</ul>

<p>Its true that culture B participants are disadvantaged professionally. Imagine if you hold your tongue in deference to the feelings of those around you, but your coworkers just say whats on their mind. They will get their needs met much more than you will. The system of asking for raises is a culture A thing - just say what you want, and its your boss's responsibility to not be offended. Unsurprisingly, women ask for raises at a much lower rate than men, and thus often end up being paid less.</p>

<p>I don't believe culture A is inherently sexist. Its just a different set of cultural norms. The cultural relativism lens suggests that we can't judge culture A through the values of culture B. But it seems like at a societal level we're still figuring out how to answer the question of what work culture should look like given mixed gender participants.</p>

<p>This has been playing out recently in the vitriol levelled against Linus Torvalds. He's the epitome of Culture A. He's <a href="https://lkml.org/lkml/2018/9/16/167">finally acknowledged that yelling at people over email is a bad idea</a>. Now that thats happened, my twitter feed is full of folks saying an apology isn't enough. Because of course, in culture B, apologies aren't enough to make things right. Its Linus's responsibility to restore the feeling of social cohesion and inclusion that his abusive tirades have eroded.</p>

<p>One person I spoke to on twitter said that Torvalds has caused damage by his rants, that he made people feel unsafe and uncomfortable in the tech space through perpetuating that culture. That makes sense from the perspective that culture B is the only non-sexist way to be.</p>

<p>In the 50s workplaces were male dominated. I'm sure culture A was king everywhere. In that world, the call that "We need workplaces that let culture B people (women) thrive" made all too much sense. But we don't live in that world any more. I'm far from convinced that a complete societal purge of culture A is possible or healthy. Amongst other things, the attempt to do so is causing a crisis amongst young boys, who are dropping out of school and university at alarming rates (Ref: <a href="https://www.amazon.com/dp/B079SGJG13/ref=dp-kindle-redirect?_encoding=UTF8&amp;btkr=1">The Boy Crisis</a>) and there's an epidemic of male suicide.</p>

<p>I also have a few female friends who feel much more at home amongst the more masculine culture A. I would paraphrase their perspective as something like this:</p>

<blockquote>
  <p>I've always felt more comfortable with male company. As an adult I have very few female friends. Growing up I had several experiences where my female friends rejected me and got really angry with me for reasons that never really seemed to make sense. The people I feel most comfortable with will rib me and give me crap, and I can do the same back to them and we have a laugh about it. I am a little creeped out by the growing push even amongst my male friends to talk about their feelings, and be sensitive. I've gotten in trouble a few times even amongst my male friends for saying the wrong thing, and I'm constantly a little anxious I'll put my foot in it and ruin some of the few friendships I have.</p>
</blockquote>

<p>And so I'm growing to think that the fierce war against culture A in the workplace is a bit misguided and wrong. I say this as someone who feels most comfortable in culture B anyway - I want everyone to feel comfortable and included. But I also respect the virtues of culture A - openness, emotional honesty, directness, taking responsibility for one's emotions and self sovereignty. And I think its important that we care about the outcome of our work, and for that to happen I need my coworkers to feel comfortable expressing disagreement. If one of my employees isn't performing, I don't want it to be a social faux pa to tell them so.</p>

<h2 id="consent">Consent</h2>

<p>The way this conflict interacts with consent culture is complicated and interesting.</p>

<p>To set the stage, by 'consent culture' I'm referring to the practice of asking for consent before doing anything intimate, at least for the first time. "May I kiss you" / "Is it ok if I do this?", etc. I'm going to establish two claims:</p>

<ol>
<li>If you don't feel safe saying "no", saying "yes" is meaningless. If I put a knife to your throat and ask if you consent to giving me your wallet, consent is coerced and the fact that I consented doesn't get you off the hook for the crime. If you personally don't feel comfortable ever saying "no thanks" when a partner asks for sex, you will eventually, inevitably have non-consensual sex.  </li>
<li>If you feel completely comfortable and safe saying no, and you have time to contemplate your decision, then verbally asking for consent is somewhat meaningless. After dinner your partner moves forward to kiss you seductively. You know they want to kiss you. You can feel the desire radiating off them to get their hands on your sweet bod. If you feel totally comfortable and safe saying "no thanks; maybe later" - then your partner doesn't need to verbalise the "may I kiss you" question thats hanging on their lips.</li>
</ol>

<p>So, then, why has there been such a strong push for verbal consent? I suspect its because relationships are one of the places where people from these two cultures meet. He's from culture A, and expects everyone to feel comfortable knowing and expressing what they want all the time. She's from culture B, where people are expected to take care of the feelings of those around us. She's been brought up to think that if anyone takes offence to what she says, she's (maybe!) done something wrong.</p>

<p>The problem that asking after consent addresses is that she <em>needs</em> to explicitly consider her own feelings. Nobody wants her to have mediocre, unenthusiastic sex that she later regrets. So we have this ritual of consent - "May I touch you here?" "Yes you may". The thing the consent asker is really trying to do here is make the other person feel comfortable saying no, while hoping they will say yes.</p>

<blockquote>
  <p>A couple years ago I had a new partner. We were dating for a couple of weeks and I went out to their place to hang out. I was excited to see them, and made a few physical bids (hugs, light touch, etc). They didn't push me away but were weirdly standoffish. I asked, and they said nothing was wrong. We went for a bush walk, and we walked in silence - during which they kept a few meters away lost in thought. Had I done something wrong?</p>
  
  <p>Eventually we talked about it - they didn't want to be physically intimate with me that day. It felt wrong to them. But it turned out they had been punished by men for saying no in past relationships. Somehow they felt like once we were 'dating', they weren't allowed to revoke consent for physical touch. We talked about it, and then practiced. I physically came on to her, and she said "no thanks" or "no fuck off" or shoved me away. It was super emotional, and honestly really good for both of us. </p>
</blockquote>

<p>I've told that story to a few times, and the interesting part is the lingering question of "Would you actually feel comfortable saying no to a partner?". I struggle with this myself! (As I said earlier, I default to the female-typical emotional caretaking / appeasement of culture B.)</p>

<p>A fun game I like to play in new relationships is to asking my new partner to practice revoking consent with me. As I see it, the point of asking for consent is to help your partner feel comfortable saying "no". If they want to refuse consent but don't feel comfortable doing so, we'll have all sorts of gross conflict. The goal is to establish the kind of masculine culture A trust here. I want you to trust that I can handle hearing 'no'. I want to trust that you will be selfish and say 'no' when you want to. If we can do that, then <em>and only then</em> can we have an actually consensual physical relationship. And as a bonus, if you can establish that sort of trust, you shouldn't need verbal "seek an enthusiastic yes" consent with that person. (Though it can still be fun.)</p>

<hr>

<p>I never know how to close long rants like this. I suppose my perspective is this:</p>

<ul>
<li>I think there's two cultures at play. I've called them A and B. We can call them masculine and feminine, competing and accomodating, bro culture and inclusive culture.</li>
<li>Each of these cultures has good parts and bad parts. The masculine culture A deals more comfortably with conflict, while culture B helps members of the group feel safe and supported.</li>
</ul>

<p>Where do you stand? What is your default? How comfortable are you with the other culture? I think the healthiest version of me is comfortable navigating successfully in both of these cultures, and I think thats a general pattern.</p>

<p>What culture do you operate under in your interpersonal relationships? I find it interesting that I almost never have heated fights with partners. I've previously thought that was something to boast about, but this year, thinking about this, I've realised that its because I am not very good at culture A style relationships. I have friends who have heated fights with their partner all the time, and for whom that seems healthy. (Research backs up that claim by the way. Speaking about arguments, <a href="https://www.gottman.com/blog/a-is-for-arguments/">Gottman says</a>: "If they don’t or can’t or won’t argue, that’s a major red flag. If you’re in a “committed” relationship and you haven’t yet had a big argument, please do that as soon as possible.")</p>

<p>Culture A teaches us that self determination and listening to one's own goals are important to all of us from time to time. Culture B teaches us that we build better communities by respecting the feelings of others. We need both of these skills sometimes, in every context. Consent is improved under the wing of culture A, but relationships in general are improved with lessons from culture B.</p>

<p>Arguing under the banner of "fighting for diversity" that culture B is the only acceptable culture is ironic and a little sad. We aren't all the same. Maybe its ok if workplaces reflect the diversity that exists in who <em>we</em> are as people. I don't want to be tyrannised by the need to be nice, from others or out of shame and guilt. Being nice out of obligation is like mandated consent - its impossible to achieve and it makes a liar out of everyone who tries. Its impossible to be authentically generous if you expect punishment for not doing so.</p>

<p>I think we need to accept and allow that some workplaces will stay in the classic masculine culture A style. And we need to negotiate an acceptable middle ground, where we accept both that others are affected by what we say, and that nobody can decide how you feel without your consent. Assessing culture fit at a new workplace should go both ways - during a job interview you should decide if the place you're considering working will be a good space for <em>you</em> to learn and grow.</p>

<p>And ultimately, if you feel more comfortable amongst emotionally accomodating communities, join organisations like the Rust community rather than the Linux kernel community. Vote with your feet. If the linux community wants to slurp up all the people you don't want to be working alongside, let them. The modern world is big enough to fit us all.</p>]]></content:encoded></item><item><title><![CDATA[A dozen ideas for a better Fortnite]]></title><description><![CDATA[<blockquote>
  <p>Edit (2020): This was written before Fortnite became the battle royale game it is today. When I wrote this, "Fortnite classic" was the whole game. Epic went a totally different direction than I was thinking about.</p>
</blockquote>

<p>I've been playing a bunch of <a href="http://fortnitegame.com">fortnite</a> over the last week. Below is a</p>]]></description><link>https://josephg.com/blog/fixing-fortnite/</link><guid isPermaLink="false">eb32f8e0-001e-4635-9017-3604096efd72</guid><dc:creator><![CDATA[Joseph Gentle]]></dc:creator><pubDate>Mon, 31 Jul 2017 04:11:30 GMT</pubDate><content:encoded><![CDATA[<blockquote>
  <p>Edit (2020): This was written before Fortnite became the battle royale game it is today. When I wrote this, "Fortnite classic" was the whole game. Epic went a totally different direction than I was thinking about.</p>
</blockquote>

<p>I've been playing a bunch of <a href="http://fortnitegame.com">fortnite</a> over the last week. Below is a bunch of things I've been thinking about while playing. This is basically a letter to the dev team that got too long for a reddit comment. This won't make any sense if you haven't played the game.</p>

<p>I've pumped about 30 hours into fortnite over the last week or so. The devs have done an absolutely amazing job at the engine but the game design itself feels like its trying to do a million things and each one needs some more design polish.</p>

<p>I want to pull apart some of how the game works because I enjoy it so much, and I can't stop thinking about this stuff while I play.</p>

<p>For me, fortnite has 4 sources of fun:</p>

<ul>
<li>Shooting zombies</li>
<li>Building bases and trap mazes</li>
<li>Scavenging for materials ("I need more nuts and bolts! Oh no my gun is breaking - need more silver!")</li>
<li>Levelling up &amp; getting more powerful</li>
</ul>

<p>But before I get into that, a more general point: <strong>the game should start much harder</strong>. I didn't fail my first mission until plankerton. Right now lots of fun mechanics can be completely ignored in the early game because you're too strong. (like mazing and teamwork). This makes stonewood boring - you are missing some of the best parts of the game. And it makes the later levels feel jarring and unfair. In comparison, in Zelda: breath of the wild most players (myself included) die from the first few mobs you see in the world - 5 minutes after the game starts. That moment frames the whole game in this sense of - "Oh! I need to actually think about how I fight" and that makes every subsequent fight more interesting.</p>

<p>With that aside lets go through the mechanics.</p>

<h3 id="shootingzombies">Shooting zombies</h3>

<p>Shooting zombies is great fun and feels good. I love the guns, the headshot mechanics and the elemental damage. The variety of enemies is great, and I love how they push you to play in different ways and move around and defend your base dynamically instead of just camping. Very well done, 5 stars.</p>

<h3 id="bases">Bases</h3>

<p>Building bases is fine, though Stormshield is really let down by how disconnected it is from the rest of the game. It feels like a minigame instead of your home base. Its awkward to get building materials into your base, and seems common to run out of nuts and bolts (and thus ammo) while doing base defence missions. Its weird that you can only defend your base 10 times, and that because of how resources work you can't do them in a row. Its also weird that you get punted out after beating the last wave. It feels like stormshield is trying to be your home base (crafting there is persistent). But its also trying to be a mission that you complete. I think the home base concept is great, and you should make it feel more like that:</p>

<ul>
<li>After you beat some enemies, you shouldn't get punted out immediately.</li>
<li>You should be able to push the button again and do the next few waves</li>
<li>There should be a better way to get ammo and crafting mats while you're in your base (more on that later).</li>
<li>Maybe you should be sent to your stormshield automatically after every mission.</li>
<li>Maybe add an endless mode after you've done the 10 missions.</li>
</ul>

<p>That would be a start. </p>

<h3 id="traps">Traps</h3>

<p>Building trap mazes is really fun, but its hard to discover that fun because of how difficult it is to learn how the zombies path. Its obvious this is a problem by looking at how people in stonewood defend objectives - most people have no idea how to make effective traps, and just end up shooting the zombies instead. This is confounded by the fact that the game is way too easy at the start - so players don't need to learn or engage with one of its most fun elements.</p>

<p>One out-of-game solution to this would be a set of mazing challenge levels (like starcraft 2) where you're dropped in with a limited pile of traps and construction resources and a very weak gun and you need to defend a fixed point against a fixed attacking force.</p>

<p>Another approach would be to draw lines in the world showing the path the zombies will take to attack your base (like Sanctum) so players can experiment with wall placement and learn how different wall configurations affect how the zombies will path. These lines could be taken away later once players have learned how to maze. (Either let players turn the lines off for extra rewards or just take them away in plankerton missions onwards.)</p>

<p>Imagine if stonewood had about 3x as many zombies in the objective waves but you could see exactly where they'll walk before you start the encounter. Building mazes would so much more fun and interesting - and you'd learn and enjoy the game's mechanics much more.</p>

<h3 id="loot">Loot</h3>

<p>Next lets talk about loot. Inventory management with crafting supplies simply isn't fun. As a player I have to make uninformed choices ("will I need these 3 stacks of ore later?") and I'm punished for deciding wrong. I think I can guess what the game designers are trying to do - to both push you into hunting down particular items ("I need more silver!") and to building different things ("I don't have enough mats to keep making guns - better use more traps instead"). Instead it feels like 90% of the materials in the game are just useless junk that clog up my inventory. I enjoy hunting down particular crafting mats, but because the XP system encourages you to invest deep in particular items there isn't much variety in what I need. And the experience is a mess ("Ugh my inventory is full again... oh god 4 stacks of planks? Ugh")</p>

<p>The first change I'd make is to remove crafting mats from the backpack. Instead make all items work like wood, stone and metal. Make them stack up to a limited number and thats it. Thats how crashlands works, and its great. The backpack can still be used for weapons and traps - but (obviously) it could be much smaller. </p>

<p>Gating weapon evolution by the materials you need to craft them is interesting - but the game should never punish you for spending XP. You should still be able to craft weaker versions of a schematic you own using the weaker materials.</p>

<p>I'm not sure what to do about the million mats thing. It feels like its just too much right now. Maybe it would be fine if there were about half as many different materials. Or maybe its just a UI thing - and if you could hold 1 stack of each material you could have a much more consistent inventory UI and it wouldn't feel like a "how do I hold all these lemons" shaped mess. I'm imagining a 1 star tab for the stonewood mats, and a 2 star tab for the plankerton mats and so on - using the same layout for each (ores, plants, etc).</p>

<p>I'd also consider removing the stormshield storage completely. Or maybe let the player hold 10 of each crafting mat in missions and autodeposit it all in stacks of 100 (or 500 for nuts and bolts) when you finish the mission. When you craft it uses what you're holding, or pulls from stormshield if you don't have enough.</p>

<p>Or not! A more wild suggestion would be to let stormshield actually be home base. Make the player plan ahead (crafting guns and traps there), before the mission starts. When the mission starts, the player's crafting materials inventory is empty. It might still make sense to let players craft weapons more in the world - but only using what you find.</p>

<p>Speaking of inventory limits, maybe the weapons and traps should have separate inventory limits. Right now the game seems like the game is trying to make me choose whether to invest in guns or traps (they use the same schematic XP and compete for inventory space). I see what you're doing there, but I disagree. I think you should push all players to do a bit of both. I would separate out the XP into weapon / schematic XP and separate out bag space into weapon space and trap space. Once crafting mats are taken out of the backpack you can imagine starting the game with space for 10 weapons &amp; traps. Maybe better still would be to start with space for 5 weapons and 5 traps.</p>

<h3 id="xp">XP</h3>

<p>Speaking of XP, right now I feel like there's a big imbalance in how progression works in the game. If you imagine the ratio of progression from quests vs progression from running around in missions, it feels like its slanted in the way of quests. I think the game would be more fun if getting XP for heroes and items was tied closer to actually using those items in game. </p>

<p>A simple fix would be to just weaken the quest rewards and raise the mission rewards. A more classic progression system here might work better.</p>

<p>But I'd consider something more wild like this: Maybe guns and traps should level up by type. Whenever a spike trap does damage, you get spike trap XP. The XP is applied automatically to all spike trap patterns now and any you find later. Want a good push trap? Easy - once enough enemies have been pushed with your push traps your trap will level up. Want a good healing trap? Make &amp; use the low level traps to unlock the better ones. That would explicitly reward you for using those traps that seem useless ("well at least I'll level it up!) and reward players more for being involved in base defence - and not just shooting stuff. It would make team dynamics harder though - it might be annoying if you built lots of traps only to have someone shoot them zombies before they reach them. But again, if there were 3x as many zombies from the start of the game I think it'd be ok.</p>

<h3 id="experimentation">Experimentation</h3>

<p>Right now the game has a problem where it punishes you for experimenting with different guns. Found a cool sniper rifle pattern? Shame it does no damage unless you invest a zillion points into it. The same system here might help - it would at least reward players for using low level weapons because you would level up those weapons. You'd probably also want to make low level weapons level up faster in later content - but maybe that would happen automatically if it was based on the damage done by the weapon (because of FORT stats). That would also incentivise the player to use weapons they found in the world - if you do a bunch of damage with that random axe you found in a chest, your axe schematics will all get stronger, and if you want to use an axe later you'll be able to craft a level 3 axe instead of a level 1 axe.</p>

<p>But this doesn't fix the bigger problem of punishing the player for experimenting with different guns. The more I think about it the more impressed I am with D3's progression system - and how easy it is to swap out skills. I'm not sure what the answer for fortnite is though. I think fortnite would lose some texture if all your weapons levelled up together.</p>

<p>A middle option might be that instead of points going into your particular assault rifle schematic XP you could put your points into assault rifles in general. Then you could happily swap out all the different rifles you own (and try new ones) without cost.</p>

<hr>

<p>Anyway, I hope thats some food for thought. The engine is fantastic and the devs should be really proud of it. I hope by launch the rest of the game can reach that fantastic level of quality.</p>]]></content:encoded></item><item><title><![CDATA[3 tribes of programming]]></title><description><![CDATA[<p>There's an old joke that computer science is a lie, because its not really about computers, and its not really a science.</p>

<p>Funny joke. Everyone laughs, then someone says "Yeah but it sort of is about computers though, isn't it?". Feet shuffle awkwardly. Someone clears their throat and before you</p>]]></description><link>https://josephg.com/blog/3-tribes/</link><guid isPermaLink="false">fb3afdf9-4258-49a6-bdb3-329f7c9c61f8</guid><dc:creator><![CDATA[Joseph Gentle]]></dc:creator><pubDate>Wed, 03 May 2017 06:51:57 GMT</pubDate><content:encoded><![CDATA[<p>There's an old joke that computer science is a lie, because its not really about computers, and its not really a science.</p>

<p>Funny joke. Everyone laughs, then someone says "Yeah but it sort of is about computers though, isn't it?". Feet shuffle awkwardly. Someone clears their throat and before you know it you're talking about Category Theory and looking up the <a href="https://en.wikipedia.org/wiki/Algorithm#Historical_background">history of the word algorithm</a>.</p>

<p>Out in the wild, these arguments look like this:</p>

<p><blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">I think I agree, and am looking forward to hearing Joe&#39;s take on it <a href="https://twitter.com/hashtag/deconstructconf?src=hash">#deconstructconf</a> <a href="https://t.co/j7H2QWG0Tr">pic.twitter.com/j7H2QWG0Tr</a></p>&mdash; Andy Lindeman (@alindeman) <a href="https://twitter.com/alindeman/status/855557506881396736">April 21, 2017</a></blockquote> <script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script></p>

<p><blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">I&#39;ll happily renounce &quot;programmer&quot; in favor of &quot;applied mathematician&quot; or something, whatever it takes to avoid C <a href="https://t.co/DsIEo5x4uI">https://t.co/DsIEo5x4uI</a></p>&mdash; Chris Martin 🐘🎺🍍 (@chris__martin) <a href="https://twitter.com/chris__martin/status/855559372927381505">April 21, 2017</a></blockquote> <script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script></p>

<p>The speaker was making the point that the whole modern stack in our computers (Kernel, OS, browser, VM) is written in C + ASM. So you should know C and ASM.</p>

<p>Is that really important? Serious question, are programs foremost lists of instructions, or expressions of logical ideas?</p>

<p><blockquote class="twitter-tweet" data-lang="en"><p lang="und" dir="ltr"><a href="https://twitter.com/hashtag/deconstructconf?src=hash">#deconstructconf</a> <a href="https://t.co/V2lGXwmaJM">pic.twitter.com/V2lGXwmaJM</a></p>&mdash; Justin Falcone (@modernserf) <a href="https://twitter.com/modernserf/status/855555296797667328">April 21, 2017</a></blockquote> <script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script></p>

<p>Or maybe its neither, and programs are just things we <em>make</em> for other <em>humans</em>. A message is fundamentally meaningless without an audience who reads it. Are programs meaningless without reference to the outside world they interact in?</p>

<p>A friend bragged to me once about how he could prove that most programs were correct and completely bug-free using Ada. I asked him if he could prove that this function was correct:</p>

<pre><code>fn sub(a, b) { return a + b }
</code></pre>

<p>He said "Of course, thats easy". So I asked how his prover would discover that the function had the wrong name, and he got delightfully flustered.</p>

<h2 id="tribes">Tribes</h2>

<p>Programs, obviously, hold all of these properties. But I think there's fundamentally 3 architypes of <em>programmers</em>, divided by which ideals we hold in highest esteem:</p>

<ul>
<li>You are a poet and a mathematician. Programming is your poetry</li>
<li>You are a hacker. You make hardware dance to your tune</li>
<li>You are a maker. You build things for people to use</li>
</ul>

<p>We self-select into communities of our peers based on these ideals. We use coded language to express these ideals to our peers.</p>

<p>I think each group has its own conferences, and its own subreddits. Its own programming languages, heroes and its own villains.</p>

<h3 id="programmingasappliedmathematics">Programming as applied mathematics</h3>

<p>The first camp views programming is fundamentally an expression of thought - a kind of mathematical poetry which we can gift with life. The fact that we execute them on von Neumann machines is an implementation detail.</p>

<p>With this mindset, these details are important:</p>

<ul>
<li><strong>Source code</strong>: The source should read like poetry - dense, with very few lines of code needed to express an idea. Once understood, the terse program seems like a beautiful and obvious description of your program. It is more important that the source code is simple than the execution is simple or fast. High level languages are better than low level languages because they let you express your intent more clearly.</li>
<li><strong>Execution</strong>: How the program is executed by the computer is an implementation detail of the compiler. It is more important that the code is simple than the execution is fast.</li>
<li><strong>Correctness</strong>: A program is correct if it implements the spec exactly. The best programs use tools like Ada to formally prove correctness.</li>
<li><strong>UI</strong>: How the code interacts with humans is a separate consideration from its implementation. Beautiful code is more important than beautiful UI.</li>
</ul>

<p>Examples: <a href="https://www.youtube.com/watch?v=f84n5oFoZBc">Rich Hickey</a>, <a href="https://vimeo.com/36579366">Brett Victor</a></p>

<p>These programmers are probably the least common, although that might be because its hard to get a job working like this. <a href="https://stackoverflow.blog/2017/02/07/what-programming-languages-weekends/?cb=1">Haskell</a> has the highest weekend/weekday usage ratio of all languages on stackoverflow.</p>

<p>Most (arguably all) of the modern advancements in programming languages come from people in this camp. If you've used React to make a website, you should know that the model of immutability and expressing your view as a pure function from data to DOM came from functional programming. Actually, most modern language features are invented by people who think of programming as thought. Years (or decades) later, those features get copied into the more popular languages and get treated as new ideas.</p>

<p>I have a friend who spent months loving <a href="http://www.jsoftware.com">J</a>. He eventually wrote a little game in J. He described his code as this perfect, beautiful crystal. Later he wanted to make it multiplayer - but to do that he would have to deal with lag. And that would require ripping apart some of the beautiful internal flow. He couldn't stomach it, so instead he abandoned the project entirely.</p>

<p>That story is <em>funny</em>, but I'm a little jealous of my friend. I bet he learned a heap and had a great time. Experiences like that make us better programmers.</p>

<p>I did a Haskell short course late last year and I challenged the main instructor. I told him "this is all well and good, but I bet I can still make useful software using my practical languages faster than you can". He said no way - using haskell he was convinced he could implement anything I could implement, faster and better and with less code. We didn't test the claim - but I still wonder - is he right?</p>

<p><strong>Favorite languages</strong>: Haskell, Lisp, ML (Ocaml, etc), Closure, ADA</p>

<p><strong>Hangouts</strong>: FP meetups, <a href="http://lambda-the-ultimate.org">Lambda the ultimate</a>, Strange Loop, <a href="https://www.data61.csiro.au">Research</a>.</p>

<p>And of course, <a href="http://steve-yegge.blogspot.com.au/2010/12/haskell-researchers-announce-discovery.html">Steve Yegge making fun of this tribe</a></p>

<h3 id="programmingashardwarehacking">Programming as hardware hacking</h3>

<p>The second camp views programming as fundamentally tied to the <em>machinery</em> of the computer. No program is run without a computer, therefore to program effectively we must keep the computer in mind at all times - hardware and software.</p>

<p>Elegance and beauty come not just from a simple code base, but by that codebase using the hardware in an elegant and efficient manner.</p>

<p>Thus, elegance like this:</p>

<ul>
<li><strong>Source code</strong>: The code should be clean, but clean code is less important than a clean execution. Low level languages are often better than high level languages because you can be more explicit about <em>what the computer will do</em> when it executes your code. (Thus you have more room to optimize).</li>
<li><strong>Execution</strong>: How the computer executes your code is paramount. Programming without thinking about execution is just begging for slow performance.</li>
<li><strong>Correctness</strong>: A program is correct if it runs the way you expect it to run, given normal parameters. Execution elegance is more important than correctness. And if a theoretical issue can't happen in practice due to how the machine works, <a href="https://twitter.com/pomeranian99/status/858856994438094848">its not a real bug</a>. A program must be adequately fast to be considered correct.</li>
<li><strong>UI</strong>: How the code interacts with humans is a separate consideration from its implementation. Its ok to let the constraints of the hardware guide the user experience.</li>
</ul>

<p>Example: <a href="http://queue.acm.org/detail.cfm?id=1814327">Poul-Henning Kamp</a>, <a href="http://www.pagetable.com">Michael Steil</a>, <a href="https://www.youtube.com/watch?v=Tfh0ytz8S0k">The 8-Bit guy</a></p>

<p>The key here is thinking about the entirety of the computer and your running program, together. According to this community, the best (only?) way to write good software is to think holistically about how it will run, and how our program will interact with the rest of the hardware and software. Doing that well achieves <a href="https://groups.google.com/forum/#!forum/mechanical-sympathy">mechanical sympathy</a> and everything runs like a well oiled clock. The joy is like that of driving a manual car that you can <em>hear</em> and <em>understand</em>.</p>

<p>Anything that obfuscates how the computer will execute your program is dangerous for the implementor - because it adds more variables to consider. Thus, people in this camp often deride garbage collectors, or the churn from JS performance benchmark results changing how we should write our code. Undefined behaviour in C compilers is an <a href="https://github.com/sandstorm-io/capnproto/blob/master/security-advisories/2017-04-17-0-apple-clang-elides-bounds-check.md">ongoing point of contention</a>.</p>

<p>In modern app development our computers are fast enough that this kind of thinking isn't really important any more. A few decades ago you needed a deep understanding of how the computer works to write software. But now basically any language you use is fast enough, so why bother learning C? Most web developers I know don't know any C, and have no interest in learning about pointers or manual memory management.</p>

<p>But this sort of work is still hugely valuable in lots of areas. The game development community still writes most code in C++ (though unity is slowly changing that). Security engineers need a systematic understanding to find vulnerabilities. Embedded systems engineers can't afford to waste cycles and RAM, and once backend systems get big enough performance starts mattering again.</p>

<p>And even when its not practical, but being forced to think about the machine can be really <em>fun</em>! For example, the <a href="https://www.lexaloffle.com/pico-8.php">PICO-8</a> imposes arbitrary 'hardware' limits to force you to be clever when designing your games.</p>

<p>To this community we owe almost all performance improvements in our computers, above and beyond what is demanded by customers. Nobody else cares about performance quite like people who think about the hardware all day. But if you're thinking about your computer as a machine, what greater ugliness can you inflict than pointless work?</p>

<p>I'm really curious if Rust will take off amongst this community. Rust is essentially a language designed <em>by</em> language nerds in the first camp above, <em>for</em> people who care about runtime efficiency. Will they take to it? Will future game engines be ported to rust?</p>

<p>Conflicts with the first group:</p>

<ul>
<li>Mutability (memory is fundamentally mutable / but it makes our programs harder to understand)</li>
<li>GC (it makes your program slow and janky / but less buggy!)</li>
<li>Abstraction (you're making your program harder / easier to reason about)</li>
</ul>

<p><strong>Fav languages</strong>: C, C++, Assembly.</p>

<p><strong>Hangouts</strong>: Hackerspaces, Game dev shops, database companies, CCC, Defcon.</p>

<p>And here's Brett Victor <a href="https://vimeo.com/71278954">making fun of this tribe</a>.</p>

<h3 id="programmingasatooltomakethings">Programming as a tool to make things</h3>

<p>The final group see programming as a means to a beautiful end, not something made beautiful through its construction. The way people in this camp describe themselves is fundamentally pragmatic. They write software because software is useful to other people.</p>

<ul>
<li><strong>Source code</strong>: The code should be clean, but only because cleaner code is easier to iterate on. Code cleanliness is less important than most other considerations.</li>
<li><strong>Execution</strong>: The program only has to be fast enough for the users. If you make it even faster, you're taking time away from adding features that people care about more.</li>
<li><strong>Correctness</strong>: Bugs are bad only in proportion to their impact. The program should act the way the users expect it to act.</li>
<li><strong>UI</strong>: The UI is more important than anything else. Every other part of the program only exists in service to the user interface.</li>
</ul>

<p>I think most professional software engineers are in this tribe - which makes sense, because this is the place where it is easiest to make money writing software.</p>

<p>In my experience people in this camp are better at community. They seem to be much more positive and encouraging of new members, and willing to help. I guess its because you can tell if you're doing a good job in the other two camps by simply taking a look yourself. If you make software for other humans, satisfaction comes from making the people around you happy.</p>

<p>I can't help but feel that this place is a touch soulless. Taken to the extreme, this world view doesn't value the beauty in the engineering itself. Although you could probably make the opposite criticism against the other groups - they don't value how their software can impact the world.</p>

<p>There's a lot of tension between this camp and the other two camps I've talked about. And it can get a bit mean. I know many product people who feel self conscious about their lack of knowledge of traditional data structures and algorithms. They feel judged by "real" programmers because they can't implement obscure algorithms and binary framing protocols. The way people in this tribe see it, other people have already implemented all that stuff anyway. So who cares?</p>

<p>Thats fair, but its also true that lots of issues are caused by the lack of technical skill amongst frontend engineers. This is mostly self correcting - if your program is too slow, you know about it and can fix it. But security engineering is a real danger. If you don't know how to secure the software you write against hackers, <a href="https://www.troyhunt.com/reckon-youve-seen-some-stupid-security-things-here-hold-my-beer/?utm_source=hackernewsletter&amp;utm_medium=email&amp;utm_term=fav">its probably not secure</a>. And you might not know its a problem <em>even if you get hacked</em>.</p>

<p>Here's an example of this conflict playing out on twitter:</p>

<p><blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr"><a href="https://twitter.com/jdan">@jdan</a> Well, then you&#39;re not a very good programmer. Sorry but that&#39;s how it is.</p>&mdash; Jonathan Blow (@Jonathan_Blow) <a href="https://twitter.com/Jonathan_Blow/status/609156243370975232">June 12, 2015</a></blockquote> <script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script></p>

<p>For context, Jonathan Blow (famous indie game developer) is saying that if you can't reverse a binary tree you're not a good developer, even if you write useful software every day.</p>

<p>Is he right? Well it depends on what 'good developer' means, and that depends on which tribes you care about. I think Blow <a href="https://www.youtube.com/watch?v=gWv_vUgbmug">is in camp 2</a>, so you're judged based on how much you know. @jdan is in camp 3, so he's judged based on what he's made. Jonathan Blow certainly writes useful software, but one of the reasons his last game (The Witness) took so long to write was that he wrote his own engine instead of using something off the shelf. When <a href="http://the-witness.net/news/2011/06/how-to-program-independent-games/comment-page-2/#comment-3655">asked about it</a> (emphasis mine):</p>

<blockquote>
  <p>I don’t know very much about Unity. However, it’s clear that one could not build The Witness in Unity without rewriting a lot of Unity (or adding a lot of things that are not there, and declining to use most of what Unity provides). And we already had a simple graphics engine anyway. So <strong>when building our own systems, we can ensure that they are really what the game needs to be its best</strong>.</p>
</blockquote>

<p>I suspect he's wrong about the first part. But I'm mostly in camp 2 myself, so I understand wanting to write your own engine anyway. I probably would have done the same thing.</p>

<hr>

<p><strong>Fav languages</strong>: Whatever gets the job done. JS, Ruby, Python, Swift, C#.</p>

<p><strong>Hangouts</strong>: Twitter, SydJS, StackOverflow, A Company Near you!</p>

<p>And of course, Gary Bernhardt <a href="https://www.destroyallsoftware.com/talks/the-birth-and-death-of-javascript">making fun of this camp</a>.</p>

<h1 id="aquietwar">A quiet war</h1>

<p>I think a lot of the conflicts and disagreements in our community can be expressed in these terms. And a lot of the misunderstandings between programmers.</p>

<p>For example, what should your programming language do when an integer overflows? If you think of programming like mathematical poetry, above all else it should give you the mathematically correct result. </p>

<p>Haskell (first camp):</p>

<pre><code>λ: 23^23
20880467999847912034355032910567 :: Num a =&gt; a
</code></pre>

<p>Vs C (second camp):</p>

<pre><code>printf("%llu\n", 1 &lt;&lt; 100); // overflows. Prints 0.
</code></pre>

<p>And if you just want to ship a product, you don't care. In javascript (third camp), there is no integer type at all. JS just uses floats for everything. And if they overflow, tough luck.</p>

<p>Rust is trying to put one foot in each of the first two camps - be a language made by programming language nerds but which compiles to efficient code. And unsurprisingly, this problem generated a long argument in the rust community. The final solution <a href="http://huonw.github.io/blog/2016/04/myths-and-legends-about-integer-overflow-in-rust/">was this</a>, where by default overflows cause panics to be thrown in debug mode, but silently work in production mode.</p>

<hr>

<p>Rob Pike (author of Go) was confused about which tribe his language is trying to appeal to. He <a href="http://commandcenter.blogspot.com.au/2012/06/less-is-exponentially-more.html">wrote this</a> a couple years after Go was released:</p>

<blockquote>
  <p>I was asked a few weeks ago, "What was the biggest surprise you encountered rolling out Go?" I knew the answer instantly: Although we expected C++ programmers to see Go as an alternative, instead most Go programmers come from languages like Python and Ruby. Very few come from C++.</p>
</blockquote>

<p>Why? Well C++ programmers are largely in camp 2 above. They want to know how their code will run. But Go has a garbage collector, and a fast compiler. Really, Go cares about getting out of your way so you can just make stuff. Its a language for people in the last camp, who want to build products. What languages do people who care about that currently use? Python, Ruby and Javascript. So of course they're who Go is converting.</p>

<h1 id="closing">Closing</h1>

<p>Next time you see an argument about whether Javascript is a cancer or a boon to our industry, or you see someone like me getting angry about <a href="https://josephg.com/blog/electron-is-flash-for-the-desktop/">modern apps being crap</a>, ask yourself which camp is speaking. Are they championing beautiful code? Performance and a "deep understanding"? Or do they just want to get work done and ship product?</p>

<p>Ultimately code is code. Even though we have different reasons for writing software, what we write is (usually) compatible. And even when its not (looking at you, Haskell) - there's always a lot of ideas we can learn from and steal.</p>

<p>We all owe each other a lot, after all. Without language wonks we would still be writing assembly. Without systems programmers we wouldn't have operating systems, and haskell and javascript would be unusably slow. And without product engineers, everyone else would be forced to write CSS. And trust me, nobody wants that.</p>

<p>Rear Admiral Grace Hopper managed to bridge machine understanding and product thinking, and in doing so invented the idea of a machine-independant computer language. Without being able to think both about what the computer can do and what we <em>want</em> the computer to do, that wouldn't have been possible.</p>

<p>But personally I think we should aspire to be like <a href="https://www.youtube.com/watch?v=YyIQKBzIuBY">Alan Kay</a> and do all three. Him and his team regularly crosses multiple tribal lines. As an example, he invented object-oriented programming from watching children learn Squeak and Logo. He thinks there's ways we can have our cake and eat it too - using modern techniques to engineer much simpler systems that are faster, more elegant and more useful for humans. If you haven't done it already, you should watch every talk he's ever given. Do it <em>slowly</em>.</p>

<p>Thats certainly what I aim for. And hopefully I'll still be blowing people's minds past the age of 70.</p>]]></content:encoded></item><item><title><![CDATA[Cracks]]></title><description><![CDATA[<p>Programming is close to godliness <br>
when you're deep in a problem, code is thought made real. <br>
In that space you stop existing as a person. <br>
You're just a conduit for creation.</p>

<p>Squint at the right moment and you can catch them <br>
those sparkling cracks in reality at the corners of</p>]]></description><link>https://josephg.com/blog/cracks/</link><guid isPermaLink="false">af0395f3-d2a1-4d37-b620-91c6b6dd967e</guid><dc:creator><![CDATA[Joseph Gentle]]></dc:creator><pubDate>Tue, 18 Apr 2017 01:32:17 GMT</pubDate><content:encoded><![CDATA[<p>Programming is close to godliness <br>
when you're deep in a problem, code is thought made real. <br>
In that space you stop existing as a person. <br>
You're just a conduit for creation.</p>

<p>Squint at the right moment and you can catch them <br>
those sparkling cracks in reality at the corners of your eyes. <br>
go ahead - look down <br>
and for a moment all of creation is laid bare before you. <br>
The path of time. People and systems and machines, <br>
churning and changing through deliberate little choices. <br>
through the strike of keys.</p>

<p>And in that moment bare witness to the interlocking malleability of it all. <br>
Of <em>us</em> all, for we are made of atoms too, are we not? <br>
Click, clack.</p>

<p>Tasks, tickets and tests are ritual <br>
as we prepare for the divine <br>
for that moment when understanding is perfect. <br>
And the machine becomes all things. Whole and divided. Voices and a symphony. A single vision and a million moving pieces.</p>

<p>And in that moment,</p>

<p>with a keystroke, <br>
god and I. <br>
We unmake the world.</p>

<hr>

<p>(I'm not religious. Just recovering from my 2+ day programming bender.)</p>]]></content:encoded></item><item><title><![CDATA[Building a r/place in a weekend]]></title><description><![CDATA[<p>On Friday I accepted a <a href="https://news.ycombinator.com/item?id=14111143">challenge</a> to clone Reddit's <a href="http://reddit.com/r/place">/r/place</a> in a weekend. And I did it, and <a href="https://josephg.com/sp/">its live</a>, and its amazing:</p>

<p><img src="https://josephg.com/blog/content/images/2017/04/final.png" alt=""></p>

<p>Being able to build this in a weekend isn't genius. Its possible because programming is made up of 2 activities:</p>

<ul>
<li>Making decisions (95%)</li>
<li>Typing (5%)</li>
</ul>

<p>Reddit</p>]]></description><link>https://josephg.com/blog/rplace-in-a-weekend/</link><guid isPermaLink="false">12a53a22-2832-4592-9f49-0df7203320be</guid><dc:creator><![CDATA[Joseph Gentle]]></dc:creator><pubDate>Sun, 16 Apr 2017 08:11:11 GMT</pubDate><content:encoded><![CDATA[<p>On Friday I accepted a <a href="https://news.ycombinator.com/item?id=14111143">challenge</a> to clone Reddit's <a href="http://reddit.com/r/place">/r/place</a> in a weekend. And I did it, and <a href="https://josephg.com/sp/">its live</a>, and its amazing:</p>

<p><img src="https://josephg.com/blog/content/images/2017/04/final.png" alt=""></p>

<p>Being able to build this in a weekend isn't genius. Its possible because programming is made up of 2 activities:</p>

<ul>
<li>Making decisions (95%)</li>
<li>Typing (5%)</li>
</ul>

<p>Reddit wrote up a wonderful blog post about <a href="https://redditblog.com/2017/04/13/how-we-built-rplace/">how they made the original</a>, so lots of the decisions were already made for me. How much load I need to handle, how big to make it, the palette and some of the UI I'm using directly. I didn't copy reddit's architecture though, simply because I don't agree with some of their technical decisions. But the places in which I disagree are all based on decades of my own programming experience, so I still don't have a lot of decisions left to make.</p>

<p>To be clear, if I was building this for reddit a weekend wouldn't be enough time. <a href="https://github.com/josephg/sephsplace">The code</a> is a mess. There's no monitoring and logging. No CI, no tests. There's no access control and no mobile support. I also haven't load tested it as thoroughly as reddit would need to. A weekend is not enough time to make it production ready for a site like reddit.</p>

<p>But thats ok - it sure works! And some quick benchmarking shows it should scale past reddit's 100k users mark.</p>

<p><img src="https://josephg.com/blog/content/images/2017/04/gow.png" alt=""></p>

<h1 id="howitworks">How it works</h1>

<p>I had a think about how I wanted data to flow through the app before I even accepted the challenge. It was important to know so I could figure out if I could actually build it in time.</p>

<p>My favorite architecture for this sort of thing is to use event sourcing and make data flow one way though the site. If you've ever used Redux with react you'll appreciate how simple this makes everything - every part of the system just has 2 responsibilities:</p>

<ul>
<li>Where do I get data from?</li>
<li>Where do I send the data?</li>
</ul>

<p>So, for sephsplace edits start in the browser, hit a server, go to kafka, get read from kafka by a server then get sent to users. </p>

<p><img src="https://josephg.com/blog/content/images/2017/04/arch1.png" alt="architecture for edits"></p>

<p>Which is simple enough. The edits themselves are globally ordered by kafka, so if two edits to the same location happen at the same time, everyone will see the same final result based on the order they come <em>back out</em> of kafka. You could make a fancier event log which load balanced edits across multiple kafka partitions, but reddit's spec says it only needs to handle 333 edits per second. A single kafka partition should be able to manage 10-100x that much load.</p>

<p>To ensure consistency I attach a version number to each edit coming out of the kafka event stream. So, the first edit was edit 0, then edit 1 and so on. At the time of writing we're up to edit 333721. (Check <code>window._version</code> on the site if you're curious.)</p>

<h2 id="subscribefromversion">Subscribe from version</h2>

<p>The genius of this system is that if you have an image that is out of date, so long as you know the version we can catch you up by just sending some edits.</p>

<p>For example, if you get disconnected while at version 1000, when you reconnect you just have to download all the operations from version 1000 to 'now'.</p>

<p><img src="https://josephg.com/blog/content/images/2017/04/operations.png" alt=""></p>

<h2 id="snapshots">Snapshots</h2>

<p>The client needs to load the page quickly without downloading the entire history of operations. To do that I made a <a href="https://josephg.com/sp/current">REST endpoint</a> which simply returns a PNG of the current page. To make this work the server just stores a copy of the page in a 1000x1000 array in memory. The array gets updated when any edits come through kafka.</p>

<p>But rendering that image out to PNG is slow. I hooked up <a href="https://www.npmjs.com/package/pngjs">pngjs</a> to render out the image, and it takes about 300ms of time per render. A single CPU can only render the page 3 times per second. This is way too slow to keep up.</p>

<p>Its probably possible to optimize that. Reddit apparently spent a lot of time bit packing their image in redis, but that sounds like a waste of time to me. I just configured nginx to fetch the image at most once every 10 seconds. All the time in between that nginx will just return a stale image.</p>

<p>But the client can catch up! The cached image has the version number embedded in a header, so when the client connects to the realtime feed it can immediately get a copy of the edits which have happened since the image was generated.</p>

<p>To make server restarts fast, every 1000 edits or so I store a copy on disk of the current snapshot &amp; what version its at. When the server starts up it works just like the client - it grabs that snapshot from disk, updates it with any recent operations from kafka and its good to go.</p>

<p><img src="https://josephg.com/blog/content/images/2017/04/arch2.png" alt=""></p>

<p>The data flow ends up looking something like this, although the server keeps a recent snapshot in memory <em>and</em> on disk. But again, data only flows one way so each part is easy to reason about in isolation.</p>

<h2 id="writingtheserver">Writing the server</h2>

<p>At about 2pm on Friday I knew what I was building and I started to work.</p>

<p>I'd never actually used <a href="https://kafka.apache.org/">kafka</a> before, and I have a rule with projects like this that any new technology has to be put in first just in case there are unknown unknowns that affect the design. But Kafka was dreamy to work with, so by 4pm I had the kafka events working and the server was rendering images out:</p>

<p><blockquote class="twitter-video" data-lang="en"><p lang="en" dir="ltr">Got simple streaming edits working via kafka. Streaming update API next. <a href="https://twitter.com/hashtag/placethrowdown?src=hash">#placethrowdown</a> <a href="https://t.co/EuEXdZIVYI">pic.twitter.com/EuEXdZIVYI</a></p>&mdash; Seph (@josephgentle) <a href="https://twitter.com/josephgentle/status/852774081015435266">April 14, 2017</a></blockquote> <script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script></p>

<p>In the video each edit sets the top left 100 pixels of the image to a random color. I'm triggering the edits via cURL (no browser code yet). The edits are published to kafka. The server gets sent the events <em>from</em> kafka then updates the image. When I refresh the page (actually its just an image URL), the server returns an updated PNG with the new content.</p>

<p>By 6pm I had an endpoint allowing the client to subscribe to a live stream of edits:</p>

<p><blockquote class="twitter-video" data-lang="en"><p lang="en" dir="ltr">SSE working for live updates, with resume &amp; replay. Not sure how to load balance SSE. Client image updating next <a href="https://twitter.com/hashtag/placethrowdown?src=hash">#placethrowdown</a> <a href="https://t.co/lu0e7WHdEb">pic.twitter.com/lu0e7WHdEb</a></p>&mdash; Seph (@josephgentle) <a href="https://twitter.com/josephgentle/status/852790675271589888">April 14, 2017</a></blockquote> <script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script></p>

<p>(I used <a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events">server-sent events</a> instead of websockets at first because they're simpler and more elegant than websockets and they work over HTTP2. But SSE doesn't support binary data, and <a href="http://caniuse.com/#feat=eventsource">you need a polyfill for IE</a> so I moved away from them later. More about that later.)</p>

<p>Anyway, with all that done the server was basically complete. I added a few caching headers for the snapshots, put it behind nginx with a strict caching policy and moved to the client.</p>

<h2 id="makingapixeleditorinabrowser">Making a pixel editor in a browser</h2>

<p>Luckily for me, I've already written a few browser games with scrollable and pannable pixel-like editors. So for the editor I blatantly stole a bunch of code from <a href="https://steam.dance/josephg/elevator">steam dance</a>.</p>

<p>The client works using 2 canvases:</p>

<ul>
<li>One invisible canvas for the image itself. This canvas is basically just an image with the whole drawing space in it. This is really simple and efficient because the image has a known, fixed size (1000px x 1000px) so it can fit comfortably in GPU memory.</li>
<li>The second canvas is the drawable area you see on the page itself. This canvas is in the DOM, and it gets resized when you resize your browser.</li>
</ul>

<p>Most of the time reddit uses CSS to render their r/place page, and then falls back to using canvases in some browsers. But I don't like needing multiple renderers if I can avoid it.</p>

<p>My draw function was just this:</p>

<p><img src="https://josephg.com/blog/content/images/2017/04/draw.png" alt=""></p>

<p>If you haven't seen it before, that wrapper for <code>requestAnimationFrame</code> improves the code in two ways:</p>

<ul>
<li>If the tab is in the background, it won't draw at all</li>
<li>It lets me call <code>draw()</code> with impunity in my code any time I need something redrawn. The page will only be redrawn once no matter how many calls to draw I make before rendering.</li>
</ul>

<p>For a game with animations you usually just render at 60fps regardless, but I want sephsplace to be able to sit in the background without <a href="https://josephg.com/blog/electron-is-flash-for-the-desktop/">using CPU unnecessarily while idle</a>.</p>

<p>For panning and zooming I <a href="https://github.com/josephg/sephsplace/blob/291c232b7d9b33666c5cd32ee8a9f72f52af2314/public/index.js#L48-L183">stole ~150 lines of code from past projects</a>.</p>

<p>Once that was done I added the palette swatches and some UI. By 9:30pm I had a working client:</p>

<p><blockquote class="twitter-video" data-lang="en"><p lang="en" dir="ltr">That asymmetric border radius tho :D. Added pan tool and color selection swatches. Next caching then I want to put it online <a href="https://twitter.com/hashtag/placethrowdown?src=hash">#placethrowdown</a> <a href="https://t.co/1fpEbVRkoa">pic.twitter.com/1fpEbVRkoa</a></p>&mdash; Seph (@josephgentle) <a href="https://twitter.com/josephgentle/status/852850616120483840">April 14, 2017</a></blockquote> <script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script></p>

<p>Then I spent hours fighting with java, zookeeper, systemd scripts for kafka, nginx and pm2.</p>

<p><strong>At 2am, 12 hours after starting, I put the site live.</strong></p>

<p>I didn't manage to sleep until 4am though.</p>

<p><img src="https://josephg.com/blog/content/images/2017/04/mandalla.png" alt=""></p>

<h2 id="day2optimizationandpolish">Day 2 - Optimization and polish</h2>

<p>A good rule of thumb is that if you want to spend any time polishing you'll need twice as much time. So I was very pleased that I had a whole day for tweaks and optimizations.</p>

<p>For this project I was aiming to be able to support reddit's numbers - 100k concurrent users and 333 edits per second. Making numbers go up is really fun, so I always like to measure before optimizing so I can really see the performance metrics shoot up.</p>

<p><blockquote class="twitter-video" data-lang="en"><p lang="en" dir="ltr">Initial testing at 400 edits/second. Its 10x slower than I want but pretty. The real problem will be adding 100k readers tho <a href="https://twitter.com/hashtag/placethrowdown?src=hash">#placethrowdown</a> <a href="https://t.co/nQDUDBVJ73">pic.twitter.com/nQDUDBVJ73</a></p>&mdash; Seph (@josephgentle) <a href="https://twitter.com/josephgentle/status/853092182579855362">April 15, 2017</a></blockquote> <script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script></p>

<p>The initial benchmarks were pretty depressing. In the video I have a script running which is sending 400 edits / second into kafka. Every part of this system is slower than I want it to be:</p>

<ul>
<li>Chrome is using an entire core of CPU just rendering the animation</li>
<li>My server is using about 34% of a core simply receiving operations from kafka and sending them out again</li>
<li>Even kafka is embarrassingly slow here - using 20% CPU to process those 400 ops/second. I expect better from you kafka!</li>
</ul>

<p>400/second is a really small number for modern computers. The reason we're using so much CPU here is bookkeeping:</p>

<ul>
<li>Kafka processes each edit individually</li>
<li>Kafka sends 1 network packet per edit (I think)</li>
<li>My server decodes each edit individually using msgpack, into a separate javascript object</li>
<li>My server then re-encodes each edit to a JSON string to send to the browser</li>
<li>The client processes each edit individually. For each edit it needs to talk to the GPU to upload that one lonely pixel.</li>
</ul>

<p>Whew - I'm tired just thinking about it. This is a staggering amount of work for the computer.</p>

<p>To fix this I made 3 changes:</p>

<p>First, we don't need to do this work per-edit! Its much better to batch up all edits into ~10th of a second blocks and process them together. That way we only need to pay for bookkeeping 10 times a second no matter how many messages we have.</p>

<p>Secondly I moved everything to binary messages.</p>

<p>The binary encoding for this is beautiful - each edit fits perfectly into 3 bytes. Look at the math - An edit is a (x, y, color) triple. The x and y coordinates are integers from 0-999 - which is almost perfectly represented as a 10 bit integer (10 bits = 1024 different values). And 16 colors fit exactly in 4 bits.</p>

<p>So we have <code>x</code> (10 bits) <code>+ y</code> (10 bits) <code>+ color</code> (4 bits) <code>= 24 bits = 3 bytes</code>. Perfect.</p>

<p><img src="https://josephg.com/blog/content/images/2017/04/bits.png" alt=""></p>

<p>Now I can batch hundreds of edits efficiently in a byte array! This is great because byte arrays are blazing fast. They're much faster because they're easy to optimize for in both hardware and software, they're GC-efficient (compared to JS lists) and they're cheap to access from C code (like, say, the nodejs networking stack).</p>

<p>Also writing bitwise code always makes me feel like I'm in hackers 💕💕</p>

<p><img src="https://josephg.com/blog/content/images/2017/04/hackers.jpg" alt=""></p>

<p>The third change was a move from server-sent events to websockets. I needed to do this because SSE doesn't support binary messages. To send the edits over SSE I'd need to encode them into base64 strings, which would be slow and increase the size of the messages. Websockets support sending binary messages directly, so its easier to just use that.</p>

<hr>

<p>With that done the same 400 edits per second it looked like this:</p>

<p><blockquote class="twitter-video" data-lang="en"><p lang="en" dir="ltr">Same 400 edits/second, but now coalesced every 200ms in batches on write. Less pretty, but the CPU difference is staggering. <a href="https://twitter.com/hashtag/placethrowdown?src=hash">#placethrowdown</a> <a href="https://t.co/Ncrti7wj92">pic.twitter.com/Ncrti7wj92</a></p>&mdash; Seph (@josephgentle) <a href="https://twitter.com/josephgentle/status/853122138961625088">April 15, 2017</a></blockquote> <script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script></p>

<p>Notice:</p>

<ul>
<li>Chrome is down to 10% CPU (from 100%)</li>
<li>The <code>node</code> process (the server) is using 2.5% CPU (down from 35%)</li>
<li>Kafka isn't listed, because it was only using about 1% of my CPU to handle the 5 larger messages every second.</li>
</ul>

<p>I threw in some more minor optimizations after this video as well - adding more batching, tweaking <code>ws</code> parameters and stuff like that. I love optimizing code - it feels so cleansing.</p>

<p><img src="https://josephg.com/blog/content/images/2017/04/lady.png" alt=""></p>

<h3 id="arewefastenoughyet">Are we fast enough yet?</h3>

<p>Where are the actual performance bottlenecks here? What needs to be fast?</p>

<p>It turns out the big scaling challenge here is actually getting data from kafka to the clients.</p>

<p>Why? Well, because even in the naive version of my code I could handle the required 333 writes per second easily. Thats a tiny number. But remember we need to support 100k active clients. So if the server gets 333 edits per second, it needs to send 33.3 million edits per second.</p>

<p>On paper, 333 writes * 3 bytes = 1k of data. Sending 1k of data per second to 100k clients is 100MB/s of traffic. Which is large but manageable. A well optimized C program should be able to send 100MB of network traffic per second no sweat.</p>

<p>What I really want is something like nginx, but designed for event logs. It should be written in C (node won't be fast enough). The closest I found was <a href="https://github.com/wandenberg/nginx-push-stream-module">nginx-push-stream</a> - which looks perfect. Its designed for exactly this use case. But I don't like it because it doesn't guarantee message order or delivery. Remember, we need a consistent message order so everyone sees the same result when two people edit the same pixel at the same time.</p>

<p>Effectively, nginx-push-stream is UDP and I want TCP. It'd definitely be good enough for this project, but I don't want to have to write the code to replay and reorder messages. And to use it I'd need a worker process which simply tails the kafka log and forwards it into nginx. And we'd need need special catchup-on-reconnect logic, because the stream it sends out wouldn't support subscribing from a specified version number.</p>

<p>Another approach would be to send the events out using long polling. That sounds wild, but if we make a URL for each block of edits, the clients could just request the next block of edits directly from nginx. Nginx can be configured to hold all 100k requests from clients and just send 1 request to the backend for the data. The server then holds the request until the data is available (ie, 1 second has passed). If we get nginx to cache the edits, it'll support catchup just fine.</p>

<p>Its just... sad doing it that way. Long polling is so... 2005. And its a pretty weird way to use of nginx.</p>

<p>In this case I'm lucky to say I didn't need to do any of that. It turns out my binary message handling + <a href="https://josephg.com/blog/rplace-in-a-weekend/npmjs.com/package/ws">ws</a> is fast enough anyway:</p>

<p><img src="https://josephg.com/blog/content/images/2017/04/perfvictory-1.jpg" alt=""></p>

<p>My laptop manages 10k clients using only about 34% of one CPU core. So 100k clients should take about 4 modern CPU cores. This is several times faster than I expected it would be. I'm grateful for the performance optimizations that have gone into the ws websocket library, nodejs and v8 over the last few years. Horizontal scaling like this will put extra load on kafka, but kafka can handle a few more orders of magnitude of load before we need to worry about it.</p>

<p>(The node processes you see in the screenshot are the websocket testing library <a href="https://www.npmjs.com/package/thor">thor</a>. I had to modify it a little to work for me though.)</p>

<p><img src="https://josephg.com/blog/content/images/2017/04/batman.png" alt=""></p>

<p>So that was that. At 5:30pm on day 2 I declared the challenge complete.</p>

<p>If I was feeling virtuous or better rested I would have rented a few AWS machines and set up a full cluster. But the thought of spending hours setting up zookeeper again was enough to convince me to declare victory and play some computer games instead.</p>

<p>It'll be fine. We can fix it live if we need to, right?</p>

<p><img src="https://josephg.com/blog/content/images/2017/04/17861712_10155313973183083_8810187488691317415_n.jpg" alt=""></p>

<h1 id="finalthoughts">Final thoughts</h1>

<p>That was a wild ride. I haven't gotten much sleep, and I spent altogether too much time deleting nazi symbols and penises. And <a href="https://twitter.com/josephgentle/status/853312152223965184">fighting botnets used to draw giant pictures of Angela Merkel</a>.</p>

<p>I think if I make something like this again I'd like to live stream the whole thing. One of the most enjoyable parts of the process was going online and seeing what people have drawn. This sort of project is a real community thing, and I'd like to involve the community more in the future.</p>

<p>As for the site - I don't know what to do with it. I'll leave it up, but I'm worried people will start drawing child porn or something if I don't keep an eye on it.</p>

<p>While working on this I feel like the most interesting design question is the policy on rate limiting:</p>

<ul>
<li>Bots are cool, but way more powerful than humans</li>
<li>If you limit edits to once per 5 minutes, will I have enough community to keep it going?</li>
</ul>

<p>Maybe I could make a version where every user has an energy bar. Then different areas of the space are either more or less volatile - so you can draw on the volatile sections for free, but its easy for others to draw over the top. If you want your art to stay for a long time you can draw in the slow regions - it'll just take ages to draw in the first place. Or maybe the space should start small with just one small tile, and the community can slowly add tiles. Each tile can only be edited for 24 hours then it gets locked down forever, forming a slowly growing mosaic. I'm sure there's lots of cool variants.</p>

<p>Thankyou reddit for making r/place and inspiring me. And thanks to everyone who's drawn awesome stuff on the page, and <a href="https://twitter.com/josephgentle">followed along on twitter</a>. Its been fun!</p>

<p>Ugh, I feel weak.. did I forget to eat again? ... Oops.</p>

<p><img src="https://josephg.com/blog/content/images/2017/04/pushpin.png" alt=""></p>]]></content:encoded></item><item><title><![CDATA[The modern web makes me want to throw up]]></title><description><![CDATA[<blockquote>
  <p>I've written a fair bit over the last few months on other mediums (FB and Hackernews). I'm going to start collecting some of that content and reposting it here.</p>
</blockquote>

<p>From <a href="https://news.ycombinator.com/item?id=12941979#12942372">here</a>:</p>

<p>Performance of modern web apps is simply awful compared to their native counterparts by any measure. They load slowly</p>]]></description><link>https://josephg.com/blog/the-modern-web/</link><guid isPermaLink="false">06bca7f8-4340-4e93-97ef-f5eaf06a990f</guid><dc:creator><![CDATA[Joseph Gentle]]></dc:creator><pubDate>Sun, 13 Nov 2016 08:30:46 GMT</pubDate><content:encoded><![CDATA[<blockquote>
  <p>I've written a fair bit over the last few months on other mediums (FB and Hackernews). I'm going to start collecting some of that content and reposting it here.</p>
</blockquote>

<p>From <a href="https://news.ycombinator.com/item?id=12941979#12942372">here</a>:</p>

<p>Performance of modern web apps is simply awful compared to their native counterparts by any measure. They load slowly and consistently feel sluggish in comparison to proper native apps. Slack takes seconds to load and it'll happily sit on over a gig of ram while in use. (And remember, its a glorified IRC client.)</p>

<p>Web apps only have two benefits over native apps:</p>

<ol>
<li>They're easier to build in a cross-platform way, because you just have to make the app once  </li>
<li>Users don't need to install anything.</li>
</ol>

<p>I hope in the long run we solve both of those problems for native apps and move back to writing applications which don't depend on a DOM implementation to run. React-native is a great step toward solving (1). I hope that in time we can have modern (reactish), good looking cross-platform toolkit for native app development.</p>

<p>But the biggest hurdle is (2), which very few people are attacking. App stores have helped a lot, but we need to be able to run native apps without installing anything. It should be as easy as it is on the web. Unfortunately native app authors usually assume all their assets are bundled. Nobody even thinks about how long it takes from the point when a user decides to install an app to when they see the first content. On the web we count that time in milliseconds and on native apps we count minutes. There aren't any hard technical problems there - we obviously manage it on the web, so its easily possible. We need native apps to catch up if they are ever going to be able to compete as a platform.</p>

<blockquote>
  <p>Why would you want to solve the problems of native apps rather than solve the problem (there is only one) of web apps, which is the performance issue.</p>
  
  <p>Customers don't install apps for the most part. Why try to solve for that?</p>
</blockquote>

<p>Why? Because building hack on top of hack is convenient, but terrible craftsmanship. The web today is a red hot mess of overlapping standards and inconsistent APIs. Google chrome is up to ~20M lines of code now, which makes it bigger than the linux kernel (with every device driver). Its basically a small virtualised operating system at this point. <br>
How many more lines of code do you think it'll take for chrome to feel fast and light again? Is it possible to achieve that by adding code, forever? And then to work around all the cruft in browsers we have frameworks like react - conceptually beautiful but which do layout by diffing thousands of DOM elements through javascript.</p>

<p>The stack is a mess, and I don't see any direction it can possibly go but fatter, bigger and uglier than ever before. Forever. Surprisingly, this is a fate native apps have avoided. And in avoiding that fate new laptops have amazing battery life and work great. Well, until you open chrome or slack that is.</p>

<p>I don't write code for customers. I do it because as a kid I fell in love with the craft. I just... didn't fall in love with programming for this. Nope nope nope I want off the train before I throw up.</p>]]></content:encoded></item><item><title><![CDATA[Electron is flash for the desktop]]></title><description><![CDATA[<p>What is slack doing?</p>

<p><img src="https://josephg.com/blog/content/images/2016/10/Screen-Shot-2016-04-05-at-8-57-24-AM.png" alt="Slack at 100% cpu usage"></p>

<p>The process was in the background when this happened. I wasn't even interacting with it - I was in a meeting. I only noticed because my laptop fans were whurring when I got back. Restarting slack seemed to fix it for now.</p>

<p>But that's not abnormal</p>]]></description><link>https://josephg.com/blog/electron-is-flash-for-the-desktop/</link><guid isPermaLink="false">af70a3ee-5e9a-490d-9b93-a9fbe23a3b49</guid><dc:creator><![CDATA[Joseph Gentle]]></dc:creator><pubDate>Sat, 29 Oct 2016 11:39:38 GMT</pubDate><content:encoded><![CDATA[<p>What is slack doing?</p>

<p><img src="https://josephg.com/blog/content/images/2016/10/Screen-Shot-2016-04-05-at-8-57-24-AM.png" alt="Slack at 100% cpu usage"></p>

<p>The process was in the background when this happened. I wasn't even interacting with it - I was in a meeting. I only noticed because my laptop fans were whurring when I got back. Restarting slack seemed to fix it for now.</p>

<p>But that's not abnormal for slack. In fact, slack often idles at 5% CPU usage. Whats it doing? I have no idea.</p>

<p>And I bet the slack team doesn't know either. How many lines of code do you think the slack team wrote to make their client work? I'd guess around 50k. Maybe 100k. But slack isn't a native app. At least - not a normal native app. Its built on top of <a href="http://electron.atom.io/">electron</a>, so when you download slack you're actually downloading a complete copy of Google Chrome. Chrome, at the time of writing is <a href="https://www.openhub.net/p/chrome">15 million non-comment lines</a>. When you download slack, 99% of the code is 'below the water'.</p>

<p>And chrome is a hog. Its huge and complicated. It uses ram and CPU like nobody's business, and it totally thrashes your battery life.</p>

<p>You can think of slack as a small javascript program running inside another operating system VM (chrome), that you have to run in order to essentially chat on IRC. Even if you've got the real chrome open, each electron app runs its own, extra copy of the whole VM.</p>

<p>And its not a stretch to call chrome an OS. By lines of code, chrome is about the same size as the <a href="https://www.linuxcounter.net/statistics/kernel">linux kernel</a>. Like the linux kernel it has APIs for all sorts of hardware, including <a href="https://www.chromeexperiments.com/webgl">opengl</a>, <a href="https://webvr.info/">VR</a>, <a href="https://www.keithmcmillen.com/blog/making-music-in-the-browser-web-midi-api/">MIDI</a>. It has an embedded copy of SQLite, memory management and its own task manager. On MacOS it even contains a userland USB driver for xbox360 controllers. (I know its there because I wrote it. Sorry.)</p>

<p>Does slack contain my code to use xbox controllers? Does the slack team know? Does <em>anyone</em> know? I mean, the slack app is 160 megs on disk. Thats about the size of 70 uncompressed copies of <em>Lord Of The Rings</em>. Who knows whats in there? The other electron apps I have on my computer are <del>Spotify</del> (<em>Edit: Not quite - see below</em>) (200 megs) and Atom (260 megs). The first time I installed linux I did it from floppy disks. It would take 450 floppy disks to store these three simple apps. Together these apps are about the size of the standard desktop ubuntu distribution. Which y'know, probably contains an IRC client, a text editor and a music player. An an entire operating system, user space and web browser. <br>
Sure! You say. Disk size is cheap you say! Sure but ram sure isn't. The brand-spanking-new macbook pros only ship with 8 GB ram by default. <a href="http://www.macrumors.com/2016/10/28/new-macbook-pros-no-32gb-ram-battery-life/">Because of battery concerns</a> you can't configure them past 16 gigs. And right now slack is sitting on somewhere between 300 megs to 1 gig of my laptop's ram:</p>

<p><img src="https://josephg.com/blog/content/images/2016/10/Screen-Shot-2016-10-29-at-9-08-59-pm.png" alt=""></p>

<p>I mean <em>come on</em>. Its a <em>text chat program</em>.</p>

<p>The other thing that isn't plentiful is battery life. The way modern CPUs conserve battery is by turning themselves off anytime they can (when nothing is scheduled). The bane of power management is programs that use a few percent of your CPU constantly. They cause your CPU to constantly wake themselves up, go to sleep and wake again. Thats the perfect way to burn that precious battery life. If anyone has time I'd love to see how much spotify, slack and atom (just left open not doing anything) decrease the battery life of a modern laptop. Because they're <em>crazy</em>.</p>

<p><img src="https://josephg.com/blog/content/images/2016/10/Screen-Shot-2016-10-29-at-8-38-32-pm.png" alt=""></p>

<p>And no - spotify isn't playing any music. Its just ... running. Doing mysterious chrome things. Its using a few percent of my CPU too, by the way. Just to exist.</p>

<p>(Vindictively while I've been writing this chrome has jumped up to 100% cpu usage, assigned to the mysterious 'browser' process in chrome's internal task manager. Thanks chrome.)</p>

<hr>

<p>To be clear, javascript on the desktop isn't the problem. In fact, I think the APIs work with in the modern web are way better than the APIs that exist on desktop. We should use them.</p>

<p>But we need ways to use those nice new paradigms (react and friends) on the desktop without running more blood copies of chrome. I just ... don't care about your app enough to justify running more chrome instances. As a developer its really easy to fall into the trap of assuming your app / website / whatever is a gift to humanity and the most important thing your users are doing. Why not use a few of their excess system resources? But we have to fight that mindset. That path lies a world where we can't have nice things. That path lies a world where our laptop batteries need to grow ever larger to support our CPUs doing even more dumb crap. That way lies the return of shockwave flash, of warm phones in our pockets which are mysteriously flat when we want to use them. Of getting paranoid about battery life and closing apps the instant we're done with them. (Looking at you, iTunes and Mischief.)</p>

<blockquote>
  <p><strong>Just Say NO to electron</strong></p>
</blockquote>

<p>Developers don't let friends write electron apps. If you want to use JS and react to make a native app, try <a href="https://facebook.github.io/react-native/">react native</a> instead. Its like electron, but you don't need to distribute a copy of chrome to all your users, and we don't need to run another copy of chrome to use your app. It turns out modern operating systems already have nice, fast UI libraries. So use them you clod!</p>

<hr>

<p>The other sad fact is that even most developers have no idea that this is even happening on their computers. They run slack but have no idea how hungry it is. As a developer its your responsibility to know this stuff. Practice seeing. Learn profiling tools. Get <a href="https://bjango.com/mac/istatmenus/">iStatMeters</a> or one of the free equivalents. You can't improve what you don't measure.</p>

<p>Maybe we should be buying slower computers so we feel the pain. Facebook has been internally <a href="http://www.theverge.com/2015/10/28/9625062/facebook-2g-tuesdays-slow-internet-developing-world">intentionally slowing down their office internet</a> once a week to help build empathy with their users in other 3rd world internet speed countries (<em>cough</em>Australia<em>cough</em>). Maybe as developers we should do this with our computers too, or just run our code way slower than normal so we can build an intuition around performance. A few years ago I left my laptop at work over a long weekend. Instead of making a trip out to grab it I decided to hook up my raspberry pi (slow-ass gen 1) and use that as a developer machine. Suddenly lots of things that were 'instant' on my normal i7 laptop started feeling awfully sluggish. So I spent the weekend fixing them to make my development workflow smooth. All that perf tuning work carries across to our regular machines. Dropping startup time from 5 seconds to 2 seconds on a raspberry pi feels huge. The same improvement became a drop from 0.5 seconds to 0.2 seconds or something. Thats still super noticeable for users. A 0.5 second startup time is small enough that its easy to overlook during development, but dropping it to 0.2 seconds feels obviously faster.</p>

<hr>

<p><strong>Users:</strong> Please complain more about slow programs. Its 2016. We carry supercomputers in our pockets. Its simply not ok for apps to be sluggish.</p>

<p><strong>Developers:</strong> Performance matters. Memory usage matters. I don't care if you're the prettiest girl at the dance, slack. I quit you the moment I walk out of the office. I delete you from my computer when I can. Slow is a bug. The fastest program is the one you don't run. So stop embedding the entirety of chrome in your app.</p>

<p>Also all you web devs: Go learn C or Rust or something. Your program <em>runs on a computer</em>. Until you know how that computer works, you're doomed. And until then get off my lawn <em>shakes fist</em>.</p>

<p>Oh, and <a href="http://idlewords.com/talks/website_obesity.htm">read this talk on the website obesity crisis</a>. Its very funny. And very sad. And very true.</p>

<hr>

<p><strong>Edit:</strong> Spotify actually uses the <a href="https://en.wikipedia.org/wiki/Chromium_Embedded_Framework">Chromium Embedded Framework</a> directly instead of running via electron. It still embeds chrome though. I didn't know that when I wrote this article, but I stand by what I said above about the resulting performance.</p>

<p><a href="https://news.ycombinator.com/item?id=14087381">Discussion on hackernews</a></p>

<p><a href="https://www.reddit.com/r/programming/comments/64oqaq/electron_is_flash_for_the_desktop/?utm_content=comments&amp;utm_medium=front&amp;utm_source=reddit&amp;utm_name=programming">Discussion on reddit r/programming</a></p>]]></content:encoded></item><item><title><![CDATA[Databases have failed the web]]></title><description><![CDATA[<p>Part 1 - a history lesson</p>

<p>The year is 1980. Last year RSI released Oracle V2, the world's first commercial SQL database for the PDP-11:</p>

<p><a href="https://commons.wikimedia.org/wiki/File:Pdp-11-40.jpg#/media/File:Pdp-11-40.jpg"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/e/ee/Pdp-11-40.jpg/1200px-Pdp-11-40.jpg" alt="Pdp-11-40.jpg"></a></p>

<p>At an unnamed bank you have rooms full of computers like the PDP-11, with specialized computer operators to keep them running. 'Dumb terminals' at people's</p>]]></description><link>https://josephg.com/blog/databases-have-failed-the-web/</link><guid isPermaLink="false">a9508fd3-d1dd-4858-9c53-0b8f6948db4c</guid><dc:creator><![CDATA[Joseph Gentle]]></dc:creator><pubDate>Sun, 11 Sep 2016 15:11:06 GMT</pubDate><content:encoded><![CDATA[<p>Part 1 - a history lesson</p>

<p>The year is 1980. Last year RSI released Oracle V2, the world's first commercial SQL database for the PDP-11:</p>

<p><a href="https://commons.wikimedia.org/wiki/File:Pdp-11-40.jpg#/media/File:Pdp-11-40.jpg"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/e/ee/Pdp-11-40.jpg/1200px-Pdp-11-40.jpg" alt="Pdp-11-40.jpg"></a></p>

<p>At an unnamed bank you have rooms full of computers like the PDP-11, with specialized computer operators to keep them running. 'Dumb terminals' at people's desks allow ordinary office workers to interact with the computer system.</p>

<p><a href="https://commons.wikimedia.org/wiki/File:DEC_VT100_terminal.jpg#/media/File:DEC_VT100_terminal.jpg"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/99/DEC_VT100_terminal.jpg/1200px-DEC_VT100_terminal.jpg" alt="DEC VT100 terminal.jpg"></a></p>

<p>How do you use your new state of the art SQL database? First you had to hire expensive DBAs (database administrators). After reading hundreds of pages of manuals these technicians would issue SQL commands directly. But the database is so fantastically useful that everyone needs to interact with it, down to lowly cashiers. Over the next few years you'll commission software to make interacting with the database easier; automating common tasks and making sure clerks can't accidentally wipe important records with a few misplaced keystrokes.</p>

<p>But in 1980 the writing was on the wall for the PDP-11. The microcomputer craze is in full force, arguably the first of three major reinventions of the computing ecosystem over the next few decades. But we don't know that yet. All we know is that the Apple II came out last year with Visicalc. Next year IBM will release their first Personal Computer. Pretty soon Microsoft will make its play too. "A computer on every desk and in every home, running Microsoft software." </p>

<p>But that hasn't happened yet. But we can see now what survived from that period (or earlier):</p>

<ul>
<li>The idea of a special computer called a 'server'.</li>
<li><a href="https://en.wikipedia.org/wiki/C_(programming_language)">The C programming language</a> (invented in 1972)</li>
<li><a href="https://en.wikipedia.org/wiki/SQL">Structured Query Language</a>, (SQL) used to talk to the database (invented in 1974)</li>
<li>The VT-100 terminal protocol. (All modern *nix and mac terminals pretend to be an old VT-100. Crazy huh?)</li>
</ul>

<p>Almost everything else has been reinvented over and over again in the decades since.</p>

<hr>

<p>Lets jump forward to 1995. What does the same office look like? Well that VT100 terminal won't cut it anymore. Office workers have whole computers on their desks all to themselves. Microcomputers with more capacity than rooms full of mainframes had just a decade ago. Windows NT 3.5 will be released this year; a landmark achievement for microsoft. An operating system with a full TCP/IP networking stack. But don't worry - SQL is still there holding all our customer records. It runs in data centres on big servers. Employees access the SQL database through application software on their workstations. (Oh yeah, we call them workstations now.)</p>

<p>From an architectural point, what changed? Well before the SQL server was a multi-user process (all programs interacting with the database ran on the mainframe itself). Now the SQL server runs alone, though it exposes itself to applications through TCP/IP ports. Application software on workstations throughout the office will connect to the SQL server, authenticate and make queries. The data will be displayed to the office worker in some native Windows application. They can modify fields, insert rows and generate graphs. Its all very fancy.</p>

<p>Access control is quite coarse, but thats ok because access to the database is restricted to employees. They'll need access to the corporate network and a login to the database to do anything. Past those barriers, its probably fine. So long as nobody runs any <em>particularly</em> slow queries or types in the wrong SQL commands directly into the SQL console everything will be fine. (Systems at this time were massively vulnerable to <a href="http://xkcd.com/327/">SQL injection attacks</a> but again, your employees <em>just wouldn't do something like that</em> so nobody was too worried).</p>

<p>When the application wanted to make a change to the data, it was easy. The application would validate the new data then insert it into the database directly (using <code>INSERT INTO ...</code> statements). If your DBAs were fancy I'm sure some people used PL/SQL and other languages embedded in the database itself, but they were always a pain to work with.</p>

<p>But other than having to manually craft SQL commands out of strings, life was looking pretty great.</p>

<hr>

<p>Part 2</p>

<p>But this is where the story gets a lot sadder. Another revolution happened, and nobody is going to tell the old database servers. Actually if you took a DBA from the 80's and showed them a modern laptop running PostgreSQL in a terminal emulator (which emulates the VT-100, remember), they'd be right at home. So at home it'd be embarrassing. Our databases have gotten faster, and they scale better. Buttttt..... we forgot to tell the database that the world changed.</p>

<p>Of course, I'm talking about the web. Or the cloud, if thats how you think about it.</p>

<p>Its like the database people said "Your website is basically a desktop app. The database is fine..." and they got to work tweaking it to be faster and more clever. To implement a frozen spec <em>better</em>. And web developers, descendants of the old frontend application developers said "Whatever, we can work around it in software anyway". And then they got to work writing database middleware in PHP.</p>

<p>And it was good. <em>cough</em> Ahahaha just kidding... it was <em>terrible</em>. To work around the lack of features in modern databases, we had to invent a third kind of thing. Not a database, not an application - but an <em>application server</em>. What does it do? Well, it uh.. takes the data from the browser, and sends it to the database. And takes the data from the database and sends it to the browser.</p>

<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/hNuu9CpdjIo?rel=0" frameborder="0" allowfullscreen></iframe>

<p>Why is this needed? Well 3 reasons:</p>

<ol>
<li>Access control on modern databases is too coarse. You want to whitelist which queries a user is allowed to make and you want fine-grained permissions around updates  </li>
<li>Databases only talk custom binary TCP protocols, not HTTP. Not REST. Not websockets. So you need something to translate between how the server works and how the browser works.  </li>
<li>You want to write complex logic for user actions, with custom on-save triggers and data validation logic. </li>
</ol>

<p>Because these features are tied to the application and data model, they're almost always bespoke systems. I have a degree in computer science, but I've wasted oh so many hours of my life writing variants of the same plumbing code over and over again. Take the data from <em>here</em>, validate it, make a database update request then respond to the browser based on what the database says...</p>

<p>And its hard to write this code correctly. You need to do correct data validation. Check for XSS and protect against SQL injection attacks. And DOS attacks from maliciously crafted queries. Set correct caching headers. Don't overfetch or underfetch data. Implement server side rendering and cache some more.</p>

<p>Entire language ecosystems have grown around solving this problem. PHP. Tomcat. Django. Rails. Node. ORMs became a thing - ActiveRecord for Rails, Mongoose for Node. XML, SOAP, JSON, GraphQL. And all the rest.</p>

<p>All because we're programming against a frozen database spec. Our frontend servers act as a weird form of tumour growing around our databases, injecting themselves as a suture over a broken API.</p>

<p><img src="https://josephg.com/blog/content/images/2016/09/Screen-Shot-2016-09-12-at-1-09-10-AM.png" alt=""></p>

<p>And its 2016 already. I want a bonus 4th missing feature that will never be plumbed manually through every damn REST endpoint. I want realtime updates. Modern databases are eventually-consistent, but only to the boundary of their replica set for some reason??? The industry standard is to simply not tell the user when the data they're looking at is stale.</p>

<p>Why don't modern databases simply provide these features? I don't know. I can guess, but I wont be charitable. Because its hard. Because modern web apps are too new, and server rendered apps look too much like the old desktop apps to give anyone pause. Because database developers don't write frontend code. Because we've had database triggers for ages (though they're still terrible to use, and they don't solve the whole problem).</p>

<p>And yes, because we are fixing it with tools like <a href="https://firebase.google.com/">Firebase</a> and <a href="http://horizon.io/">Horizon</a>.</p>

<p>But they aren't good enough yet. Along with <a href="https://josephg.com/blog/composing-databases/">composability operators</a> I think its time to write some <em>code</em>.</p>]]></content:encoded></item><item><title><![CDATA[Composing databases]]></title><description><![CDATA[<p>Why don't we compose databases the same way we compose our functions?</p>

<p>We use mathematical operators to compose functions all the time. Most of the time we do it without even really thinking about it:</p>

<pre><code>y = op2(op1(x))  
</code></pre>

<p>Or with chaining and more functions and stuff:</p>

<pre><code>complexOp = x =&gt;</code></pre>]]></description><link>https://josephg.com/blog/composing-databases/</link><guid isPermaLink="false">eedb543b-91fa-42a0-972d-100c6ef77c91</guid><dc:creator><![CDATA[Joseph Gentle]]></dc:creator><pubDate>Wed, 24 Aug 2016 05:10:49 GMT</pubDate><content:encoded><![CDATA[<p>Why don't we compose databases the same way we compose our functions?</p>

<p>We use mathematical operators to compose functions all the time. Most of the time we do it without even really thinking about it:</p>

<pre><code>y = op2(op1(x))  
</code></pre>

<p>Or with chaining and more functions and stuff:</p>

<pre><code>complexOp = x =&gt; x.op1().op2()  
y = complexOp(x)  
</code></pre>

<p>(This is 'obvious' so far, but bear with me). So why is function composition so useful? Well because:</p>

<ul>
<li>You can make simple primitives, then build complex functions out of those primitives.</li>
<li>We might have the wrong function for a particular use case, and we need to transform it for a different use case.</li>
<li>You can break up balls of mud into small reusable pieces, then make the computer recombine them.</li>
</ul>

<hr>

<p>So, couldn't we do the same thing with data stores?</p>

<p>Lets imagine a simple idealised key-value store. I'm stealing datomic's view of what a database is - which is simply a set of keys which hold values over time.</p>

<p>So my database will store the number of coconuts each of Sally, George and Sam have. Sally was the first entry in the database (with 6 coconuts). Then we found out George has 10. Then george gave 2 to Sally, who ate one. And so on. Right now Sally has 7, George has 8 and Sam has 5.</p>

<p><img src="https://josephg.com/blog/content/images/2016/08/1.png" alt="timeline of a database"></p>

<p>So there's a few concepts, a databases's <em>keys</em> ('Sally', 'George', 'Sam'), the values at any point in time (now, etc). We can also see that some operations have happened. I'll add them to the diagram:</p>

<p><img src="https://josephg.com/blog/content/images/2016/08/2.png" alt="diagram with events"></p>

<p>These are called <em>transactions</em>, <em>operations</em> or <em>events</em>. They atomically make some set of changes to the values stored in a key.</p>

<p>The transactions will (explicitly or implicitly) happen at some <em>time</em>. This might be a real clock, a logical clock (v1, v2, v3, etc), a vector clock or something fancier like an <a href="http://gsd.di.uminho.pt/members/cbm/ps/itc2008.pdf">interval tree clock</a>).</p>

<p>I'm going to imagine an oversimplified API made up of functions like these, but any real implementation will have a lot more detail:</p>

<pre><code>get(key) =&gt; value  
set((key1, value1), (key2, value2), ...)  
watch() =&gt; stream of (key, value) pairs  
</code></pre>

<p>(The watch function will tell us when any operations are applied to the database).</p>

<h3 id="composition">Composition</h3>

<p>So what equivalents do we have to function composition? Well, we want to make functional middleware of sorts that consume and exposes the same database interface. Lets talk through a few obvious examples.</p>

<h4 id="union">Union</h4>

<p>Given two databases, <code>db1 ∪ db2</code> exposes a new database interface which has the union of all keys. For <code>get()</code> if a key exists in both databases, behaviour is undefined. Writes could always sent to <code>db1</code>, or sent based on some user-specified rules.</p>

<p>Maybe my password database and my coconut databases are separate. I will create a database view <code>view = users ∪ coconuts</code> against which to run queries. From the point of view of the query runner, there is only one database. Infrastructure changes (merging, resplitting, sharding, etc) can all be managed behind that database view.</p>

<p>You could also use the union operator to manage indexes. Imagine implementing Wikipedia. You have a primary store of data, but also a search database. You could design a database view based on <code>view = pages ∪ SearchIndex(pages)</code>.</p>

<h4 id="mount">Mount</h4>

<p>Unfortunately if you're using union alone you might run into namespace collisions. (Does the <code>Steph</code> key mean the user data object or the coconut count?). We can define <code>Mount(db, path)</code> to be a database interface through which all objects in <code>db</code> are accessible with <code>path</code> prepended to the key. <code>set(key, value)</code> would only allow writes where the expected prefix matches. If it matches, the prefix is stripped and the write is sent to the underlying store.</p>

<p>With this and our union function, we can define <code>view = Mount(users, 'users/') ∪ Mount(coconuts, 'coconuts/')</code>. Then <code>view.get('users/Steph')</code> and <code>view.get('coconuts/Steph')</code> are clear and unambiguous.</p>

<p>It complects the abstractions a little but if the union function understood mounts it could allow writes to different paths unambiguously.</p>

<h4 id="filter">Filter</h4>

<p>Given a database and a predicate, the function <code>Filter(db, pred)</code> exposes a new database interface through which only keys which match the predicate are visible.</p>

<p>This would be useful for user access control. So, all of Sam's database accesses hit the <code>samdb = Filter(db, key =&gt; userCanAccess(sam, key))</code> data store. If Sam tries to access the <code>users.seph</code> object, well, in Sam's database view that object simply doesn't exist. (And cannot be created).</p>

<p>You could create variants of this for access control and doing access control based on deep inspection.</p>

<h4 id="view">View</h4>

<p>Now we get into the beautiful stealing from CouchDB land. Lets say you're implementing a blog. To be displayed to the user, each article needs to be rendered to HTML. <code>View(db, fn)</code> will present a database interface through which each value is visible transformed by the function. Writes are not allowed.</p>

<p>This allows us to pre-render (or lazy render + cache) the HTML content of our blog. You could combine this: <code>dbview = Mount(posts, '/post') ∪ Mount(View(posts, renderPost), '/postHTML')</code>. Now the raw post content can be read and modified via <code>'/post/slug'</code> and the rendered content itself is immediately available in the rendered paths.</p>

<hr>

<p>I could happily keep describing useful functions, but we'd be here all day. Lets just name a few more useful things then move on:</p>

<ul>
<li>Schema validation middleware, which passes reads but does checked writes before saving</li>
<li>Expose a local DB view over a network connection</li>
<li>Ingest a remote DB over a network connection</li>
<li>Tools to expose &amp; consume a database from the browser, like firebase.</li>
<li>A caching DB proxy (active via watch, or passive)</li>
</ul>

<h2 id="versioningandconsistency">Versioning and consistency</h2>

<p>I don't want to go to <a href="https://aphyr.com/tags/jepsen">jepsen hell</a>. Is it possible to build this while maintaining some useful consistency guarantees? In short, I believe so. It'll take another blog post this long to talk about how though. I've been thinking about this for a long time and I have a bunch of ideas depending on how general purpose you want to make it. We might also need to restrict what data we're allowed to make transactions across (no transactions spanning multiple primary stores, that sort of thing).</p>

<p>Sharding and replica sets are also interesting - but how they're set up is orthogonal to the logical database network. There's no reason you couldn't have both. The only thing in the way is a lot of code.</p>

<h3 id="isthisevenuseful">Is this even useful?</h3>

<p>Good question. I was recently asked about this while doing some consulting work. "Sure, but what would we use it for?". I wanted a database like this in every single one of our half dozen or so projects. In a sense, MVC is really model-view-everything else. The utility of this sort of thinking is allowing us to move more and more of the 'everything else' into the model.</p>

<p>I'm going to point to projects and say how I'd use a cool pluggable data store.</p>

<h4 id="blogproject">Blog project</h4>

<p>You're making a wordpress-like website with search, powered by server and client rendered react.</p>

<ul>
<li>The server rendering code is slow (200ms). I'd move that into a view on the database, eagerly re-rendering whenever anything is saved in the editor.</li>
<li>The full-text-search would move to an elasticsearch wrapper / interface.</li>
<li>The project uses client-side renders once the page has loaded, for inter-route navigation. I'd expose the database itself to the client through a firebasey API.</li>
</ul>

<h4 id="carparkproject">Carpark project</h4>

<p>The project has people register their car online, then they can drive into carparks. Their license plates are read via a camera and OCR by on-premises computers. The computers have whitelists of cars they allow in automatically. The whitelist needs to be constantly kept up to date. We track how long you stay and charge the customer's credit card directly. Oh yeah, and the internet randomly goes down sometimes.</p>

<p>So the active sync is begging to be implemented via a filter + simple caching middleware. The root server has a view through which only the whitelisted license plates are exposed. That view is actively synced + cached to the carpark's computer. Going the other way we cache entry &amp; exit events on the carpark computer and actively flush those back to the root servers.</p>

<hr>

<p>I'm going to stop there not because I don't have more ideas, but because I could be at this for days. Anyway, because nobody else has done it yet, I really want to build this thing. I think its an obvious and important piece of internet infrastructure that would make it much easier to build cool stuff simply.</p>

<p>If your company is interested in funding me to build this &amp; opensource it all, get in touch - I'm <a href="https://josephg.com/blog/composing-databases/&#109;&#x61;&#x69;&#108;&#x74;&#111;:m&#x65;&#x40;&#106;&#x6f;&#115;&#x65;&#112;h&#x67;&#x2e;&#x63;&#111;&#x6d;">m&#x65;&#x40;&#106;&#x6f;&#115;&#x65;&#112;h&#x67;&#x2e;&#x63;&#111;&#x6d;</a>.</p>]]></content:encoded></item></channel></rss>