<?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[Richard's Blog]]></title><description><![CDATA[For dull technical discussions.]]></description><link>https://blog.r6l7.com/</link><image><url>https://blog.r6l7.com/favicon.png</url><title>Richard&apos;s Blog</title><link>https://blog.r6l7.com/</link></image><generator>Ghost 4.48</generator><lastBuildDate>Sat, 31 Jan 2026 05:55:44 GMT</lastBuildDate><atom:link href="https://blog.r6l7.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Selected Sequence CRDTs]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>When reading <a href="http://shop.oreilly.com/product/0636920032175.do">Designing Data-Intensive Applications</a>, I was intrigued by the brief mention of Conflict-free replicated Datatypes, or <a href="https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type">CRDT</a>s.  These data structures can be copied to many computers in a network, where they undergo concurrent modification, and later be merged back together into a result that accurately reflects all of</p>]]></description><link>https://blog.r6l7.com/selected-sequence-crdts/</link><guid isPermaLink="false">5e79791f10124a08b73b72f0</guid><category><![CDATA[algorithms]]></category><dc:creator><![CDATA[Richard Larocque]]></dc:creator><pubDate>Sun, 15 Apr 2018 10:05:31 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>When reading <a href="http://shop.oreilly.com/product/0636920032175.do">Designing Data-Intensive Applications</a>, I was intrigued by the brief mention of Conflict-free replicated Datatypes, or <a href="https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type">CRDT</a>s.  These data structures can be copied to many computers in a network, where they undergo concurrent modification, and later be merged back together into a result that accurately reflects all of those concurrent modifications.  The simplest such type is an incrementing counter, but there exist CRDTs for more complicated structures like lists and maps.</p>
<p>This reminded me of an interesting problem I worked on long ago.  Abstractly, the problem was to allow concurrent edits to an ordered list in an environment where the computers making those changes were sometimes disconnected from the network, making coordination impossible.  We came up with <a href="https://github.com/chromium/chromium/blob/master/components/sync/base/unique_position.h#L21">a solution</a>, but didn&apos;t really have a name for it.  I&apos;ve long wondered whether or not there existed any well-known solutions to this problem.</p>
<p>It turns out that our solution was actually a &quot;Sequence CRDT&quot;.  These CRDTs are a topic of active research and have applications in collaborative document editing.  If you can allow distributed edits to a sequence of characters, then you&apos;ve solved one of the hairier problems of building a &quot;Google Docs&quot;-like collaborative editor.</p>
<p>Now that I know where to look, I can compare our approach to more well-known academic solutions to the same problem.</p>
<h3 id="introductions">Introductions</h3>
<p>All three of the solutions I&apos;ll be discussing have certain ideas in common.</p>
<p>Every scheme involves some sort of &quot;position identifier&quot; to represent a single element&apos;s position within a sequence.  These identifiers must be unique and <a href="https://en.wikipedia.org/wiki/Dense_order">densely ordered</a>.</p>
<p>Somewhat informally, given two positions $x$ and $y$ representing the positions of two elements in some sequence, then:</p>
<ul>
<li>$x \neq y$ (uniquness); and</li>
<li>assuming $x &lt; y$, there exists some $z$ where $x &lt; z &lt; y$ (density).</li>
</ul>
<p>The use of position identifiers allows the position of an element can be specified entirely within the element itself, thus avoid many of the perils of conflicting changes being made without coordination.</p>
<p>If we had used a linked list structure, tracking successors or predecessor to define the sequence ordering, we might find ourselves in trouble if that predecessor or successor had been moved by another agent while we made an offline change.</p>
<p>If we had used a non-dense set of identifiers, like the set of integers, we might eventually find ourselves with $x$ and $y$ having values 23 and 24 respectively, and we would be unable to find any integer value between them.</p>
<p>If we have density but lack uniqueness guarantees, it&apos;s possible that user A and user B take similar actions at the same time that create positions $x$ and $y$ where $x = y$, but then you have the uncomfortable fact that there is no $z$ where $x &lt; z &lt; y$.</p>
<p>With these constraints in place, it is possible to write functions that generate positions for inserting elements between any two existing elements within the list.  These new elements can be merged into other replicas of the sequence that may have undergone different modifications, and their relative positioning will remain unchanged.</p>
<h3 id="logoot">Logoot</h3>
<p>The paper introducing Logoot<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup> includes a good explanation of the underlying theory and the applications to collaborative document editing.  My description of it will involve lots of non-rigorous hand-waving, so please refer to the paper if you want a more complete explanation.</p>
<p>In Logoot, the position consists of a list of <code>(Integer, SiteId)</code> tuples.  The integer values are chosen for sequence ordering purposes, while the <code>SiteId</code> ensures that every position value is unique.</p>
<p>For example, we might have positions $x$ and $y$ that look like this:</p>
<p>$$<br>
x=[(10,s_1),(5,s_2),(7,s_3)]<br>
$$</p>
<p>$$<br>
y = [(10, s_4), (6, s_6)]<br>
$$</p>
<p>(where $s_n$ is some arbitrary site identifier)</p>
<p>Comparison of position identifiers is implemented by comparing the integer values at each position in the list from left to right.  The <code>SiteId</code> values are used as a tie-breaker if the integer values are equal.  The <code>SiteId</code> is generated when the element is created by combining a client-specific unique identifier and a client-local logical clock that is incremented with every new position brought into existence on that client, which guarantees no two <code>SiteId</code> values will ever be equal.</p>
<p>If we wanted to insert a position <code>z</code> between the two positions, it might look something like this:<br>
$$<br>
z = [(10, s_7), (5, s_8), (3, s_9)]<br>
$$<br>
(the last element could be omitted if $s_8 &gt; s_2$, but this choice of $z$ works for any choice of $s_8$ and $s_2$.)</p>
<p>This is one of many possible $z$ values.  It turns out that the algorithm used to choose among the many possible in-between values can have a big impact on performance.  If the choice is made poorly, or a worst-case scenario is encountered, the length of these lists can grow without bound.</p>
<p>That problem forms the motivation for looking at the next algorithm.</p>
<h3 id="lseq">LSEQ</h3>
<p>The LSEQ paper<sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup> includes a thorough discussion of allocation strategies and their consequences.</p>
<p>It refers to one of the allocation strategies recommended by the Logoot paper as the &quot;boundary&quot; allocation strategy, in that tended to stick to the &quot;leftward&quot; edge of possible position choices.  That strategy is very effective when new elements are generally inserted at the end of the sequence, as is typical in document editing.  But it performs poorly when new elements are added to the beginning of the sequence.</p>
<p>LSEQ uses two allocation strategies: &quot;boundary+&quot; and &quot;boundary-&quot;.    The former is a re-branding of Logoot&apos;s original &quot;boundary&quot; strategy and the latter is its opposite.  The former favors right-insertion and the latter favors left-insertion.</p>
<p>LSEQ also introduces the concept of different &quot;bases&quot; at each level of the list.  Logoot uniformly used &quot;Integers&quot; in its list elements (presumably meaning <code>uint32_t</code> or <code>uint64_t</code>).  LSEQ doubles the available position-space with every list element.  So one could have the first element in the range (0..8), the second in range (0..16), third in range (0..32), and so on.</p>
<p>This is combined with the clever idea of selecting different allocation strategies at each level.  This ensures that, even if a particular edit-pattern is a worst-case at one level, the position IDs being created by that pattern will soon overflow to the next level, where that same editing pattern may well be a best-case scenario.</p>
<p>The paper ends with some benchmarks on real-world data (Wikpedia edits) that confirm this strategy does in fact perform better than Logoot in many cases.</p>
<h3 id="uniquepositions">UniquePositions</h3>
<p>UniquePositions are a completely different implementation of the sequence CRDT concept.</p>
<p>The position identifiers are intended to be stored and compared as C strings.  This has the benefit of making them compatible with existing systems that may not support more complex comparison algorithms.  And since <code>strcmp(3)</code> is highly optimized on most systems, it&apos;s likely the comparison will be reasonably fast, too.</p>
<p>The uniqueness comes from a guaranteed-unique suffix that forms the last 28 bytes of the string.  These are generated when the position identifier first comes into existence and depend on a large number of random bits to ensure uniqueness.</p>
<p>The <a href="https://github.com/chromium/chromium/blob/master/components/sync/base/unique_position.cc#L290">algorithm</a> for inserting between two existing positions is rather convoluted because of the complexity of keeping that unique suffix around while also trying to minimize ID growth.</p>
<p>All of this adds up to an algorithm that&apos;s kind of similar in behavior to Logoot, in that it suffers from unbounded identifier growth.  We&apos;ll deal with that in the next section.</p>
<h4 id="compressinguniquepositions">Compressing UniquePositions</h4>
<p>If we look at the positions that result from worst-case edit patterns, we see what looks like a compression problem.  The strings tend to grow to have large prefixes of <code>0xff, 0xff, 0xff, ...</code> or <code>0x00, 0x00, 0x00, ..</code> depending on whether the insertion is repeated on the right or left.  This suggests that some form of <a href="https://en.wikipedia.org/wiki/Run-length_encoding">run-length encoding</a> might be appropriate.</p>
<p>So we start with textbook run-length encoding.  When we see more than four characters of the same value <code>x</code> and certain alignment criteria are met, we can encode them as <code>[x, x, x, x] ++ &lt;32-bit big-endian count of x&gt;</code>.  This is a wasteful encoding if there are only 4-7 of these characters present; but it&apos;s quite effective at compressing long runs of repeated characters up to 2^32 characters long.</p>
<p>But this means we&apos;d lose the nice property we had earlier where our comparison function was simple string comparison.  What we&apos;d like is some compression function <code>C</code> that comes with a guarantee that <code>x &lt; y</code> implies <code>C(x) &lt; C(y)</code>.</p>
<p>Taking some key ideas from a paper called &quot;Order-Preserving Key Compression&quot;<sup class="footnote-ref"><a href="#fn3" id="fnref3">[3]</a></sup>, we allow two different ways to encode the same count.  Rather than having a simple 32-bit unsigned count, we allow counts up to 31-bits to be encoded either as <code>c</code>, the count itself, or <code>2^32 - c</code>.  For decompression purposes either encoding represents the same count.</p>
<p>The choice of encoding is not arbitrary.  It is chosen to ensure the compressed strings have the same sort-ordering as their uncompressed equivalents.  From the <a href="https://github.com/chromium/chromium/blob/master/components/sync/base/unique_position.cc#L440">source code</a>:</p>
<blockquote>
<p>When producing a repeated character block, the count encoding must be chosen in such a way that the sort ordering is maintained.  The choice is best illustrated by way of example:</p>
<blockquote>
<p>When comparing two strings, the first of which begins with of 8 instances of the letter &apos;B&apos; and the second with 10 instances of the letter &apos;B&apos;, which of the two should compare lower?  The result depends on the 9th character of the first string, since it will be compared against the 9th &apos;B&apos; in the second string.  If that character is an &apos;A&apos;, then the first string will compare lower.  If it is a &apos;C&apos;, then the first string will compare higher.</p>
</blockquote>
<p>The key insight is that the comparison value of a repeated character block depends on the value of the character that follows it.  If the character follows the repeated character has a value greater than the repeated character itself, then a shorter run length should translate to a higher comparison value.  Therefore, we encode its count using the low encoding.  Similarly, if the following character is lower, we use the high encoding.</p>
</blockquote>
<p>In the end, we get the desired shortening of long-repeated strings of <code>0xff</code>s caused by right-insertion and <code>0x00</code>s caused by left-insertion.</p>
<h3 id="chartsandconclusions">Charts and Conclusions</h3>
<p>I built some <a href="https://github.com/richardlarocque/sequence-crdt">micro-benchmarks</a> so I could test out UniquePosition against Logoot and LSEQ.  Here is</p>
<p><img src="https://blog.r6l7.com/content/images/2020/03/chart.png" alt="right-insertion benchmarks" loading="lazy"><br>
<img src="https://blog.r6l7.com/content/images/2020/03/chart--1-.png" alt="left-insertion benchmarks" loading="lazy"></p>
<p>The uncompressed variants of UniquePosition grow almost as quickly as Logoot, which is not good.  Those 10KB position identifiers would be likely to create challenges for any normal use case.  But we can also see that LSEQ and <em>compresed</em> unique positions both do a very good job of keeping the identifier size growth under control.</p>
<p>That said, I would definitely prefer LSEQ to UniquePosition in new projects.  The current implementation of unique positions requires decompressed positions when performing inserts.  Although it&apos;s only temporary, the additional allocations and O(n) comparisons performed on these long positions would be rather expensive.  LSEQ has no such problem.</p>
<h3 id="references">References</h3>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p><a href="https://hal.inria.fr/inria-00432368">https://hal.inria.fr/inria-00432368</a> &quot;St&#xE9;phane Weiss,  Pascal Urso,  Pascal Molli.   Logoot:   A Scalable Optimistic Replication Algorithm for Collaborative Editing on P2P Networks.    29th IEEE International Conference on Distributed Computing Systems - ICDCS 2009, Jun 2009, Montreal, Canada.  IEEE, pp.404-412, 2009, 2009 29th IEEE International Conference on Distributed Computing Systems.  &lt; <a href="http://www.computer.org/portal/web/csdl/doi/10.1109/ICDCS.2009.75">http://www.computer.org/portal/web/csdl/doi/10.1109/ICDCS.2009.75</a> &gt; &lt; 10.1109/ICDCS.2009.75 &gt; &lt; inria-00432368 &gt; <a href="#fnref1" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
<li id="fn2" class="footnote-item"><p><a href="https://hal.archives-ouvertes.fr/hal-00921633">https://hal.archives-ouvertes.fr/hal-00921633</a> &quot;Brice N&#xE9;delec, Pascal Molli, Achour Mostefaoui, Emmanuel Desmontils. LSEQ: an Adaptive Structure for Sequences in Distributed Collaborative Editing. 13th ACM Symposium on Document Engineering (DocEng), Sep 2013, Florence, Italy. pp.37&#x2013;46, 2013, &lt; 10.1145/2494266.2494278 &gt;. &lt; hal-00921633 &gt;&quot; <a href="#fnref2" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
<li id="fn3" class="footnote-item"><p><a href="http://bitsavers.trailing-edge.com/pdf/dec/tech_reports/CRL-94-3.pdf">http://bitsavers.trailing-edge.com/pdf/dec/tech_reports/CRL-94-3.pdf</a> &quot;Gennady Antoshenkov, David B. Lomet, and James Murray. 1996. Order Preserving Compression. In Proceedings of the Twelfth International Conference on Data Engineering (ICDE &apos;96), Stanley Y. W. Su (Ed.). IEEE Computer Society, Washington, DC, USA, 655-663. &quot; <a href="#fnref3" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
</ol>
</section>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Web Service Concurrency Shoot-out]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>I work mainly on web services, mainly in Ruby. Our team is pretty happy with it. We have no intention of rewriting existing Ruby code into other languages.</p>
<p>However, we&apos;re running into cases where Ruby is not the right tool for the job. That has led us to</p>]]></description><link>https://blog.r6l7.com/web-service-concurrency-shoot-out/</link><guid isPermaLink="false">5e79791f10124a08b73b72ef</guid><category><![CDATA[Tech]]></category><dc:creator><![CDATA[Richard Larocque]]></dc:creator><pubDate>Sun, 07 Jan 2018 08:58:14 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>I work mainly on web services, mainly in Ruby. Our team is pretty happy with it. We have no intention of rewriting existing Ruby code into other languages.</p>
<p>However, we&apos;re running into cases where Ruby is not the right tool for the job. That has led us to search for a general purpose &quot;fast&quot; language that we can use as an alternative.</p>
<p>This post focuses on a particular aspect of &quot;fast&quot;: support for threads and concurrent execution. Ruby and Python are sometimes criticized for their global interpreter locks that prevent them from using more than one CPU at a time. But how does this matter in practice for web services, and how much better is the competition?</p>
<p>Before we discuss the details, let&apos;s look more closely at our use case.</p>
<h3 id="webservices">Web Services</h3>
<p>As a web service, our number one metric for throughput is requests serviced per second. To optimize throughput, we can either serve individual requests more quickly (ie. reduce latency), or serve more requests concurrently.</p>
<p>Reducing latency is clearly the better option. Lower request time usually translates to happier users and decreased server costs. But this approach tends to hit diminishing returns pretty quickly.</p>
<p>Reducing latency in a typical web service is difficult because most of the request time is not dependent on the server. The server will do some processing, translate the request into a database query, forward it along, wait for a result, then re-encode the result and pass it back to the user. Most of the request time is spent in the &quot;waiting for database result&quot; step. If waiting for the database makes up 90% of the request latency, then optimizing the non-database, web-server-dependent parts will at most result in a 10% latency speedup (by Amdahl&apos;s Law).</p>
<p>So we turn our attention to the second optio: concurrency. Here we see a big opportunity to make an impact. Since a request that&apos;s blocked waiting for a response from a remote service requires no CPU time, we could in theory support hundreds of them at once. Maybe even tens of thousands of requests are not outside the realm of possibility.</p>
<p>This is a exciting possibility. If we could process thousands of requests in parallel (compared to the 40ish concurrent requests per CPU that our Rails servers are currently configured to support) we could serve the same amount of traffic with much fewer server instances.</p>
<p>But it turns out that serving ten thousand connections simultaneously is a pretty hard problem. In fact, they even have a name for it: the c10k problem.</p>
<p>Let&apos;s look at some common technologies to see how they&apos;ve approached this problem and how well it scales.</p>
<h3 id="mriruby">MRI Ruby</h3>
<h4 id="ruby18">Ruby 1.8</h4>
<p>In Ruby 1.8, you could call Thread.new as often as you like, and top would still (correctly) show no new OS threads. Threads in Ruby 1.8 were more like &quot;green threads&quot; or &quot;fibers&quot;. The OS had no idea how many of these Ruby 1.8 threads your program is running at a time.</p>
<p>This model has some drawbacks. Normally, when a thread makes a blocking call like sleep(2) or read(2), the OS will put that thread to sleep so it can give CPU time to some other thread that isn&apos;t sleeping. This ensures the CPU is always doing work, so long as there is work to be done. But the OS can&apos;t block and resume threads it doesn&apos;t know about.</p>
<p>The Ruby 1.8 VM uses some pretty clever tricks to work around this limitation. In that version of Ruby, Kernel#sleep did not call sleep(2). Instead, it coordinated with the VM&apos;s scheduler to yield the CPU to another Ruby thread. Same thing with File#read. The Ruby 1.8 VM effectively included logic equivalent to an OS process scheduler, including a form of preemption.</p>
<p>Unfortunately, Ruby 1.8 was less good at doing this sort of thing than a typical OS would be. Certain gems, including an early version of the MySQL gem, would still make OS calls that would block every thread in the process. In fact, writing gems the &quot;wrong&quot; way would be significantly easier than writing them the right way. And even without poorly written gems, the VM&apos;s threading logic was not especially good or fast. (See this blog post from Joe Damato for all the gory details.)</p>
<p>So in Ruby 1.9 they did things differently.</p>
<h4 id="ruby19">Ruby 1.9</h4>
<p>In Ruby 1.9 Thread.new created real OS threads. They VM still did not allow threads to run in parallel; a Ruby process was still limited to using at most one CPU at a time, thanks to the interpreter lock. But at least the OS could see the Ruby threads as threads.</p>
<p>For most use cases, this was a big improvement. The Ruby VM no longer had to implement its own process scheduling and preemption logic. They could use the OS to schedule threads, and the OS generally did this faster and better than Ruby 1.8 used to. Gems could be written that would call out to C libraries and make blocking OS calls without blocking other threads.</p>
<p>But there are drawbacks to this approach, too. The next section will demonstrate one of them.</p>
<h4 id="microbenchmarkruby18vsruby2x">Micro-benchmark: Ruby 1.8 vs. Ruby 2.x</h4>
<p>Here is a tiny benchmark meant to test some form of scalability. The idea is to create thousands of threads and have them all go to sleep.</p>
<pre><code>NUM_THREADS = 100000  
ts = (1..NUM_THREADS).map do |i|  
  Thread.new(i) do |x|
    sleep 10
    puts x
  end
end

ts.each(&amp;:join)  
puts &apos;done&apos;
</code></pre>
<p>I compiled a non-optimized copy of Ruby 1.8.7 and compared it with the Ruby 2.3 that I obtained through my package manager. Here&apos;s how long this benchmark took for each of them.</p>
<p>At 10,000 threads:<br>
Ruby 1.8: 24 seconds<br>
Ruby 2.3: 18 seconds</p>
<p>At 100,000 threads:<br>
Ruby 1.8: 3 minutes<br>
Ruby 2.3: crashed</p>
<p>The lesson I would take from this benchmark is that OS threads come with a price. They&apos;re really good at what they do up until a point. Beyond that point, the scheduling and memory overheads start to get really expensive.</p>
<p>This matters because a web service is not far removed from this benchmark. From a scheduling point of view, a thread that&apos;s sleeping looks a lot like a thread that&apos;s waiting for a response from a database. Our ideal highly-concurrent web service would be able to support many thousands of these requests simultaneously.</p>
<h3 id="go">Go</h3>
<p>Go has made a name for itself in part because of its unique support for concurrency. Its goroutines are notable for their low memory usage and minimal scheduling overhead.</p>
<p>Goroutines are a kind of userspace thread. Go spawns OS threads in proportion to the number CPUs available (or the GOMAXPROCS setting). The Go runtime schedules goroutines to run on these threads. The OS knows about the threads, but not the goroutines.</p>
<p>It&apos;s also pretty smart about scheduling. Like Ruby 1.8, it implements a time.Sleep not as a sleep(2) system call, but as an indication to the thread scheduler to not schedule this thread for a while. That means sleeping is really fast.</p>
<p>I rewrote the 100,000 sleeping thread benchmark from the Ruby section in go:</p>
<pre><code>package main

import &quot;fmt&quot;  
import &quot;time&quot;  
import &quot;sync&quot;

func main() {  
    const NumSleepers = 100000

    var wg sync.WaitGroup
    wg.Add(NumSleepers)

    for i := 0; i &lt; NumSleepers; i++ {
        go func(index int) {
            time.Sleep(10 * time.Second)
            fmt.Println(&quot;Done: &quot;, index)
            wg.Done()
        }(i)
    }
    wg.Wait()
}
</code></pre>
<p>Go crushed it. This program took less than 12 seconds to run. The number of OS threads remained constant throughout. Compared to Ruby, which took 3 minutes or crashed, this is quite an improvement.</p>
<p>But it turns out this is a case of gaming the benchmark. Or at least a demonstration that the benchmark is poorly written. While the go runtime does have a cheap implementation of sleep, other kinds of blocking calls are not so efficient. Here&apos;s another demonstration:</p>
<pre><code>package main

import &quot;fmt&quot;  
import &quot;time&quot;  
import &quot;bufio&quot;  
import &quot;os&quot;

func main() {  
    const NumSleepers = 100

    // Spawn |NumSleepers| goroutines that block on STDIN.
    for i := 0; i &lt; NumSleepers; i++ {
        go func(index int) {
            for {
                bio := bufio.NewReader(os.Stdin)
                bio.ReadLine() // Block waiting for input.
            }
        }(i)
    }
    // Block forever.
    x := make(chan struct{})
    &lt;-x
}
</code></pre>
<p>In this case, Go actually creates 100 OS threads, in proportion to the 100 blocked goroutines. Unlike time.Sleep, the runtime can&apos;t avoid the syscall implicit in bio.Readline() quite so easily. Crank up NumSleepers to 100,000 and this program starts to panic and crash just as much as the Ruby 2.3 benchmark did in the previous section.</p>
<p>Unfortunately for us, the blocking I/O microbenchmark is a better simulation of the web server than the sleep test. Our web server&apos;s requests are not spending most of their time waiting for a wall-clock. They&apos;re performing read(2) and write(2) calls on sockets. Go, in its current implementation, would be forced to spawn OS threads to handle this I/O. If it spawns too many threads, it crashes.</p>
<p>What we really need to scale this web service is event-driven I/O, like what Ruby 1.8 had. But we want it to be faster and more consistently implemented than that. Which brings us to the next candidate on our list.</p>
<h3 id="nodejs">Node.js</h3>
<p>Enter Node.js. It uses only one OS thread, but on that thread there are multiple contexts of execution. When one context is blocked because it&apos;s waiting on a timer or blocking I/O, the runtime just context-switches to the next one.</p>
<p>If you read through the c10k link from earlier, you already know that a typical good solution to this problem is to use non-blocking I/O with event loop based around a select(2)-like interface, probably wrapped in a library like libevent or libuv for convenience.</p>
<p>Node.js is a JavaScript wrapper around a c10k solution.</p>
<p>Like Go, it aces the 100,000 sleeping threads test:</p>
<pre><code>#! /usr/bin/env node

var numSleepers = 100000;  
for (var i = 0; i &lt; numSleepers; i++) {  
  var delay = 10000;
  setTimeout(function(x) {
    console.log(&apos;Done &apos; + x);
  }, delay, i);
}
</code></pre>
<p>This runs in 11.5 seconds. That&apos;s actually a little bit faster than Go, possibly due to having a simpler (and less fair) scheduling algorithm.</p>
<p>Unlike Go, it aces the blocked-on-file variant of the test, too:</p>
<pre><code>#! /usr/bin/env node

var fs = require(&apos;fs&apos;);

var numSleepers = 100000;  
for (var i = 0; i &lt; numSleepers; i++) {  
  var buf = new Buffer(1024);
  fs.read(process.stdin.fd, buf, 0, 1024, null,
          function(x) {
            return function() {
              console.log(&quot;Done&quot; + x);
            }
          }(i));
};
</code></pre>
<p>This looks synchronous, but each of those callbacks contains its own context.<br>
And yet this program spawns a constant number of OS threads no matter how many concurrent I/O operations are in progress. It&apos;s fast, too: when I pipe /bin/yes into that program it outputs 100,000 &quot;Done&quot; lines and terminates in under three seconds.</p>
<p>So it seems like the JavaScript community got something right. Although Node.js can&apos;t make use of multiple cores as easily as some other languages, and the non-preemptive scheduling might be problematic in CPU-bound workloads, for an I/O heavy server this model is pretty nice.<br>
Conclusions and Caveats</p>
<p>So Node.js is the winner, right?</p>
<p>Well, that depends on context. I&apos;ve made a few assumptions along the way that led to this conclusion. Your context may differ.</p>
<p>First, I assume that your web server isn&apos;t doing a lot of CPU-intensive work, or if it does, that this work could easily be farmed out to a separate process. If you insist on doing lots of computation in the same process that handles web requests, you might want to choose something else. A server like that is likely to hit CPU bottlenecks long before it hits 10,000 concurrent requests anyway.</p>
<p>Second, I assume that cooperative multitasking is good enough for use case. If your service supports even one kind of CPU-intensive request, then the Node.js cooperative multitasking model should scare you. In Node.js a single bad request could hog the CPU and the event loop could affect the latency of all other requests in the system. Preemptive multi-tasking models, like OS threads or goroutines, would preempt such a request before it disrupts the rest of the system.</p>
<p>Finally, I&apos;ve implicitly rejected lots of other viable solutions because I assume that ecosystem support is important. The c10k solutions found in other languages tend to involve non-idomatic tricks that make the vast majority of their language&apos;s package ecosystem unavailable. Compare, for example, this IMAP for EventMachine and the Net::IMAP implementation that ships as part of Ruby&apos;s standard library. Which of these would you expect has the larger community? But if you&apos;re attached to a language other than JavaScript and don&apos;t mind working with a smaller ecosystem, then you can take your pick of non-JavaScript event-driven frameworks. Ruby&apos;s EventMachine, Python&apos;s Twisted, and Java&apos;s Netty all seem to be perfectly fine choices, though I admit to having little first-hand experience with them.</p>
<p>That said, if your use case requires a strong open-source ecosystem support, has a workload heavily dependent on I/O, and you want to minimize the number of CPU cores needed to solve the problem, then yes, Node.js is a good choice.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Announcing Vox]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p><a href="http://vox.r6l7.com">Vox</a> is a login-free voice and text chat platform.  I wrote and published it months ago, but haven&apos;t had a soapbox on which to talk about it until recently.</p>
<h3 id="howtouseit">How to use it</h3>
<p>Vox is a low-friction way to talk to a group of people.  There are no</p>]]></description><link>https://blog.r6l7.com/announcing-vox/</link><guid isPermaLink="false">5e79791f10124a08b73b72ea</guid><category><![CDATA[Tech]]></category><category><![CDATA[vox]]></category><category><![CDATA[web]]></category><dc:creator><![CDATA[Richard Larocque]]></dc:creator><pubDate>Sun, 25 Jan 2015 09:07:56 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p><a href="http://vox.r6l7.com">Vox</a> is a login-free voice and text chat platform.  I wrote and published it months ago, but haven&apos;t had a soapbox on which to talk about it until recently.</p>
<h3 id="howtouseit">How to use it</h3>
<p>Vox is a low-friction way to talk to a group of people.  There are no installs, no sign-ups, and no prompts except for the browser&apos;s request for microphone access.</p>
<p>The basic concept is similar to IRC.  If you visit <a href="http://vox.r6l7.com/r/demo1">http://vox.r6l7.com/r/demo1</a>, you&apos;ll be brought to the &quot;demo1&quot; room.  Anyone else who visits that channel will be brought to the same room.  This makes it possible to set up a common meeting place based on a topics or shared interests.</p>
<p>If you&apos;d prefer to avoid random strangers, you can pick a room name at random.  Or you can visit <a href="http://vox.r6l7.com/">http://vox.r6l7.com/</a> directly without specifying a room name, which will redirect you to a new, randomly generated room.  Then you can share the link with whoever you want to talk to by other means.</p>
<p>This makes it easy to upgrade an existing text conversation into a voice conversation.  All you have to do is visit the site then share the link with someone who has a browser and you&apos;re good to go.  Modern mobile browsers should work as well just as well as Desktops.</p>
<p>In short: No more exchanging Skype IDs!</p>
<h3 id="underlyingtechnologies">Underlying Technologies</h3>
<p>The main technical ingredient is <a href="http://www.webrtc.org/">WebRTC</a>.  There&apos;s really no other way I could have written it, so keep that in mind as I rant about the drawbacks.</p>
<p>Support is <a href="http://iswebrtcreadyyet.com/">mixed</a>.  Apparently it doesn&apos;t work on Safari or IE.  I didn&apos;t bother to test those browsers since I don&apos;t use Windows or iOS, but I acknowledge a lot of others are still stuck on those platforms.</p>
<p>Even where it is supported, there are issues.  I spent hours trying to implement a volume slider, but kept getting only silence in Chrome.  Eventually I learned that combining WebRTC with WebAudio is currently not supported on Chrome.  That&apos;s <a href="crbug.com/121673">bug 121673</a>.  Expect to see a volume slider added some time after that bug is fixed.</p>
<p>All in all, it&apos;s very much a web technology.  It&apos;s easy to build and use, except for the part where the browser vendors decided to each go their own way.</p>
<h3 id="limitations">Limitations</h3>
<p>The audio traffic is peer-to-peer.  This makes the server very scalable.  All it has to do is pass around a few control-plane messages and handle the text chat traffic.  Clients, on the other hand, would probably have a bad time if the room had any more than a few people.</p>
<p>Scaling on the server side is pretty bad, too.  I&apos;ve been mainly focused on basic functionality, so I haven&apos;t gotten around to implementing <a href="http://12factor.net/">twelve factor</a> principles.  The room state really should be stored in <a href="http://redis.io">Redis</a> or some other equivalent.  For now, the assumption of &quot;one service, one process, one machine&quot; is baked into the code.</p>
<h3 id="seealso">See Also</h3>
<p>If you like the concept but don&apos;t like my implementation of it, check out <a href="https://talky.io">Talky.io</a>.  I didn&apos;t know about them when I started the project, but it seems they&apos;ve built something quite similar, and did it first.  They&apos;re also much better at web design.</p>
<p>In fact, you should probably just use them instead of Vox.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[It Works: Running on NixOS]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Well, that was an ordeal.  It took the better part of a week, but I have a server again.</p>
<p>This blogging platform was not part of the problem.  It was a joy to work with.  Hats off to the people at <a href="https://ghost.org">Ghost</a></p>
<p>The reason it took so long to setup</p>]]></description><link>https://blog.r6l7.com/it-works-nix/</link><guid isPermaLink="false">5e79791f10124a08b73b72e9</guid><category><![CDATA[NixOS]]></category><category><![CDATA[SysAdmin]]></category><category><![CDATA[Tech]]></category><dc:creator><![CDATA[Richard Larocque]]></dc:creator><pubDate>Mon, 12 Jan 2015 12:16:05 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Well, that was an ordeal.  It took the better part of a week, but I have a server again.</p>
<p>This blogging platform was not part of the problem.  It was a joy to work with.  Hats off to the people at <a href="https://ghost.org">Ghost</a></p>
<p>The reason it took so long to setup is that it&apos;s running on an OS from the future.  It&apos;s called <a href="https://nixos.org">NixOS</a>, and some of the time it&apos;s really great.</p>
<h2 id="nix">Nix</h2>
<p>The headline feature for this operating system is that it uses Nix, the purely functional package manager.  With Nix, all packages are given unique names that include a hash of all their inputs, including not just their sources but also the compilers used, the build scripts, and the rest of the environment.  Inputs that are not explicitly specified are not present for the package build process, so it&apos;s really hard to create hidden dependencies.  It&apos;s kind of like <code>deb</code> or <code>rpm</code>, but a little better about tracking dependencies.</p>
<p>Its approach to deploying those packages is unique.  Rather than install packages in place, it stashes them all in the Nix store (located at <code>/nix/store</code>) and uses a forest of symlinks and <code>$PATH</code> entries to put them together into a useful combination.  Want to use a new version of Python for a little while, without reconfiguring your system?  Just invoke <code>nix-shell</code> with the proper incantations and it will build you a new environment and start a shell in it.  With a system like this, package rollbacks are pretty easy, too.</p>
<p>If you&apos;re a Haskell programmer and you haven&apos;t heard of Nix before, you may be salivating right now.  That language has an unforuntate combination of being finicky about package versions, and a tooling system that&apos;s not very good about installing two versions of the same library side-by-side.  Nix&apos;s features will be of useful for lot of people, but Haskell programmers have more reasons to be excited about it.</p>
<h2 id="nixos">NixOS</h2>
<p>All that&apos;s pretty neat, and an operating system based on Nix would be interesting in of itself.  But NixOS goes beyond that starting point.</p>
<p>NixOS puts itself in charge of building <code>/etc</code> and other config paths based on a single <code>configuration.nix</code> file.  It does this using the same domain-specific language as the Nix package manager.  The language has all the usual features, like functions, variables, and importing other files.  It turns out this is a great idea.</p>
<p>I never noticed it before, but there&apos;s a lot of redundant work in configuring a typical Linux box.  Ports and user names wind up in several different config files, and the system doesn&apos;t work if they don&apos;t line up.  It&apos;s much easier to manage these things when a port number or user name can be specified as a variable which can then be pulled into several different config files.</p>
<p>Although the end result is an <code>/etc</code> that should look familiar to most admins, the work of formatting the Nix configs into the usual file formats is well hidden.  That&apos;s another benefit of using a programming language for this; all the logic for writing these files can be handled in a library.  The admin sees a single uniform interface for configuring everything.</p>
<p>Almost everything is written to be easy to use.  Here&apos;s one of my favorites:</p>
<pre><code>ghostContentPath = &quot;/var/ghost/content&quot;;
services.tarsnap.enable = true;
services.tarsnap.config.ghost.directories = [ ghostContentPath ];
</code></pre>
<p>Those three lines enable tarsnap daily backups for this blog!</p>
<p>There&apos;s lots more goodies like this in the <a href="https://nixos.org/nixos/manual/">NixOS Manual&apos;s</a> <a href="https://nixos.org/nixos/manual/ch-options.html">Appendix</a>.</p>
<h2 id="nixops">NixOps</h2>
<p>Finally, there&apos;s a tool that ties this all into the cloud.  This makes it easy to spin up an EC2 or GCE instance that matches that specification.  Or you can build a template and stamp out a dozen different instances, with each one parameterized slightly differently.  When the machine specifications are just expressions in a programming language, they can be copied, modified and instantiated easily.</p>
<p>Deploying changes is easy, too.  Just update the config files and run <code>nixops deploy</code>.  The program will take care of starting or stopping machines, installing packages, and restarting services.</p>
<p>At this point, it starts to encroach on Puppet and Chef&apos;s territory.  Which is a good thing, since those tools seem to be regarded as (at best) a necessary evil.</p>
<h2 id="myexperience">My Experience</h2>
<p>The down-side is that actually making these packages can be a bit of a pain.  It&apos;s a young project.  There&apos;s very little documentation or examples, so you&apos;re on your own once you&apos;ve strayed from the well-traveled path.  The Nix interpreter&apos;s error messages are often confusing.  Perhaps worst of all, most editors don&apos;t have syntax highlighting for <code>.nix</code> files yet.</p>
<p>The reason it took me so long to build this site is that I had to build a bunch of my packages from scratch without much help from documentation.</p>
<p>Most of my work is too crude to be upstreamed into the nixpkgs tree, but I can still post it online.  Maybe this can save someone else some time.</p>
<p><a href="https://github.com/richardlarocque/nix-configs">https://github.com/richardlarocque/nix-configs</a></p>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>