<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>About on rnewman</title>
    <link>/</link>
    <description>Recent content in About on rnewman</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <copyright>Richard Newman. All rights reserved.</copyright>
    <lastBuildDate>Tue, 15 Jul 2025 00:00:00 +0000</lastBuildDate><atom:link href="/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Removing downloaded macOS text to speech voices</title>
      <link>/post/software/2026/remove-macos-voices/</link>
      <pubDate>Sat, 14 Mar 2026 00:00:00 +0000</pubDate>
      
      <guid>/post/software/2026/remove-macos-voices/</guid>
      <description>&lt;p&gt;I recently played around with using &lt;a href=&#34;https://github.com/dokterbob/macos-speech-server&#34;&gt;&lt;code&gt;macos-speech-server&lt;/code&gt;&lt;/a&gt; for STT/TTS in Home Assistant. When experimenting with &lt;code&gt;avspeech&lt;/code&gt;, the macOS-native speech engine, I discovered that macOS 26 no longer allows you to delete voice packs after installing them — the management UI is gone, and the advice online about swiping in the list doesn&amp;rsquo;t work.&lt;/p&gt;
&lt;p&gt;At 100–500MB per voice, these add up.&lt;/p&gt;
&lt;p&gt;Unfortunately, deleting the files from &lt;code&gt;/System/Library/AssetsV2/&lt;/code&gt; is blocked by system integrity protection: even &lt;code&gt;sudo&lt;/code&gt; can&amp;rsquo;t do it.&lt;/p&gt;
&lt;p&gt;Fortunately these are &amp;ldquo;mobile assets&amp;rdquo;, so there&amp;rsquo;s a macOS framework to manage them. Claude and I pulled together a Swift script that uses that framework to list the voice assets and let you delete the ones you no longer want. I put it &lt;a href=&#34;https://github.com/rnewman/remove-macos-voices&#34;&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>The incident spiral</title>
      <link>/post/software/2026/incident-spiral/</link>
      <pubDate>Tue, 13 Jan 2026 00:00:00 +0000</pubDate>
      
      <guid>/post/software/2026/incident-spiral/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve watched this pattern unfold more than twice, so it&amp;rsquo;s time to write it down.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;A team has a quality problem. Reliance on unreliable partners, technical debt after a couple of years of fast growth, accrued complexity — whatever the causes, we&amp;rsquo;ve become aware that things aren&amp;rsquo;t just humming along.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The team starts taking incidents more seriously. A new leader joins, or senior leadership puts the team under a microscope. The intent is good.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Incident retrospectives become more thorough; these take time. Each retro produces more action items, which take time to address. Some action items improve detection, so on-call engineers get paged more, and we open more incidents to track. The team is now spending 20–40% of its engineering bandwidth on incidents, retros, and action items.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Meanwhile, the team is expected to keep shipping features and absorbing company-wide changes. Those changes themselves increase the surface area for things to go wrong. Running at 60% capacity means corners are cut on feature work and people are tired. More incidents.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is a bad situation: the more the team tries to meet expectations, by shipping features and taking incidents seriously, the worse things get.&lt;/p&gt;
&lt;h2 id=&#34;action-items-are-a-trap&#34;&gt;Action items are a trap&lt;/h2&gt;
&lt;p&gt;In most companies, incident action items bypass normal prioritization. A P1 comes with a one-week deadline and supersedes other work for whichever engineer is assigned the ticket. This is &lt;strong&gt;by design&lt;/strong&gt;, because, without that deadline and that accountability, the backlog of action items will keep getting punted until after the next sprint, when the cold light of day shines on the list of things you told your product manager you&amp;rsquo;d get done. You sat in a meeting and said these things were &lt;em&gt;necessary&lt;/em&gt; to prevent recurrence, accelerate detection, help with mitigation, or reduce blast radius, right?&lt;/p&gt;
&lt;p&gt;But consider the tradeoff that occurs in practice. Teams are always balancing feature and reliability work, and some of that planned reliability work might matter more than any incident action item.&lt;/p&gt;
&lt;p&gt;A P1 action item for a minor incident might preempt a &lt;em&gt;planned&lt;/em&gt; project to deprecate a problematic system or remove a single point of failure.&lt;/p&gt;
&lt;p&gt;The incident action item addresses a failure mode we&amp;rsquo;ve just seen. The planned work might avoid a catastrophic failure that we haven&amp;rsquo;t yet seen. We prioritize the former because it&amp;rsquo;s vivid. We&amp;rsquo;ve been lucky on the latter — so far.&lt;/p&gt;
&lt;p&gt;Lorin Hochstein captures this with a thought experiment he calls the &lt;a href=&#34;https://surfingcomplexity.blog/2023/12/22/the-courage-to-imagine-other-failures/&#34;&gt;Oracle of Delphi&lt;/a&gt;. Imagine an oracle tells you that if you do an incident&amp;rsquo;s follow-up work, you&amp;rsquo;ll avoid a recurrence… but you&amp;rsquo;ll suffer a novel eight-hour outage next month. If you do the reliability work that was already on your backlog, you&amp;rsquo;ll have another minor incident like the one you just had, but avoid the big one. Which do you choose?&lt;/p&gt;
&lt;p&gt;Creating a P1 or P2 incident action item is a statement that this is the most important thing you can work on this week, based on an implicit assumption that the &lt;strong&gt;last&lt;/strong&gt; incident is a strong predictor of future reliability issues. That assumption is often wrong. You were surprised before. You&amp;rsquo;ll be surprised again.&lt;/p&gt;
&lt;h2 id=&#34;the-complexity-that-was-supposed-to-help&#34;&gt;The complexity that was supposed to help&lt;/h2&gt;
&lt;p&gt;There&amp;rsquo;s a related problem with the &lt;em&gt;kind&lt;/em&gt; of action items that retros produce. The natural response to an incident is to add something: a check, a cache, a retry, a fallback, a circuit breaker. Each is reasonable in isolation, and they&amp;rsquo;re easy for folks on the periphery of a system to imagine and propose as action items. Over time, they accumulate.&lt;/p&gt;
&lt;p&gt;Hochstein has a &lt;a href=&#34;https://surfingcomplexity.blog/2017/06/24/a-conjecture-on-why-reliable-systems-fail/&#34;&gt;conjecture&lt;/a&gt; about this: once a system reaches a certain level of reliability, most major incidents will involve either a manual intervention intended to mitigate a minor incident, or unexpected behavior of a subsystem whose primary purpose was to improve reliability.&lt;/p&gt;
&lt;p&gt;This isn&amp;rsquo;t theoretical. The &lt;a href=&#34;https://surfingcomplexity.blog/2025/12/14/aws-reinvent-talk-on-their-oct-25-incident/&#34;&gt;October 2025 AWS outage&lt;/a&gt; involved an unanticipated interaction between multiple reliability mechanisms: redundant enactor instances, a locking mechanism, a cleanup mechanism, a transactional mechanism, and a rollback mechanism. All sensible design decisions, but the incident emerged from their interaction. The &lt;a href=&#34;https://surfingcomplexity.blog/2025/12/06/quick-takes-on-the-dec-5-cloudflare-outage/&#34;&gt;December 2025 Cloudflare outage&lt;/a&gt; was triggered by a killswitch — a mechanism specifically designed to quickly disable misbehaving rules — that had worked well in the past but failed in a corner case.&lt;/p&gt;
&lt;p&gt;Caches, retries, and bimodal fallback paths are all common contributors to incidents. I see them proposed as action items every week.&lt;/p&gt;
&lt;p&gt;This doesn&amp;rsquo;t mean reliability mechanisms are bad. We need retries, timeouts, bulkheading, failovers, rate limiting, caches, circuit breakers, and all the rest. Complexity is inevitable, and indeed we must learn to ‘surf’ it, as Hochstein puts it. However, we must also continually work to &lt;strong&gt;simplify&lt;/strong&gt; to make room for the new complexity we&amp;rsquo;re adding.&lt;/p&gt;
&lt;h2 id=&#34;breaking-out&#34;&gt;Breaking out&lt;/h2&gt;
&lt;p&gt;The urge to take incidents seriously is correct. The problem is that when every incident generates two weeks of follow-up work, the team can&amp;rsquo;t keep up. Prevention competes with response, and both compete with the roadmap.&lt;/p&gt;
&lt;p&gt;Here are some approaches I&amp;rsquo;ve found helpful to reduce the burden and increase agency, starting with the two I think matter most.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Predictable external failures shouldn&amp;rsquo;t be incidents.&lt;/strong&gt; One airline being unreachable isn&amp;rsquo;t an incident for a travel booking site. One restaurant being unexpectedly closed isn&amp;rsquo;t an incident for Uber Eats. Some failure modes exist by design — they will happen, and the system should handle them gracefully, informing the user as appropriate. (To do this right you might need to monitor external dependencies with synthetics, rather than just watching error rates.)&lt;/p&gt;
&lt;p&gt;This doesn&amp;rsquo;t mean ignoring these failures. Track them. Build dashboards. Set up weekly reviews to spot trends. If a partner&amp;rsquo;s error rate doubles, that&amp;rsquo;s a conversation to have with the partner, or a reason to reconsider the integration. But don&amp;rsquo;t page someone at 2am.&lt;/p&gt;
&lt;p&gt;You can achieve similar benefits by weakening guarantees: for example, retries, job queues, dead-letter queues, and so on are all mechanisms to turn failures into latency. If your users are OK with latency, you can use those mechanisms to make your system more resilient.&lt;/p&gt;
&lt;p&gt;You might make a different decision here if you have a lot of influence or control over the partner or destination, if the partner is fungible, if there&amp;rsquo;s something you can do to unblock your own customer &lt;em&gt;in extremis&lt;/em&gt;, or if you want to actively track these failures. However, I think this point will be immediately recognizable to teams trapped in a particular bad state.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Be more discriminating about action items.&lt;/strong&gt; Action items should fill urgent gaps in detection or ability to mitigate, fix obvious bugs, and prevent cascading failures. Not every incident needs a code change. Good action items should address a &lt;em&gt;class&lt;/em&gt; of incidents, not just the specific failure you observed.&lt;/p&gt;
&lt;p&gt;Almost everything else you can think of, particularly migrations and rewrites, should be added to the backlog to be weighed against the other reliability work you were already planning.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m fond of asking in retros &lt;a href=&#34;./post/software/2024/an-incident-lens&#34;&gt;the questions I learned at Amazon&lt;/a&gt;. They&amp;rsquo;re simple, but they nudge you into taking different perspectives and generalizing to a class of failures: what would have halved your time to detection? What would have halved your time to recover? What would have halved the blast radius?&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s also useful to ask what would halve the &lt;strong&gt;cost&lt;/strong&gt; of this class of incident, because achieving that can help us break out of the spiral. Sometimes the answer is “nothing practical, and we accept this will happen occasionally” — that might be better than doing a bunch of work just to feel like you&amp;rsquo;re responding to the incident. If you&amp;rsquo;re never declining action items, you&amp;rsquo;re not being selective enough.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;After an incident, look for complexity to remove, not just safeguards to add.&lt;/strong&gt; We should be skeptical of action items that add new things that can interact and fail. After an incident, ask: what could we &lt;em&gt;remove&lt;/em&gt; to make this simpler? Could we eliminate the dependency that failed, rather than wrap it in more error handling? Can we make two things &lt;a href=&#34;https://en.wikipedia.org/wiki/Fate-sharing&#34;&gt;share fate&lt;/a&gt;? Could we drop a feature that isn&amp;rsquo;t worth its operational cost?&lt;/p&gt;
&lt;p&gt;Asking simplifying questions &lt;em&gt;all the time&lt;/em&gt; is something I expect of staff engineers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Be honest about staffing.&lt;/strong&gt; A team spending 30% of its time on incidents might be understaffed for its scope. Sometimes the answer is “we can&amp;rsquo;t operate this much surface area with this many people.” That&amp;rsquo;s uncomfortable to say, but the alternative — denial, leading to degraded quality, burnout, and attrition — is worse.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Make the cost visible with an incident budget.&lt;/strong&gt; Allocate a fixed percentage of engineering time — say, 15% — to incident work. When that&amp;rsquo;s exhausted, remaining action items compete with features through normal prioritization. This makes the cost visible, forces explicit tradeoffs, and creates pressure to make incidents cheaper.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;None of these changes will fix everything overnight, but they might break the feedback loop.&lt;/p&gt;
&lt;p&gt;Engineering needs to be sustainable. A team drowning in incidents can&amp;rsquo;t think strategically or make deliberate choices. The first step is to reclaim the ability to plan, then use that ability to chart a course towards holistic reliability.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>An incident lens</title>
      <link>/post/software/2024/an-incident-lens/</link>
      <pubDate>Sun, 25 Aug 2024 00:00:00 +0000</pubDate>
      
      <guid>/post/software/2024/an-incident-lens/</guid>
      <description>&lt;p&gt;I first started owning the operational posture of modern (SaaS, microservices, cloud, &lt;em&gt;etc&lt;/em&gt;.) services in 2018, when I joined AWS.&lt;/p&gt;
&lt;p&gt;With service ownership came incidents (and &lt;a href=&#34;https://wa.aws.amazon.com/wat.concept.coe.en.html&#34;&gt;CoEs&lt;/a&gt;!), and the ensuing six years built my awareness of availability and formed the lenses I use in incident retrospectives. Those lenses now shape the advice I give.&lt;/p&gt;
&lt;p&gt;Last week&amp;rsquo;s example: a senior engineer wanted to improve the deployment processes used by a nascent service. It is today manually deployed to a shared testing environment and a production environment, usually in order, once per week.&lt;/p&gt;
&lt;p&gt;The first thought of the engineer and his manager was that we should build a &amp;lsquo;stable&amp;rsquo; pre-production environment — let&amp;rsquo;s call it &lt;em&gt;pilot&lt;/em&gt; for the sake of discussion — avoiding contention in the testing environment. Changes would bake there for a while before flowing to prod. Various additional suggestions, most of them good, built on that: make deployments include only a single change, so we know what broke; build a pipeline that a person would promote instead of running Terraform, and so on.&lt;/p&gt;
&lt;p&gt;These are all worth doing! But from an incident perspective, we tend to ask some very simple questions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;How did you know something went wrong?&lt;/li&gt;
&lt;li&gt;How long did it take you to find out?&lt;/li&gt;
&lt;li&gt;How big was the impact?&lt;/li&gt;
&lt;li&gt;How long did it take you to recover?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In a retrospective, we&amp;rsquo;ll rotate these questions a bit:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;How would you have halved the time to detection?&lt;/li&gt;
&lt;li&gt;How would you have halved the time to recovery?&lt;/li&gt;
&lt;li&gt;How would you have halved the blast radius?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Thinking in this mindset led to my advice: there is little point in building a pilot environment or a release pipeline unless you can (a) reliably roll back deployments, (b) detect bustage in prod quickly enough to automatically decide to roll back, and (c) roll back quickly enough, and/or deploy to small enough populations, to minimize how many users are affected, depending on the maturity level and SLA of your service.&lt;/p&gt;
&lt;p&gt;We have no automatic rollback other than ECS blue-green deploys, and while we have alarms on service metrics, many endpoints take little to no traffic. (I&amp;rsquo;ve seen this situation in a few places: uncommon endpoints take no traffic, so they can easily regress without being detected in any reasonable rollback window.)&lt;/p&gt;
&lt;p&gt;Introducing a pilot environment would only give us a false sense of security, or even make things worse by improving developer productivity: we&amp;rsquo;d deploy broken changes, bake them without detecting any problems, then roll out the broken change to 100% of users in prod. Maybe we&amp;rsquo;d even watch the dashboards, and not see any problem for a week or two!&lt;/p&gt;
&lt;p&gt;The need to detect bustage before it affects lots of users, and roll back quickly, dictates the roadmap I suggested:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Build synthetic tests (&amp;ldquo;canaries&amp;rdquo;) to ensure a volume of traffic that mimics real usage on every API (&amp;ldquo;which API endpoints are you willing to break without noticing?&amp;rdquo;).&lt;/li&gt;
&lt;li&gt;Build detectors on those canaries and service metrics to fire alarms. Any bustage should be detectable. Now your time-to-detection is low.&lt;/li&gt;
&lt;li&gt;Build a pipeline that rolls back if those alarms fire. Now your time-to-recovery for simple bugs is low.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Then&lt;/em&gt; build a pilot stage to serve as a smaller &amp;lsquo;canary&amp;rsquo; population. This stage bakes a release before it goes out. Now you have reduced the blast radius of a bad change, introducing a place to run canaries and real internal users to generate novel traffic.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Other concerns, like smaller rapid deployments, are secondary to these. They might be important — perhaps developer velocity is hampered by the shared testing environment — but I&amp;rsquo;ve found that bad deployments are themselves a large source of friction, as well as being bad for the customer.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Camellia oil: the ideal shave</title>
      <link>/post/life/2023/camellia-oil/</link>
      <pubDate>Sat, 05 Aug 2023 00:00:00 +0000</pubDate>
      
      <guid>/post/life/2023/camellia-oil/</guid>
      <description>&lt;p&gt;Like many, I have a drawer half-full of part-used shaving soaps, brushes, and so on. Over the last year I began pursuing simpler solutions that would travel well, and had settled on the smallest soap sticks I could find: &lt;a href=&#34;https://amzn.to/3s1WVWX&#34;&gt;Speick&lt;/a&gt; and &lt;a href=&#34;https://amzn.to/3OpMxQ9&#34;&gt;La Toja&lt;/a&gt; with a neat &lt;a href=&#34;https://muhleshaving.com/products/travel-black-anodized-aluminum-silvertip-fiber-travel-shaving-brush&#34;&gt;Mühle travel brush&lt;/a&gt;. I began using the sticks at home, too. It&amp;rsquo;s less hassle to lather on my face than in a bowl.&lt;/p&gt;
&lt;p&gt;A few months ago I was planning a five-day backpacking trip, and realized that I often end up with an itchy neck by day four. I needed an option that would be suitable for lightweight or ultralight backpacking: I didn&amp;rsquo;t want to carry soap and a brush, and ideally wouldn&amp;rsquo;t need to thoroughly heat water, lather, and clean up just to shave.&lt;/p&gt;
&lt;p&gt;Shave oil was the obvious option. But every brand I looked at was the same: packed to the gills with scents and botanicals, heavy on the menthol, and as much as $20 for a tiny bottle. I tried Somerset&amp;rsquo;s &amp;ldquo;Extra Sensitive Shaving Oil&amp;rdquo; (£11.50 for 35ml) and I have to wonder what the regular version is like — the &amp;ldquo;extra sensitive&amp;rdquo; was heavily scented and made my skin vibrate with menthol.&lt;/p&gt;
&lt;p&gt;I tried shaving with some common cosmetic ingredients (&lt;em&gt;e.g.&lt;/em&gt;, squalane) and did not get a good shave. I didn&amp;rsquo;t want to use a typical food oil that would go rancid or attract bears in the backcountry, so I didn&amp;rsquo;t try olive or nut oil.&lt;/p&gt;
&lt;p&gt;Eventually it occurred to me that I had a big bottle of stable, safe, non-food oil sitting on my shelf. I gave it a try, and enjoyed a bafflingly good shave: smooth, unscented, nick-free. The only downside is one shared with all shaving oils, a tendency to leave clumps of oil and stubble in the sink.&lt;/p&gt;
&lt;p&gt;The oil I settled on is cold-pressed camellia oil (tsubaki abura), the oil of &lt;em&gt;camellia japonica&lt;/em&gt;. &lt;a href=&#34;https://amzn.to/3OmB1oH&#34;&gt;A 100ml bottle is $10&lt;/a&gt;. I have been using it for years to oil carbon steel kitchen knives, which is its traditional use, along with machine lubrication (&lt;em&gt;e.g.&lt;/em&gt;, sewing machines). It&amp;rsquo;s tasteless, odorless, food-safe, and non-drying (all of which are good for kitchen knives!). Apparently it&amp;rsquo;s also non-comedogenic and used in skincare.&lt;/p&gt;
&lt;p&gt;Six drops is more than sufficient to shave my entire face. I put some in a little dropper bottle for travel, and I&amp;rsquo;ve also been using it at home, because it&amp;rsquo;s fast, convenient, and gives just as good a shave as careful prep and luxurious lather.&lt;/p&gt;
&lt;p&gt;For carry-on travel I pair the little dropper bottle of oil with a &lt;a href=&#34;https://amzn.to/3Ktow9H&#34;&gt;Bic Metal disposable razor&lt;/a&gt;, which is the simplest and best TSA-safe razor I&amp;rsquo;ve found. When I&amp;rsquo;m checking a bag I take my &lt;a href=&#34;https://www.italianbarber.com/products/razorock-game-changer-84-p-double-edge-razor&#34;&gt;RazoRock Game Changer 0.84-P&lt;/a&gt; with a &lt;a href=&#34;https://amzn.to/3rWHHCy&#34;&gt;little sliding metal tin&lt;/a&gt; of &lt;a href=&#34;https://amzn.to/47hwInt&#34;&gt;Gillette Nacet blades&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As pictured, the Bic Metal weighs &lt;strong&gt;5g&lt;/strong&gt;, and the half-full dropper bottle is &lt;strong&gt;9.4g&lt;/strong&gt;. You could probably get that lower by using a different dropper bottle.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;./img/2023-08-razors.jpeg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;A total outlay of $10 in oil might be enough for me to shave at home and on the road for my entire life. Shaves at home with the Nacet blades cost me about 7¢. Shaves on the road with the Bic Metal cost about 25¢.&lt;/p&gt;
&lt;p&gt;For backpacking and travel the oil also serves as an impromptu lubricant for squeaky gear that folds or turns, and as a skin emollient, but it&amp;rsquo;s small and light enough that it doesn&amp;rsquo;t need to be multipurpose.&lt;/p&gt;
&lt;p&gt;My goal with a lot of gear is to get to a point of simply not having to think about it: to have found a solution that&amp;rsquo;s robust, reliable, and works so well that I stop looking for anything better. I have reached that point with this kit.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Tenets</title>
      <link>/post/software/2022/tenets/</link>
      <pubDate>Fri, 21 Oct 2022 00:00:00 +0000</pubDate>
      
      <guid>/post/software/2022/tenets/</guid>
      <description>&lt;p&gt;I learned a lot during my time at AWS. One thing that I haven’t seen discussed much is how documented tenets can be used to structure a team’s path.&lt;/p&gt;
&lt;p&gt;Tenets in this context are documented principles and beliefs that a team holds about itself, its users, and its products. They can be a useful guide for decision-making, and even the process of defining and phrasing the team’s tenets — and the ruthless prioritization needed to hit the expected limit of 5–7 — brings tremendous clarity. As with planning, the value is largely in the process, not the outcome!&lt;/p&gt;
&lt;p&gt;Tenets can be set at the team level, the service level, or for smaller efforts and scopes that would benefit from directional clarity.&lt;/p&gt;
&lt;p&gt;Writing good tenets is hard, and even tenured leaders often misunderstand the concept, losing some of the benefits.&lt;/p&gt;
&lt;p&gt;A bad tenets list reads like a statement of values, a wishlist, simple priorities, or untethered aspirations. Our product will be fast, cheap, and available everywhere.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.factoftheday1.com/p/tenets-at-amazon-a2bb8a56ae94&#34;&gt;This blog post&lt;/a&gt; publishes some internal Amazon tenets (see the w.amazon.com links!).&lt;/p&gt;
&lt;p&gt;A good example is from IoT:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Necessary offline operations: We help customers build systems in the cloud that work in predictable ways when connectivity is limited.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The point of writing up tenets is not to capture something that&amp;rsquo;s unambiguous or uncontested (&lt;em&gt;products should be fast&lt;/em&gt;, &lt;em&gt;the team should have a high bar for quality&lt;/em&gt;), nor to capture something that&amp;rsquo;s easily defined in some process doc or as a metric and thus does not require much human judgment. It&amp;rsquo;s to define a mental model for coping with conflicting priorities, to define a position in an exclusive value space, or to provide a mast to which a team under fire can tie themselves.&lt;/p&gt;
&lt;p&gt;I’m sure it would be very convenient to tie some IoT capabilities to a network connection. Making a system work reliably with limited connectivity is hard and must be designed in from the start; it’s something that customers need help with; and it’s a requirement that engineering teams would love to drop! This tenet encodes an &lt;em&gt;intention&lt;/em&gt; that steers decisions towards the inconvenient choice that’s right for customers.&lt;/p&gt;
&lt;p&gt;Tenets like this are a statement in advance, while we still have the space and presence of mind to use good judgment, that we won’t do the expedient thing. It is a form of &lt;a href=&#34;https://effectiviology.com/precommitment/&#34;&gt;precommitment&lt;/a&gt; — the corporate equivalent of setting out your workout clothes the night before, because you know you won’t feel like exercising in the morning.&lt;/p&gt;
&lt;p&gt;Accessibility is another good example. It&amp;rsquo;s important, it has top-down guidance in many product orgs, and yet teams will routinely sacrifice accessibility (and localization!) in order to hit a deadline, saying &amp;ldquo;yeah, well, accessibility is important, but of course we have to weigh it against shipping features…&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;A weak tenet like &lt;em&gt;accessibility is important&lt;/em&gt; is strengthened by making it definite — &lt;em&gt;our releases are always accessible to users of assistive technologies&lt;/em&gt; — or make the tradeoffs explicit: &lt;em&gt;we prioritize accessibility over time to market, and will delay a release that doesn’t serve all of our customers&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;A good tenet can capture discomfort and interaction patterns, too: &lt;em&gt;we avoid excluding our remote teammates by making decisions asynchronously and with time for thoughtful comments, rather than in synchronous meetings&lt;/em&gt;. Again, there’s a tradeoff captured: that the team will go slower in order to be inclusive and make more measured decisions.&lt;/p&gt;
&lt;p&gt;Other tenets can express an opinionated stance about product-market fit: &lt;em&gt;our users are sophisticated and prefer detailed, accurate docs rather than vague marketing materials&lt;/em&gt;, or &lt;em&gt;we accommodate our industry&amp;rsquo;s long sales cycles by treating trial users with the same priority as sold users&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;At a leadership offsite my team tried an exercise: gather in small groups and write what you think the whole org’s tenets should be, then come together and discuss. The outcome was educational, if not surprising: many newer leaders wrote bad tenets; many wrote tenets that focused on the importance of their teams’ efforts, rather than the breadth of the business and our coordinated long-term success; and few wrote tenets that surfaced the essential tensions between their own priorities and those of other managers. This exercise is time well spent if you can spare an hour, but be prepared to work through the disagreements about what really matters for your teams!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Writing for an audience</title>
      <link>/post/software/2022/writing-for-an-audience/</link>
      <pubDate>Fri, 21 Oct 2022 00:00:00 +0000</pubDate>
      
      <guid>/post/software/2022/writing-for-an-audience/</guid>
      <description>&lt;p&gt;Many Stripes are in the habit of writing &amp;ldquo;5:15s&amp;rdquo;: short updates that take fifteen minutes to write and five to read. My own staff notes are infrequent, on the order of every month or two.&lt;/p&gt;
&lt;p&gt;In part that is because I find it hard to find the time to digest my week into a useful narrative. If you told 25-year-old me how many hours of meetings I would have each week, and how much time I spend on Slack, he wouldn’t have believed you, and would have gone back to writing code. (First he would have asked you what Slack was!) The raw dump would be overwhelming and unnavigable.&lt;/p&gt;
&lt;p&gt;In part it’s because I spend a lot of time constructing custom-fit narratives for individuals or small groups, trying to serve their needs by shaping my experiences and perspectives into a parable and an account that succinctly gives them what they need for the situation in which they find themselves. A mentee recently apologized for presenting so many questions and problems for me to respond to, and I laughed — this is the most fun part of my job!&lt;/p&gt;
&lt;p&gt;Mostly, though, it’s because I am acutely aware that a broadcast narrative — whether that’s an email, a project review update, a 5:15, or a conference talk — is a tool for changing an audience’s perspective, and getting that right requires both understanding the audience and providing (or assuming) enough context that the message can be practically brief. Often the context cannot feasibly be communicated, either because it is too early and unstable, or because it is so impractically large that the audience will extract no net value from what I have to say.&lt;/p&gt;
&lt;p&gt;That leaves me where I am today: with occasional abstract updates.&lt;/p&gt;
&lt;p&gt;Those of you who succeed at broader notes: I would love to hear your perspectives and ideas. Those who read these abstract notes: I am curious whether you find them valuable, or would prefer something more concrete (or different entirely).&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>First month at Stripe</title>
      <link>/post/software/2022/first-month-at-stripe/</link>
      <pubDate>Sun, 06 Feb 2022 00:00:00 +0000</pubDate>
      
      <guid>/post/software/2022/first-month-at-stripe/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m approaching the end of my first month at Stripe. I didn&amp;rsquo;t announce where I was landing &lt;a href=&#34;./post/software/2022/back/&#34;&gt;in my earlier post&lt;/a&gt; out of a sense of counting my chickens, so I&amp;rsquo;m glad to change that!&lt;/p&gt;
&lt;p&gt;It is a strange and amusing shift to go from working at a bookshop with a climate pledge to working at a &lt;a href=&#34;https://press.stripe.com/&#34;&gt;publisher&lt;/a&gt; with a &lt;a href=&#34;https://stripe.com/climate&#34;&gt;carbon sequestration arm&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After more than three years at AWS I was quite settled on my team, surrounded by people I knew well, and aware of the patterns swirling at various levels. I was also aware of my pace of learning having slowed, and I was tired of managing my cognitive dissonance. While I miss the people, the Principal Engineering community, and some unique aspects of my role, and I&amp;rsquo;m grateful for how much I had the opportunity to learn, I feel good about my decision to leave.&lt;/p&gt;
&lt;p&gt;I also feel good so far about my decision to join Stripe: even as a relatively large startup, there is still a coherence of vision and some agility, and it really feels like we are all pulling in roughly the same direction. The people are kind, and that&amp;rsquo;s even expressed in the benefits decisions that management makes, like explicitly giving emergency medical and caring leave this year — too many of my friends at Amazon burned through PTO due to surprise COVID or caring responsibilities. No place is perfect, but so far I can live with the imperfections.&lt;/p&gt;
&lt;p&gt;My first weeks at Stripe have been a firehose, as one might imagine: simultaneously learning a new domain (finance and payments), how Stripe&amp;rsquo;s businesses fit in to that, the technologies we use, the work that&amp;rsquo;s beginning, and the people. Stripe has a very thorough &amp;ldquo;101&amp;rdquo; onboarding program, but I quite quickly (too quickly?) ramped up on some real work, and I feel like I accidentally struck a good balance: after 19 working days I already have a web of relationships with my onboarding cohort, my new teams, and other staff engineers in my area, and I&amp;rsquo;m starting to develop the intuitions and questioning/routing behaviors that staff engineers use to do our jobs.&lt;/p&gt;
&lt;p&gt;More importantly, I have managed to strike a good balance of time. Meetings happen 9–3 Pacific to avoid impacting east coasters, and Wednesdays are meeting-free for makers. I stop at 4 or 5, depending on when I started my day. Working at Amazon was always described to me as &amp;ldquo;intense&amp;rdquo;. So far Stripe is also intense, but bounded and mindfully so: everyone has given me advice to make sure that I don&amp;rsquo;t take on too much.&lt;/p&gt;
&lt;p&gt;We have ambitious goals for the next twelve months, so it&amp;rsquo;s a relief to find that people view that as a long hike, not as a deathmarch. Onwards!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Tip: Synergy lag/jerking on macOS</title>
      <link>/post/life/2022/tip-synergy/</link>
      <pubDate>Mon, 10 Jan 2022 00:00:00 +0000</pubDate>
      
      <guid>/post/life/2022/tip-synergy/</guid>
      <description>&lt;p&gt;I recently switched from using &lt;a href=&#34;https://acrosscenter.com&#34;&gt;Across&lt;/a&gt; back to using &lt;a href=&#34;https://symless.com/&#34;&gt;Synergy&lt;/a&gt; for keyboard/mouse sharing.&lt;/p&gt;
&lt;p&gt;I noticed substantial jerkiness and latency when my mouse was on the remote machine — pauses of a few hundred milliseconds every couple of seconds.&lt;/p&gt;
&lt;p&gt;The culprit was AirDrop. Turning off AirDrop and closing AirDrop Finder windows allows silky smooth mousing.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>I&#39;m back; things changed</title>
      <link>/post/software/2022/back/</link>
      <pubDate>Thu, 06 Jan 2022 00:00:00 +0000</pubDate>
      
      <guid>/post/software/2022/back/</guid>
      <description>&lt;p&gt;As &lt;a href=&#34;./post/software/2021/a-long-pause/&#34;&gt;I wrote&lt;/a&gt; last year, I didn&amp;rsquo;t feel like I had the space to write for public consumption while performing my role at AWS, and I would be back when things changed.&lt;/p&gt;
&lt;p&gt;They have: I decided to leave AWS at the start of 2022.&lt;/p&gt;
&lt;p&gt;As is tradition I then spent a couple of days fiddling with blogging systems, migrating my posts out of Medium and recovering a couple of posts from an even older blog. This is the result.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t expect a particular cadence of writing, and I&amp;rsquo;m trying to not let the perfect be the enemy of good, but I&amp;rsquo;m optimistic that I will publish things now that I have an easy place to do so.&lt;/p&gt;
&lt;p&gt;More next week.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>A long pause</title>
      <link>/post/software/2021/a-long-pause/</link>
      <pubDate>Sat, 25 Sep 2021 00:00:00 +0000</pubDate>
      
      <guid>/post/software/2021/a-long-pause/</guid>
      <description>&lt;p&gt;Working at AWS is very different to working at smaller mission-driven companies like Mozilla.&lt;/p&gt;
&lt;p&gt;Even in projects where &lt;a href=&#34;https://github.com/aws/amazon-chime-sdk-js/issues?q=richnew10&#34;&gt;I get to think or work in the open&lt;/a&gt;, there is policy around communication that has a chilling effect on writing, as well as cultural pressure to use my time to write more leveraged &lt;a href=&#34;https://aws.amazon.com/blogs/business-productivity/transforming-audio-and-shared-content-in-the-amazon-chime-sdk-for-javascript/&#34;&gt;blog posts to serve the interests of the team&lt;/a&gt;. That means I don’t write here.&lt;/p&gt;
&lt;p&gt;This culture is in stark contrast to &lt;a href=&#34;./tags/medium/&#34;&gt;my pieces on Medium&lt;/a&gt; from 2018, my last year at &lt;a href=&#34;./tags/mozilla/&#34;&gt;Mozilla&lt;/a&gt;, in which I wrote to explain — and wrote &lt;em&gt;to think through&lt;/em&gt;! — two problems with which I have grappled for long stretches of my career: how different groups of people (individuals, teams, or even communities) can &lt;em&gt;reuse&lt;/em&gt; information that overlaps in space and vocabulary; and how different devices can &lt;em&gt;change&lt;/em&gt; shared data over time and space without the need for heavyweight central coordination.&lt;/p&gt;
&lt;p&gt;The AWS blog certainly attracts more readers than this little Medium, but it doesn’t serve those purposes of explaining and thinking through problems.&lt;/p&gt;
&lt;p&gt;In occasional bursts of free time I return to the topic of structured storage, whether by writing index chunk join code to understand an &lt;a href=&#34;https://arxiv.org/abs/1210.0481&#34;&gt;algorithm&lt;/a&gt;, or more recently by digging into &lt;a href=&#34;https://blueskyweb.org/satellite&#34;&gt;an identity challenge that is intrinsically decentralized&lt;/a&gt;, only to find &lt;a href=&#34;https://json-ld.org/spec/latest/json-ld/&#34;&gt;JSON-LD&lt;/a&gt; (&lt;a href=&#34;https://www.w3.org/2001/sw/wiki/RDF&#34;&gt;RDF&lt;/a&gt; in disguise!) hiding under the covers. Writing prose for public consumption, however, has fallen by the wayside.&lt;/p&gt;
&lt;p&gt;I’ll be back when things change.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Thinking about Syncing, Part 4: keeping track</title>
      <link>/post/software/storage/2018/thinking-about-syncing-4/</link>
      <pubDate>Thu, 31 May 2018 00:00:00 +0000</pubDate>
      
      <guid>/post/software/storage/2018/thinking-about-syncing-4/</guid>
      <description>&lt;p&gt;In &lt;a href=&#34;./post/software/storage/2018/thinking-about-syncing-3/&#34;&gt;Part 3&lt;/a&gt; we argued that the concerns of application code differ from those of synchronization code. In this part we will take a moment to explore the latter in more depth.&lt;/p&gt;
&lt;h3 id=&#34;the-needs-of-synchronization&#34;&gt;The needs of synchronization&lt;/h3&gt;
&lt;p&gt;In &lt;a href=&#34;./post/software/storage/2017/thinking-about-syncing-1/&#34;&gt;Part 1&lt;/a&gt; we discussed at a high level what a sync system has to do: move information around between its clients to allow them to reach agreement on the state of the world.&lt;/p&gt;
&lt;p&gt;More concretely, syncing storage systems need to consider &lt;strong&gt;equivalence&lt;/strong&gt;, management of &lt;strong&gt;identifiers&lt;/strong&gt;, management of &lt;strong&gt;change&lt;/strong&gt;, and detection of &lt;strong&gt;conflict&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id=&#34;management-of-identifiers&#34;&gt;Management of identifiers&lt;/h4&gt;
&lt;p&gt;Most systems need some way to identify &lt;em&gt;things —&lt;/em&gt; entities in the domain of discourse. These might be numeric identifiers, globally-unique random identifiers (UUIDs/GUIDs), or salted hashes of some key. Sometimes a system will use one kind of identifier internally and another to sync. The allocation, rewriting, or in-out mapping of these identifiers is a concern of sync systems.&lt;/p&gt;
&lt;h4 id=&#34;equivalence&#34;&gt;Equivalence&lt;/h4&gt;
&lt;p&gt;When multiple &lt;a href=&#34;./post/software/storage/2017/thinking-about-syncing-1/&#34;&gt;timelines&lt;/a&gt; can exist, it’s possible for the same conceptual entity to be identified by more than one name. Only systems in which every entity is given a stable identifier predictably derived from its unique attributes (such as a URL), or systems in which identity allocation is a centralized function, can avoid this.&lt;/p&gt;
&lt;p&gt;When more than one identifier exists for the same entity, they can be ‘smushed’ — one is replaced with the other — or mappings can be built.&lt;/p&gt;
&lt;h4 id=&#34;management-ofchange&#34;&gt;Management of change&lt;/h4&gt;
&lt;p&gt;‘Change’ is a blanket term. For our purposes, it consists of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Addition&lt;/strong&gt;: the introduction of a new fact into the system by one or more of its clients.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Update&lt;/strong&gt;: the assertion by a client that an old fact has been replaced by a new fact.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Retraction&lt;/strong&gt;: the assertion by a client that an old fact is no longer true.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deletion&lt;/strong&gt;: the forced un-stating of a fact so that it’s no longer part of any state, including any historical record.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expiration&lt;/strong&gt;: the pruning of old state, either on the individual client or system-wide, due to irrelevance. This is typically done to reduce storage footprint and improve speed. Expiration can result in clients diverging or introducing duplicate identifiers.&lt;/li&gt;
&lt;li&gt;The expansion or alteration of the ontology or schema of the system itself.&lt;/li&gt;
&lt;li&gt;A change in the set of syncing devices or the expected behavior of the system (&lt;em&gt;e.g.&lt;/em&gt;, turning a feature on or off).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Not only does the system need ways to model these things, but it also needs to keep track of ordering and progress: the same change shouldn’t be applied more than once, no matter the topology of the system.&lt;/p&gt;
&lt;h4 id=&#34;detection-ofconflict&#34;&gt;Detection of conflict&lt;/h4&gt;
&lt;p&gt;When two changes conflict, the conflict should be detected and some resolution should be reached without data loss.&lt;/p&gt;
&lt;p&gt;Beyond these concerns, we can list some requirements that we would like a real-world, practical sync system to meet.&lt;/p&gt;
&lt;h4 id=&#34;consistency-in-the-databasesense&#34;&gt;Consistency (in the database sense)&lt;/h4&gt;
&lt;p&gt;The state that the system stores and syncs should remain consistent with some expected properties — &lt;em&gt;e.g.&lt;/em&gt;, required fields should be present and of the correct types. A change that breaks consistency must fail.&lt;/p&gt;
&lt;h4 id=&#34;eventual-consistency-in-the-distributed-systemsense&#34;&gt;Eventual consistency (in the distributed system sense)&lt;/h4&gt;
&lt;p&gt;All clients in the system should converge on the same end state.&lt;/p&gt;
&lt;h4 id=&#34;quiescence&#34;&gt;Quiescence&lt;/h4&gt;
&lt;p&gt;When no client is introducing new changes, no significant activity should occur: we expect the system to rapidly stop exchanging data once conflicts have been resolved.&lt;/p&gt;
&lt;h4 id=&#34;atomicity&#34;&gt;Atomicity&lt;/h4&gt;
&lt;p&gt;It should not be possible for other clients to see only part of another client’s changes, because it makes maintaining correctness more difficult.&lt;/p&gt;
&lt;h4 id=&#34;incrementality&#34;&gt;Incrementality&lt;/h4&gt;
&lt;p&gt;Adding new facts to existing entities (&lt;em&gt;e.g.&lt;/em&gt;, adding creation dates to all of your bookmarks) should not require disproportionate work to be done by clients.&lt;/p&gt;
&lt;p&gt;Adding new entities should not require disproportionate work, even if those entities are related to other entities. (&lt;em&gt;E.g.&lt;/em&gt;, adding a new history visit should not require re-uploading the title and URL and previous visits of the history item.)&lt;/p&gt;
&lt;p&gt;Adding new clients to the set should not require disproportionate work from other clients.&lt;/p&gt;
&lt;h4 id=&#34;continuation-ofservice&#34;&gt;Continuation of service&lt;/h4&gt;
&lt;p&gt;Ordinary changes — data additions, schema extensions, client additions, etc. — should be routine and low-impact. We don’t want to lock out clients, force upgrades, or lose data as a result of a minor change; doing so harms the user experience and adds friction to engineering. When things are working they should continue to work.&lt;/p&gt;
&lt;h3 id=&#34;a-modestproposal&#34;&gt;A modest proposal&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Applications must design for syncability when considering identifiers. We cannot avoid thinking about uniqueness, what constitutes an identifier, when they can change, how we refer to entities (which is, after all, the point of having an identifier!), and when two entities should be considered the same… and what we should do when that occurs. &lt;code&gt;INTEGER PRIMARY KEY AUTOINCREMENT&lt;/code&gt; will end in tears.&lt;/li&gt;
&lt;li&gt;If the application must support disconnected operation and the other constraints discussed earlier, then it should use a log-structured store to allow for automatic conflict-detecting sync. Application code should be prepared to resolve detected conflicts.&lt;/li&gt;
&lt;li&gt;If the application’s data is relational — and choosing to model it in a non-relational tool doesn’t change this fact! — then we must think about how identification and constraints work in our domain. If a non-relational store is used, a relational layer will accrete on top, and abstractions leak. (Sometimes they are &lt;a href=&#34;https://hackernoon.com/leaky-by-design-7b423142ece0&#34;&gt;leaky by design&lt;/a&gt;.)&lt;/li&gt;
&lt;li&gt;Use events to record data if you can. Derive more narrow representations from the event-shaped, log-structured data. But even event-shaped data has a schema, and storage should make that easy to evolve.&lt;/li&gt;
&lt;li&gt;Use event-shaped APIs to separate storage representation and concerns from the application layer. &lt;code&gt;setBookmarked(url, title, true)&lt;/code&gt; is worse than &lt;code&gt;addBookmark(url, title, timestamp)&lt;/code&gt; . &lt;code&gt;didClickStar(page, context)&lt;/code&gt; might be the right abstraction to aim for.&lt;/li&gt;
&lt;li&gt;A data model that can correctly represent the domain, support syncing, and extend to future needs, will often not be a good fit for the querying requirements of the rest of the application. Resolve this tension by maintaining two (or more) representations, not by expecting a single representation to meet all needs.&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Thinking about Syncing, Part 3: separation of concerns</title>
      <link>/post/software/storage/2018/thinking-about-syncing-3/</link>
      <pubDate>Wed, 30 May 2018 00:00:00 +0000</pubDate>
      
      <guid>/post/software/storage/2018/thinking-about-syncing-3/</guid>
      <description>&lt;p&gt;In &lt;a href=&#34;./post/software/storage/2017/thinking-about-syncing-1/&#34;&gt;Part 1&lt;/a&gt; we framed synchronization as exchanging information to allow clients to converge on a shared understanding of the world, specifically involving the merging of timelines.&lt;/p&gt;
&lt;p&gt;In &lt;a href=&#34;./post/software/storage/2017/thinking-about-syncing-2/&#34;&gt;Part 2&lt;/a&gt; we discussed several ways applications might do this: through snapshots (like Firefox Sync), change operations (like Google Docs), or revisions (like CouchDB).&lt;/p&gt;
&lt;p&gt;We outlined a number of limitations of snapshot- and transformation-oriented approaches, and discovered that applications with certain requirements — offline operation, client-side crypto, &lt;em&gt;etc.&lt;/em&gt; — might be best served by an approach that builds a concrete shared timeline between clients.&lt;/p&gt;
&lt;p&gt;In this post we explore how the needs of a typical client application, and the data model that supports those needs, differ from the needs and corresponding data model of synchronization code. The UI usually wants to quickly examine the current state of a small slice of the world, while the synchronizer wants to reliably manage change over time. We will look at a way in which these two sets of concerns can be separated.&lt;/p&gt;
&lt;p&gt;We will expand on the example given in &lt;a href=&#34;./post/software/storage/2017/thinking-about-syncing-2/&#34;&gt;Part 2&lt;/a&gt;, draw an analogy to DVCSes, and see some further examples of systems that separate the concerns of data management from the concerns of data consumers.&lt;/p&gt;
&lt;h3 id=&#34;events-andtables&#34;&gt;Events and tables&lt;/h3&gt;
&lt;p&gt;In &lt;a href=&#34;./post/software/storage/2017/thinking-about-syncing-2/&#34;&gt;Part 2&lt;/a&gt; we saw a brief example of an eminently syncable data representation: the independent event, a stand-alone historical fact.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{&amp;quot;title&amp;quot;: &amp;quot;Bohemian Rhapsody&amp;quot;, &amp;quot;played&amp;quot;: &amp;quot;2017–10–02T15:48:44Z&amp;quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is easy to sync because it can just be copied around: it doesn’t refer to anything (no identifiers to manage, and no identifiers introduced), it doesn’t reflect a change to existing data, and it can’t conflict with anything.&lt;/p&gt;
&lt;p&gt;But this isn’t everything we need for a typical app. Let’s take a step back and think about features, and how those relate to storage.&lt;/p&gt;
&lt;p&gt;There’s implied and missing context in that event.&lt;/p&gt;
&lt;p&gt;We want to be able to record that &lt;em&gt;this&lt;/em&gt; user played a particular song — not just one with that title! — by &lt;em&gt;that&lt;/em&gt; artist, in a playlist, on a device, and so we run into issues of &lt;strong&gt;identity&lt;/strong&gt;, &lt;strong&gt;uniqueness&lt;/strong&gt;, and &lt;strong&gt;reference&lt;/strong&gt; to other entities. Modeling these things is non-trivial: like most application data it’s relational, even if it’s stored in a document database, and that means we need careful management of identifiers and consistency.&lt;/p&gt;
&lt;p&gt;We want to record facts that change over time — star ratings and playlist memberships, renaming playlists, and so on. That means having some conception of updates and breaking of relations.&lt;/p&gt;
&lt;p&gt;We need to handle deletions and creations (&lt;em&gt;e.g.&lt;/em&gt;, recreating a playlist with the same name) with care. And we need a way to permanently delete data; some guilty pleasures need to be forgotten.&lt;/p&gt;
&lt;p&gt;These things need to sync, so the changes you make on your phone are reflected on your laptop.&lt;/p&gt;
&lt;p&gt;A log-structured model is a good fit for this: we can record additions and changes as they happen, and merge our changes in when we sync. We can record new kinds of data easily.&lt;/p&gt;
&lt;p&gt;But we’re not done yet: the front-end code has its own requirements.&lt;/p&gt;
&lt;p&gt;We want to slice and dice this data to support the UI on all of a user’s devices. We want them to be able to quickly find the last ten songs they played, their top 20 most played songs, their top rated songs. They need to be able to browse their current playlists, search by artist or date, and sort the results.&lt;/p&gt;
&lt;p&gt;It’s straightforward to see how our syncing requirements map to an event log, but these front-end retrieval tasks are expensive with a pure event model: play count is a sum aggregate, star rating is last-write-wins, last played is a max aggregate.&lt;/p&gt;
&lt;p&gt;Conversely, it’s easy to see how these front-end features map efficiently to a conventional tabular or object-based storage system, but it’s hard to implement a change-based or log-based syncing system on top of a table for playlists, a table for songs, &lt;em&gt;etc.&lt;/em&gt; with SQL &lt;code&gt;UPDATE&lt;/code&gt; queries. With in-place updates we must manually manage timestamps and change counters and tombstones.&lt;/p&gt;
&lt;p&gt;We’d like to be able to build new front-end features without affecting, or even fully understanding, how the supporting data is synchronized. And we’d like to be able to reuse or change our synchronization code, or extend the data that’s synced, without having to worry about the existing complex query needs of the UI. This is classic &lt;strong&gt;separation of concerns&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Our sync-related requirements are in tension with our ‘direct’ application requirements. Fortunately, our little music app isn’t the first to have to resolve these tensions.&lt;/p&gt;
&lt;h3 id=&#34;an-abridged-history-of-versioncontrol&#34;&gt;An abridged history of version control&lt;/h3&gt;
&lt;p&gt;Most of us — developers, writers, musicians, and more — start out using unsophisticated tools to manage change: copying or zipping directories if we need to preserve an older version of some work.&lt;/p&gt;
&lt;p&gt;If a developer need to move some changes between those different directories, they manually copy files. A sophisticated user might point &lt;code&gt;diff&lt;/code&gt; at the relevant files to produce a patch, edit it, and apply that patch with &lt;code&gt;patch&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you’ve ever had a file on your desktop called something like &lt;code&gt;Essay (v1) final draft FINAL(2) EDITED (TO PRINT).pdf&lt;/code&gt;, then you’ve used this method.&lt;/p&gt;
&lt;p&gt;We might jokingly call this &lt;strong&gt;snapshot-oriented version control&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Early version control systems improved on this somewhat. &lt;a href=&#34;https://en.wikipedia.org/wiki/Revision_Control_System&#34;&gt;RCS&lt;/a&gt; tracked per-file versioning metadata in an adjacent &lt;code&gt;,v&lt;/code&gt; file in the filesystem. It turns out that the needs of file-oriented tools that use versioned data — source code, documentation, &lt;em&gt;etc.&lt;/em&gt; — are very different from the needs of version control tools themselves, particularly at scale.&lt;/p&gt;
&lt;p&gt;Build tools and IDEs want fast hierarchical access to the &lt;em&gt;current&lt;/em&gt; state of your source code, but version control tools want to do things like quickly list every user who changed files in a directory, across moves and renames, in the last fifteen years. Your linter wants to find files missing a copyright header, but your coworker wants you to send her that debug commit that you never landed.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Concurrent_Versions_System&#34;&gt;CVS&lt;/a&gt; and later VCSes split files and changes, beginning to record log files, to store deltas, and to support atomic operations over multiple files.&lt;/p&gt;
&lt;p&gt;Modern version control systems like Git &lt;em&gt;completely divorce&lt;/em&gt; the working tree — the files on disk — from the internal data model of the version control system itself. A &lt;a href=&#34;https://git-scm.com/docs/git-clone#git-clone---bare&#34;&gt;Git repository can exist with no working tree at all&lt;/a&gt;, or with &lt;a href=&#34;https://git-scm.com/docs/git-worktree&#34;&gt;multiple working trees&lt;/a&gt;. The working tree is simply a checkout, a convenient instantiation of a particular state in the repository.&lt;/p&gt;
&lt;p&gt;When a Git client updates one repository from another, it does so by replicating &lt;em&gt;objects —&lt;/em&gt; internal representations of new files, trees, and commits — then advancing refs, then optionally rebasing local changes on top of the new remote changes, and finally &lt;em&gt;optionally&lt;/em&gt; updating a working tree to match the new head. Git doesn’t send operations, it efficiently packs changes, and the remote repo doesn’t need to know anything about the local working tree.&lt;/p&gt;
&lt;p&gt;We can even produce different kinds of checkouts from a single Git repository. It’s easy to grab individual files at any point in history using &lt;code&gt;git show&lt;/code&gt;, and check out only part of a tree with a &lt;a href=&#34;https://git-scm.com/docs/git-read-tree#_sparse_checkout&#34;&gt;sparse checkout&lt;/a&gt;. And users can extract &lt;a href=&#34;https://gregoryszorc.com/blog/2013/11/08/using-mercurial-to-query-mozilla-metadata/&#34;&gt;different kinds of non-file data&lt;/a&gt; from these tools, too.&lt;/p&gt;
&lt;p&gt;The concerns of the data management tool, and the concerns of the consumers of the data it manages, are very different. Modern DVCSes resolve this tension by using two different data representations, deriving each from the other when necessary. The internal data representation is the one that manages change, consisting of atomic commits arranged into branches. The secondary, derived representation — a tree of files — is the one typically consumed by user-facing applications.&lt;/p&gt;
&lt;h3 id=&#34;seeing-similarities&#34;&gt;Seeing similarities&lt;/h3&gt;
&lt;p&gt;Once we see these tensions, and the solution of &lt;em&gt;not&lt;/em&gt; sharing a representation between consumers, we can see the same separation in other places — email clients, photo libraries, even &lt;a href=&#34;https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel&#34;&gt;the MVVM pattern&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Some good examples are found in the database industry itself.&lt;/p&gt;
&lt;p&gt;A common configuration of SQLite writes changed rows to a &lt;a href=&#34;https://www.sqlite.org/wal.html&#34;&gt;Write-Ahead Log&lt;/a&gt; (WAL), rather than directly updating the main database pages. This allows SQLite to get writes on disk cheaply and support concurrent readers and writers. The main database file is updated by ‘replaying’ the WAL. PostgreSQL &lt;a href=&#34;https://www.postgresql.org/docs/9.3/static/warm-standby.html&#34;&gt;supports log-shipping replication on top of its WAL&lt;/a&gt;; the main database format can be specialized to meet the needs of queries, rather than having to accommodate replication metadata.&lt;/p&gt;
&lt;p&gt;As with DVCSes, these internal models of change can also offer value to application code — &lt;code&gt;git log&lt;/code&gt; is useful! &lt;a href=&#34;http://guide.couchdb.org/editions/1/en/notifications.html&#34;&gt;CouchDB&lt;/a&gt; and &lt;a href=&#34;https://pouchdb.com/guides/changes.html&#34;&gt;PouchDB&lt;/a&gt; expose change feeds — the same data they use for reliable syncing — to application code. Datomic is structured around a transaction log that is the source of its current indexed state, encouraging applications to take advantage of long-lived persistent data.&lt;/p&gt;
&lt;p&gt;DVCSes have tensions between log-centric and file-centric consumers, and they resolve them by deriving the working tree from the repository.&lt;/p&gt;
&lt;p&gt;SQL databases have tensions between readers, writers, and replication, and they resolve them by (in very simple terms) deriving database tables and indices from a written log that is also available to replicate.&lt;/p&gt;
&lt;p&gt;A document store like CouchDB has to act like a simple object store while also managing multi-master replication and conflict, and it does so by storing a tree of document revisions.&lt;/p&gt;
&lt;p&gt;We can see that a similar separation of concerns can apply to client-side application storage. &lt;strong&gt;Applications should structure their writes as a log&lt;/strong&gt;, and derive tabular or object-oriented representations from it.&lt;/p&gt;
&lt;h3 id=&#34;generalizing-the-argument-cqrs&#34;&gt;Generalizing the argument: CQRS&lt;/h3&gt;
&lt;p&gt;Synchronization is not the only feature in tension: the needs of &lt;em&gt;different parts of the application&lt;/em&gt; can be at odds with each other. They benefit from having different representations, too.&lt;/p&gt;
&lt;p&gt;In a browser we might naturally store bookmarks as a tree in memory, or in a flat file, so they can be shown in folders. We also need fast textual search over the titles, for which we would use a full-text index. We want fast lookup by URL to check whether the current page is bookmarked, so we want some kind of indexed lookup there, perhaps a &lt;a href=&#34;https://en.wikipedia.org/wiki/Bloom_filter&#34;&gt;Bloom filter&lt;/a&gt;. We want bookmarks to share icons, so we need some way to store and identify those. We want to find the last five bookmarks the user created, so we need some kind of timestamp index. And of course we want to write a new bookmark quickly without updating all of those structures! &lt;em&gt;Features grow over time, and data stretches to try to keep up&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This isn’t news; &lt;a href=&#34;https://engineering.linkedin.com/distributed-systems/log-what-every-software-engineer-should-know-about-real-time-datas-unifying&#34;&gt;certainly not&lt;/a&gt; for anyone who works on big sites.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.baeldung.com/cqrs-event-sourced-architecture-resources&#34;&gt;CQRS&lt;/a&gt; asserts that ‘command’ (writes) and ‘query’ (reads) are best handled with different data representations, even a different representation for each reader. &lt;a href=&#34;http://martinfowler.com/eaaDev/EventSourcing.html&#34;&gt;Event Sourcing&lt;/a&gt; declares that what products want to do with data is going to change over time, and so we should record data as generally as we can — as events — in order to adapt. Even &lt;em&gt;ad hoc&lt;/em&gt; log-oriented enterprises, those that haven’t had a consultant fill whiteboards with sagas and schemas, take for granted that different tools will build varied representations from the same log.&lt;/p&gt;
&lt;p&gt;Client apps often &lt;em&gt;do&lt;/em&gt; keep multiple representations of data, but in an &lt;em&gt;ad hoc&lt;/em&gt; way: write-back in-memory caches, DB indices, and queries that update two tables. By structuring the application around a rich log, it becomes relatively straightforward to derive &lt;em&gt;multiple&lt;/em&gt; specialized representations, and add to and change those representations over time.&lt;/p&gt;
&lt;p&gt;This is not a new observation in the wider industry.&lt;/p&gt;
&lt;p&gt;Leading analytics tools like &lt;a href=&#34;https://amplitude.com/&#34;&gt;Amplitude&lt;/a&gt;, and Mozilla’s own &lt;a href=&#34;https://wiki.mozilla.org/Data/Platform&#34;&gt;data platform&lt;/a&gt;, are designed as aggregators of immutable logs, constructing derived data sources to support various query systems, including SQL-based Redash queries.&lt;/p&gt;
&lt;p&gt;If you talked to a data analysis engineer, and told them that you were going to drop raw event data on the floor as soon as today’s derived dashboards were compiled, rather than warehousing them to answer a different set of questions next week, they’d be horrified. Yet this is what we do when a client app turns a user action, like clicking a toolbar icon, into a decontextualized &lt;code&gt;INSERT INTO bookmarks (url, title, date) …&lt;/code&gt;: we cut down a rich arrangement of data, data that is of interest to other features, into a single simple representation that’s specialized for a particular use. We can do better.&lt;/p&gt;
&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;It’s better to sync logs than state or changes, particularly when data is encrypted or devices go offline.&lt;/li&gt;
&lt;li&gt;Log-structured data is well understood outside of client apps: it’s the underpinning of DVCSes, databases, and some web apps, as well as being a core part of analytics systems like Amplitude.&lt;/li&gt;
&lt;li&gt;Different parts of larger client apps have differing query needs, and those needs are in tension, too.&lt;/li&gt;
&lt;li&gt;Understanding an app’s data as a log of changes, transformed into varied representations for specific uses, not only brings clarity to syncing, but also makes it easier to target those differing needs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Still to come in this series: more details on merging; more concrete exploration of how an application might be structured around a log, from modeling the domain through to defining views; and discussion of the differences between event-structured and log-structured data.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Thinking about Syncing, Part 2: timelines and change</title>
      <link>/post/software/storage/2017/thinking-about-syncing-2/</link>
      <pubDate>Wed, 01 Nov 2017 00:00:00 +0000</pubDate>
      
      <guid>/post/software/storage/2017/thinking-about-syncing-2/</guid>
      <description>&lt;p&gt;In &lt;a href=&#34;./post/software/storage/2017/thinking-about-syncing-1/&#34;&gt;the first part of this series&lt;/a&gt; we established some definitions, concluding with a framing of synchronization as the merging of two timelines.&lt;/p&gt;
&lt;p&gt;In this part we will examine some broad approaches to that problem.&lt;/p&gt;
&lt;h3 id=&#34;the-state-ofthings&#34;&gt;The state of things&lt;/h3&gt;
&lt;p&gt;Some applications aren’t initially designed to sync. They’re designed to support a particular user experience, and their data storage reflects that experience. Sync support is added later.&lt;/p&gt;
&lt;p&gt;When wiring syncing into an existing offline-first application, engineers tend to change as little as possible. (Indeed, Firefox Sync began as an optional add-on to Firefox, with no changes to Firefox’s internals at all.)&lt;/p&gt;
&lt;p&gt;The application continues to store only the current local state — a snapshot, an instant along the timeline. Parts of this persistent state are now &lt;em&gt;annotated&lt;/em&gt; with metadata — change counters, timestamps, global identifiers, tombstones — to allow changes to be tracked, extending the state to address the concerns of the sync code. &lt;strong&gt;Each time the state changes the old state is lost&lt;/strong&gt;; only the metadata serves to indicate that a previous state existed.&lt;/p&gt;
&lt;p&gt;The last synced state is kept on a server (often a simple object or document store) in much the same way. A simple latest-wins form of conflict resolution suffices to manage change, hoping that few conflicts will occur.&lt;/p&gt;
&lt;p&gt;There are various permutations of this depending on how merges are performed and which actors in the system do the work, but the idea is the same. In this post we’ll call this &lt;strong&gt;snapshot-oriented sync&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Snapshot-oriented sync is simple and mostly non-intrusive, and for some applications it’s sufficient.&lt;/p&gt;
&lt;h4 id=&#34;merging&#34;&gt;Merging&lt;/h4&gt;
&lt;p&gt;A client that fetches changes from the server and merges them with local state, without reference to earlier states, by definition performs a two-way merge (which isn’t really a merge at all!). If only one client writes at a time, this is all we need; the other timeline is known to be the previous state, and the change applies without conflict.&lt;/p&gt;
&lt;p&gt;A more sophisticated client that tracks the earlier shared parent (prior to remote changes, local changes, or both) can perform a three-way merge, which is less likely to lose data when two clients make changes around the same time.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;./img/1*qlvBFUXkV3QGiP_LLCTeUw.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Note that in a snapshot-oriented approach — one that looks only at the current state of each client — the intermediate states aren’t available for use in the merge: some aren’t published to the server (and the rest aren’t preserved), so clients never see each other’s states, and the application doesn’t record its own fine-grained history.&lt;/p&gt;
&lt;p&gt;At this point we don’t need to know how a merge works; it’s enough to know that a merge produces a unified timeline, and we can have more or less information available when we do so. A snapshot-based system might merge changed fields, or take one side over the other based on a timestamp, or even pause and ask the user to resolve a conflict.&lt;/p&gt;
&lt;p&gt;The longer the duration between syncs, the more frequent the changes, the more coarse-grained the entities being synchronized, and the larger the volume of data, the more likely it is that the limitations of this approach will become apparent. It’s also difficult to avoid data loss without doing the necessary bookkeeping to support three-way merge, but that bookkeeping is onerous.&lt;/p&gt;
&lt;p&gt;Tracking of change over time in a snapshot-oriented system can be done in a number of ways: &lt;em&gt;e.g.&lt;/em&gt;, if an application’s data model is relatively straightforward, clients might include in each object an explicit record of the client’s view of recent changes, using that record to do a better job of merging, or clients might keep a copy of the last snapshot seen during a merge.&lt;/p&gt;
&lt;p&gt;In snapshot-oriented sync there is no first-class, explicit (certainly not explicit and long-lived) model of the passage of time, of change, or even of the state of other devices. As a result it’s hard to support features like restore, and it can be difficult to reason about the sync-related properties of the system.&lt;/p&gt;
&lt;h3 id=&#34;converging-onchange&#34;&gt;Converging on change&lt;/h3&gt;
&lt;p&gt;Rather than copying &lt;em&gt;state&lt;/em&gt; around, implicitly deriving and reconciling changes, we can instead copy &lt;em&gt;changes&lt;/em&gt; around, and work with those. The goal is to reach the same state — to converge — on each client.&lt;/p&gt;
&lt;p&gt;We can try to do this in several ways. We can send carefully designed events, as in &lt;a href=&#34;https://en.wikipedia.org/wiki/Operational_transformation&#34;&gt;Operational Transformation&lt;/a&gt;, relying on each client to transform and apply each operation in order to converge on the same state. We can restrict ourselves to a particular set of datatypes (&lt;a href=&#34;https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type&#34;&gt;CRDTs&lt;/a&gt;) that allow us to either send commutative updates or guaranteed-mergeable states between clients, such that by definition each client will reach the same state. Or we can track enough state that we can send deltas or diffs to each client, as in &lt;a href=&#34;https://neil.fraser.name/writing/sync/&#34;&gt;Differential Synchronization&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;All change-oriented methods have advantages over an &lt;em&gt;ad hoc&lt;/em&gt; state-based approach, but they make tradeoffs, have significant difficulties (particularly around ordered delivery of operations), and accrue some complexity: DiffSync must maintain shadow copies, OT must take great care with transformation to ensure that the system converges, and CRDTs limit storage design. OT and DiffSync have been widely used for collaborative editing tools, but these techniques are harder to apply to long-lived structured data across offline devices. There is very little discussion in the literature about the application of these specific techniques to client-encrypted data, where the server can’t help with diffing. (If you know of some, please let me know!)&lt;/p&gt;
&lt;p&gt;We can call the application of these techniques to syncing &lt;strong&gt;transformation-oriented sync&lt;/strong&gt;, because clients exchange the changes that should be applied to the other clients to achieve a desired outcome. (CvRDTs are technically an exception.) In transformation-oriented sync there is no long-lived model of change over time. The syncing protocol is nonetheless quite invasive and closely coupled to storage, because it relies on an accurate stream of changes as they occur, or tightly constrains the data structures used.&lt;/p&gt;
&lt;h4 id=&#34;events&#34;&gt;Events&lt;/h4&gt;
&lt;p&gt;If your application works solely in terms of &lt;a href=&#34;https://en.wikipedia.org/wiki/Commutative_property&#34;&gt;commutative&lt;/a&gt; changes, with minimal or no deletion, and little need for managing identifiers, then simple replication of events — a trivial kind of CRDT — might be sufficient. For example, consider a music player that records an event each time a track is played, by name only. We can model this with a simple event object:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{&amp;quot;title&amp;quot;: &amp;quot;Bohemian Rhapsody&amp;quot;, &amp;quot;played&amp;quot;: &amp;quot;2017–10–02T15:48:44Z&amp;quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Clients can share these objects, and as soon as each client has the same set, they all have the same state.&lt;/p&gt;
&lt;p&gt;Unfortunately, most applications are not this simple. How do we identify events in order to delete them? How do we represent richer track entries that might be renamed, linked to artists or albums, or be associated with cover art? How do we order events without comparing client clocks?&lt;/p&gt;
&lt;p&gt;This approach is instructive, however, and we’ll return to it in &lt;a href=&#34;./post/software/storage/2018/thinking-about-syncing-3/&#34;&gt;Part 3&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;looking-back-to-lookforwards&#34;&gt;Looking back to look forwards&lt;/h3&gt;
&lt;p&gt;In Firefox we see writes on different clients that conflict and need to be resolved with some finesse: one device bumps the &lt;code&gt;lastUsed&lt;/code&gt; field of a login, another saves your new password in the &lt;code&gt;password&lt;/code&gt; field, they race to sync, and — because desktop Firefox only does two-way merges — your password change is undone.&lt;/p&gt;
&lt;p&gt;Firefox clients spend prolonged periods offline. Your work machine is offline all weekend, your cellphone is in Airplane Mode during the flight while you use your tablet on the overpriced Wi-Fi, and so on. Even when they are online, they can’t assume cheap, high-bandwidth connectivity.&lt;/p&gt;
&lt;p&gt;Moreover, the set of Firefox Account clients isn’t definitively known in advance; a new device can sign in at any moment.&lt;/p&gt;
&lt;p&gt;An application’s requirements might be even broader. Perhaps there’s a need for an audit trail or backup/restore functionality, or maybe it’s important that each client converge on exactly the same state. Convergence is a property that’s often expected but can’t be assumed, and indeed Firefox Sync clients do not necessarily converge, sometimes in surprising ways.&lt;/p&gt;
&lt;p&gt;We know that snapshot-based syncing is limited: it forgets the sequence of events, making conflicts more difficult to resolve, and it has lots of other limitations. It seems natural to look for a mechanism that syncs changes, rather than snapshots. But transformation-oriented approaches, as used in Google Docs, are not a natural fit for long-lived, offline, encrypted systems.&lt;/p&gt;
&lt;p&gt;With these requirements in mind there’s little alternative but to design around a &lt;em&gt;history&lt;/em&gt; of change on each client and on the server, with one history being canonical. This ongoing record tracks enough of each timeline to allow for a three-way merge, with conflicts detected, and for offline and new clients to catch up incrementally without data loss. Clients themselves have the cleartext to merge.&lt;/p&gt;
&lt;p&gt;Depending on other factors (&lt;em&gt;e.g.&lt;/em&gt;, whether clients need rapid random access to server storage), this design tends towards an implementation in which clients store and exchange &lt;em&gt;deltas&lt;/em&gt; or &lt;em&gt;revisions&lt;/em&gt;, collaboratively building a shared concrete timeline. DVCS tools are just like this: clients store sequences of commits, using merges and rebases to combine branches. In Git or Mercurial terms, the current agreed-upon state of the world is a particular branch, usually called &lt;code&gt;master&lt;/code&gt;, and when a client pushes changes that branch is atomically advanced to a new ‘head’, a particular state that evolved from an earlier &lt;code&gt;master&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We will call this &lt;strong&gt;log-oriented sync&lt;/strong&gt;, because clients record a log of states — their own timeline — that diverges from a known shared history, and then refer to the logs of two timelines when merging. This is subtly different from transformation-oriented sync, because the log represents a sequence of recorded or merged states rather than an outgoing queue, and because the clients agree on history: there is a strict shared ordering of states. Local changes might need to be &lt;em&gt;rebased&lt;/em&gt; or augmented when two timelines merge, but they definitely already happened.&lt;/p&gt;
&lt;p&gt;CouchDB works like this: a revision tree stores history, automatically resolved conflicts refer to earlier revisions for manual repair, and synchronizing two CouchDB or PouchDB databases involves bidirectional replication of revisions.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://engineering.linkedin.com/distributed-systems/log-what-every-software-engineer-should-know-about-real-time-datas-unifying&#34;&gt;Jay Kreps wrote an excellent and comprehensive treatise on the centrality of the log to modern data systems&lt;/a&gt;; it’s well worth the read.&lt;/p&gt;
&lt;h3 id=&#34;aside-the-role-of-theserver&#34;&gt;Aside: the role of the server&lt;/h3&gt;
&lt;p&gt;Firefox Sync uses client-side crypto to achieve privacy guarantees, which by definition means that a central server can’t use the &lt;em&gt;content&lt;/em&gt; of stored data to manage change control.&lt;/p&gt;
&lt;p&gt;However, that only restricts the system’s ability to use a central server to resolve &lt;em&gt;conflicts&lt;/em&gt;. For our purposes, let’s define a conflict as the presence on two timelines of two incompatible changes.&lt;/p&gt;
&lt;p&gt;In a system with client-side crypto, conflicts typically need to be resolved by a client that can see cleartext.&lt;/p&gt;
&lt;p&gt;It’s still possible to use a central coordinator to serialize writes and detect collisions, and of course to deliver changes to clients. It’s even possible to find data via salted and hashed metadata, allowing random or attribute-based access: Firefox Sync does this by giving each record a GUID and an optional &lt;code&gt;sortindex&lt;/code&gt; field.&lt;/p&gt;
&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Bearing in mind that a sync is the process of reaching agreement between clients, we covered three basic approaches:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Exchange snapshots of state, inferring changes when needed to resolve conflicts.&lt;/li&gt;
&lt;li&gt;Exchange streams of changes or diffs, applying them to each client’s state so that each client should converge on the same end state.&lt;/li&gt;
&lt;li&gt;Maintain a relatively long-lived history of changes, periodically merging each client’s timeline back with the main timeline.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This only partly answers the question of how to model application state; clearly there is interplay between how application data is represented and how it is synchronized, but that’s a long way from saying something concrete like “store state as JSON objects with vector clocks”. This is a big topic, so we’ll get to it in &lt;a href=&#34;./post/software/storage/2018/thinking-about-syncing-3/&#34;&gt;Part 3&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Thinking about Syncing, Part 1: timelines</title>
      <link>/post/software/storage/2017/thinking-about-syncing-1/</link>
      <pubDate>Wed, 11 Oct 2017 00:00:00 +0000</pubDate>
      
      <guid>/post/software/storage/2017/thinking-about-syncing-1/</guid>
      <description>&lt;p&gt;I’ve been thinking about syncing data — in particular, about Firefox Sync, systems that touch it, and systems that might replace it — for about seven years now. I’ve been thinking about data representation in general for most of my career.&lt;/p&gt;
&lt;p&gt;In that time I’ve begun to piece together a practical understanding of what we mean when we talk about syncing. This series of blog posts aims to capture some context and my mental model, and draw some conclusions that I think are true, given the constraints.&lt;/p&gt;
&lt;p&gt;Comments, clarifications, and pointers to literature that I’ve missed would be very much appreciated.&lt;/p&gt;
&lt;p&gt;In this blog post we will introduce the concept of a &lt;em&gt;timeline&lt;/em&gt;, which is a linear sequence of states, and define syncing for our purposes as the process of merging two timelines.&lt;/p&gt;
&lt;p&gt;Subsequent posts will cover some approaches to merging timelines, examine how the concerns of synchronization are in tension with other parts of an application, and suggest a model for resolving that tension by separating concerns.&lt;/p&gt;
&lt;p&gt;Let’s start at the beginning: by exploring what sync systems do.&lt;/p&gt;
&lt;h3 id=&#34;facets-of-a-syncsystem&#34;&gt;Facets of a sync system&lt;/h3&gt;
&lt;p&gt;“To sync”, at its most abstract, is to take two or more storage systems and make them agree on the state of their shared world.&lt;/p&gt;
&lt;p&gt;There can be several reasons for doing this — to deliver a consistent user experience across devices, to safeguard the user’s data, to enable new product features — but the method is similar regardless.&lt;/p&gt;
&lt;p&gt;Typically those systems will &lt;strong&gt;persist data&lt;/strong&gt;, but in some cases it might be temporary. For example, Firefox Sync’s tab sync doesn’t write to disk. The current tabs are all fetched from the server when you launch Firefox, kept up to date during the session, then dropped when you quit.&lt;/p&gt;
&lt;p&gt;Typically their &lt;strong&gt;interactions&lt;/strong&gt; will be mediated by a central server, but they could also communicate directly.&lt;/p&gt;
&lt;p&gt;Typically the state of the world is &lt;strong&gt;application data&lt;/strong&gt;, usually data that reflects a user’s activity.&lt;/p&gt;
&lt;p&gt;Typically clients (in my world they’re rich client applications like browsers) will &lt;strong&gt;sync more than once&lt;/strong&gt;, keeping themselves “in sync” over time, but we can also imagine situations in which a client syncs only once.&lt;/p&gt;
&lt;p&gt;Firefox Sync is typical in these senses. Your Firefox profiles periodically synchronize long-lived user data with each other, communicating via encrypted payloads stored on a central server that acts as a shared whiteboard.&lt;/p&gt;
&lt;p&gt;The process of syncing is, at a slightly less abstract level, the process of &lt;em&gt;exchanging&lt;/em&gt; enough facts that the clients can then &lt;em&gt;converge&lt;/em&gt; on agreement.&lt;/p&gt;
&lt;p&gt;Exchanging facts and reaching agreement can be as simple as a one-way overwrite of a file, or as complex as a fine-grained automatic merge over local Wi-Fi.&lt;/p&gt;
&lt;p&gt;We can think about the commonalities of these processes in terms of timelines and merges.&lt;/p&gt;
&lt;h3 id=&#34;timelines-andmerges&#34;&gt;Timelines and merges&lt;/h3&gt;
&lt;p&gt;Consider a particular state — your current to-do list, perhaps. That state is the successor of an earlier state, and the precursor of a later state. In the to-do list example the &lt;em&gt;earliest&lt;/em&gt; state is an empty list. As you add items, delete them, and mark them as done, you change one state into another. We will call this ordered sequence of states a &lt;em&gt;timeline&lt;/em&gt;. If you’re familiar with version control systems, you might think of this as a &lt;em&gt;branch&lt;/em&gt;, though unlike VCSes most CRUD apps don’t keep the old states around.&lt;/p&gt;
&lt;p&gt;The simplest networked systems don’t sync at all; there is a single timeline on a server, and only &lt;em&gt;rendered&lt;/em&gt; state is held by the client. Traditional CGI web apps are this way: all persistent data is stored on a server, and changes are made by sending a request to that server. Your to-do list might look like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn-images-1.medium.com/max/800/1*H786Dl0FzTO_prODi8Au1Q.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;The next most simple are those that keep a copy of the state itself on each client, but only change it in one place. All attempted modifications are sent to the server to be applied to the canonical state. The server responds with updates to the client’s state; the server’s updates always win over any speculative changes made by the client. This is how &lt;a href=&#34;http://redux.js.org/&#34;&gt;Redux&lt;/a&gt; apps often work. This is syncing without any trouble: it’s a single loop of state transfer. There’s only one timeline, even if multiple clients are asking the server to make changes. All actions move inwards to the server, changes move outwards to the clients, and control over races is in the hands of the server.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn-images-1.medium.com/max/800/1*NVk19bfwg0jvGtqO138gBg.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;When we think of “syncing”, though, we usually think of systems in which &lt;em&gt;multiple timelines&lt;/em&gt; can occur as part of normal operation. Those systems are more complex than an ordinary web app: they require the clients or the server to sometimes combine two timelines into one, or to maintain two timelines in parallel.&lt;/p&gt;
&lt;p&gt;We can start to form a taxonomy of the more complex kinds of system by asking some questions. These questions are phrased in terms of a central server, but they generalize to peer-to-peer or local-only setups.&lt;/p&gt;
&lt;h4 id=&#34;can-you-record-any-permanent-data-before-beginning-tosync&#34;&gt;Can you record any permanent data before beginning to sync?&lt;/h4&gt;
&lt;p&gt;If so, multiple starting timelines need to merge at least once. This situation is unavoidable for a product with as long a history as Firefox — we have millions of users with precious bookmarks and logins on their devices, and they want to keep them when they start using a Firefox Account. If those users have more than one device, they’ll start with at least two separate timelines.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn-images-1.medium.com/max/800/1*H0WALktpjKp4KmLjRygHPA.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;(Note that these little diagrams represent &lt;em&gt;timelines —&lt;/em&gt; sequences of states — not the state of each device. After the merge there are still two clients, but they both agree on the same state of the world: they are at the same point on the same timeline.)&lt;/p&gt;
&lt;h4 id=&#34;can-you-record-data-while-temporarily-partitioned-from-the-syncing-infrastructure&#34;&gt;Can you record data while temporarily partitioned from the syncing infrastructure?&lt;/h4&gt;
&lt;p&gt;Clients might not be constantly communicating with the server or other clients. They might face network interruptions or outages, be running on a device that forbids background syncing or cellular data use, or the app might only sync on a schedule.&lt;/p&gt;
&lt;p&gt;In these scenarios, clients must buffer outgoing changes, and the client or the server must perform little ‘mini merges’ on each sync. State changes on two devices temporarily result in &lt;em&gt;two&lt;/em&gt; timelines, both of which must merge back.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn-images-1.medium.com/max/800/1*TlsCifCd-83Z5iZgzRtTmg.png&#34; alt=&#34;&#34;&gt;
&lt;img src=&#34;https://cdn-images-1.medium.com/max/800/1*DdPlSLvtQMu2Hl_bdePiTg.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;h4 id=&#34;can-you-stop-using-sync-and-keep-yourdata&#34;&gt;Can you stop using sync and keep your data?&lt;/h4&gt;
&lt;p&gt;If you stop syncing altogether — not just a temporary blip, but really signing out — can you record more data, then later resume use of sync? Or can you sign out of one account and sign in to another account with the same data?&lt;/p&gt;
&lt;p&gt;This question implies some complex weaving of timelines; indeed, the production of new &lt;em&gt;long-lived&lt;/em&gt; timelines. Each client can sign out, record some new data, restore from backups, and do all kinds of things before signing into the same or another account. Users really do these things (and worse!) and expect something sensible to happen. Some Firefox Sync users routinely sign in and out in order to tightly control when syncing happens, or to try to build unidirectional syncing workflows.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn-images-1.medium.com/max/800/1*30adE2rQQEN4hSIe1CBL2A.png&#34; alt=&#34;&#34;&gt;
&lt;img src=&#34;https://cdn-images-1.medium.com/max/800/1*Rm5FhoeQElcczqC_jUTb8g.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;A user might even decide to take a device from one account to another.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn-images-1.medium.com/max/800/1*Hxm7nNCnRoOmQSbgyywtlQ.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;h4 id=&#34;can-a-user-rewritehistory&#34;&gt;Can a user rewrite history?&lt;/h4&gt;
&lt;p&gt;Can you restore from a local backup while sync is enabled? Can you permanently erase data?&lt;/p&gt;
&lt;p&gt;Permanently erasing data — &lt;em&gt;e.g.&lt;/em&gt;, clearing your recent browsing history, or deleting saved logins — requires scrubbing the ‘official record’. Given that the official record is spread across multiple clients, and perhaps one or more servers, this can be complex. Even ordinary deletion isn’t necessarily trivial.&lt;/p&gt;
&lt;p&gt;(Firefox Sync gets this wrong in some edge cases. The most obvious is history: visits, described by their timestamps and reasons, are stored as part of their parent history record, which holds the URL and current title, and that structure has no way to represent the deletion of an individual visit. Consequently, using the ‘Forget’ feature in Firefox to erase just &lt;em&gt;part&lt;/em&gt; of your long-term interactions with a website has no effect on those visits that have already synced to another device.)&lt;/p&gt;
&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;We began by defining syncing as the process by which clients converge on agreement about the state of their shared world.&lt;/p&gt;
&lt;p&gt;We defined a timeline as an ordered sequence of states. We recognized that the simplest form of syncing is a one-way transfer of state corresponding to steps along a timeline. We came to a stipulative real-world definition of syncing as the transfer of such state that might require the merging of two timelines. We asked a number of questions, the answers to which determine when timelines might be created or merged.&lt;/p&gt;
&lt;p&gt;Many apps say “no” to some or all of these questions. Firefox needs to support all of them; Firefox Sync clients have complex state, and periodically merge it with changes from other clients.&lt;/p&gt;
&lt;p&gt;Saying “no” to some of these questions constrains the definition of syncing. Indeed, if you say “no” to all of the first three — mandatory account, no offline work, no disconnect — the app doesn’t meet our stipulative definition of syncing at all. Such an app is a website or a remoting system like X Windows or VNC, not a syncing system.&lt;/p&gt;
&lt;p&gt;In future parts we will touch on the representation of states and timelines in an application, discuss some broad approaches to syncing, examine some of the tasks that must be performed in a syncing system, and finally discuss how support for sync should affect API design and how a client stores data.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;./post/software/storage/2017/thinking-about-syncing-2/&#34;&gt;Part two is next&lt;/a&gt;!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>A conceptual introduction to Project Mentat</title>
      <link>/post/software/storage/2017/conceptual-introduction-to-mentat/</link>
      <pubDate>Tue, 21 Feb 2017 00:00:00 +0000</pubDate>
      
      <guid>/post/software/storage/2017/conceptual-introduction-to-mentat/</guid>
      <description>&lt;p&gt;This post is intended for a particular audience: developers who perhaps haven’t done lots of database or data representation work, want to get started working &lt;em&gt;on&lt;/em&gt; Mentat, and are looking for a ‘quick fix’ of context.&lt;/p&gt;
&lt;p&gt;You should have already read &lt;a href=&#34;./post/software/storage/2016/introducing-mentat/&#34;&gt;Introducing Project Mentat&lt;/a&gt;. This post might fill in some gaps for you.&lt;/p&gt;
&lt;p&gt;This post covers, very briefly:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;What is a database?&lt;/li&gt;
&lt;li&gt;What is a schema?&lt;/li&gt;
&lt;li&gt;What is event sourcing?&lt;/li&gt;
&lt;li&gt;What is Datomic?&lt;/li&gt;
&lt;li&gt;What is SQLite?&lt;/li&gt;
&lt;li&gt;What is Project Mentat?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The answers are relatively brief and somewhat opinionated, but they should offer enough of a starting point for further research. Disagreement is welcome!&lt;/p&gt;
&lt;h4 id=&#34;what-is-a-database&#34;&gt;What is a database?&lt;/h4&gt;
&lt;p&gt;Databases of all kinds share a common goal: to provide persistent storage and querying to one or more applications. Beyond that, different tradeoffs yield a surprising variety of solutions.&lt;/p&gt;
&lt;p&gt;The traditional “databases”, as most developers now understand the term, are relational SQL databases. The only interaction is via a textual query language, SQL. These databases are typically ACID: atomic, consistent, isolated, and durable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Atomic&lt;/strong&gt; means that a write (indeed, a related collection of writes) either happens in its entirety or doesn’t happen at all.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Consistent&lt;/strong&gt; means that rules and conditions expressed in the database (&lt;em&gt;e.g.&lt;/em&gt;, foreign key constraints, &lt;code&gt;NOT NULL&lt;/code&gt; constraints, type definitions) continue to apply at all times.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Isolated&lt;/strong&gt; means that readers and writers don’t see each other while they’re working. Writes conceptually happen in one instant in time, and within a particular transaction you are isolated from those moments.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Durable&lt;/strong&gt; means that once data is written it isn’t lost.&lt;/p&gt;
&lt;p&gt;Note that these properties have nothing to do with whether a DB uses SQL, is relational, or otherwise. However, so-called “&lt;strong&gt;NoSQL&lt;/strong&gt;” databases often neglect some of these properties: it’s not uncommon for them to lose data that’s been confirmed as written (&lt;a href=&#34;http://stackoverflow.com/questions/18488209/does-mongodb-journaling-guarantee-durability&#34;&gt;MongoDB&lt;/a&gt; being the most mocked example), not expose transaction primitives (or for their transactional guarantees to only apply within a single row or document), or not bother with consistency constraints at all.&lt;/p&gt;
&lt;p&gt;Some have argued that &lt;a href=&#34;http://adam.herokuapp.com/past/2007/12/17/a_world_without_sql/&#34;&gt;these properties (and other characteristics of relational databases) are obsolete&lt;/a&gt;: for example, that in-database consistency constraints are better handled by business logic inside an application. Different databases draw these lines in different places.&lt;/p&gt;
&lt;p&gt;You might have heard terms like “&lt;a href=&#34;https://en.m.wikipedia.org/wiki/Consistency_model&#34;&gt;eventually consistent&lt;/a&gt;” when used to describe distributed systems. (Eventual consistency simply means that, if you wait long enough, all readers will see the same last write.) Distributed systems are hard, and many hosted NoSQL databases are clustered in order to scale, forcing them to contend with the &lt;a href=&#34;https://en.m.wikipedia.org/wiki/CAP_theorem&#34;&gt;CAP theorem&lt;/a&gt;. We won’t dig any deeper into that, because for the purposes of this post we aren’t concerned with distributed storage.&lt;/p&gt;
&lt;p&gt;Different databases have opinions about the kinds of data they store and the way they model it. Sometimes these opinions are so pervasive that we don’t really notice them.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Relational databases store &lt;em&gt;relations&lt;/em&gt; between &lt;em&gt;entities&lt;/em&gt;. SQL databases model relations as tables with an arbitrary number of columns. Entities are rows in some table, and are identified by keys. Queries join relations (tables) to yield new relations. SQL databases are not &lt;em&gt;ideal&lt;/em&gt; for storing graphs — graph traversal requires recursive joins, which is a relatively recent SQL feature — documents, unstructured data, &lt;em&gt;etc.&lt;/em&gt;, though &lt;a href=&#34;http://renesd.blogspot.ca/2017/02/is-postgresql-good-enough.html&#34;&gt;PostgreSQL is often good enough&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Document databases store content without establishing an up-front explicit schema. They often use JSON or XML as a native data format. (‘Schemaless’ storage seems like a time saving, but see below.)&lt;/li&gt;
&lt;li&gt;Graph databases model data as links between nodes. Sometimes those links can themselves be annotated. Querying is via path traversals and conditionals, which can be very natural for some domains. Typically graph databases are designed to be faster at graph operations like “find me related actors” than querying a graph modeled in another kind of database.&lt;/li&gt;
&lt;li&gt;Geospatial databases focus on spatial coordinates as a primary way of finding things.&lt;/li&gt;
&lt;li&gt;Applications often &lt;a href=&#34;https://wiki.mozilla.org/Performance/Avoid_SQLite_In_Your_Next_Firefox_Feature#How_to_Store_Your_Data&#34;&gt;use an &lt;em&gt;ad hoc&lt;/em&gt; flat file&lt;/a&gt; as a database: it’s read into memory and flushed to disk when changed. Typically this is a knee-jerk response to a badly configured database (“I don’t need all that complex database stuff!”), or to a proliferation of independent databases, and it forces the application developer to manually choose when to flush, how to query data in memory, how to handle scaling, &lt;em&gt;etc.&lt;/em&gt; Flat files are a good solution for data that rarely changes, isn’t concurrently modified, and is simple to query; configuration files are a good example. It’s a bad solution for data that changes frequently and needs to be read or written transactionally. &lt;a href=&#34;https://www.servethehome.com/firefox-is-eating-your-ssd-here-is-how-to-fix-it/&#34;&gt;Firefox has well-documented problems with session store&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;And so on.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;what-is-aschema&#34;&gt;What is a schema?&lt;/h4&gt;
&lt;p&gt;A description of your domain. A recipe for the shape of your data. Go read &lt;a href=&#34;http://martinfowler.com/articles/schemaless/&#34;&gt;Martin Fowler’s take on schemaless storage&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Database schemas are also often where indices/indexes are described: relational databases &lt;em&gt;conflate&lt;/em&gt; semantic, structural, and index descriptions of data into a single schema.&lt;/p&gt;
&lt;p&gt;Relational databases (and others) use indexes to make queries fast. An index in a relational database is (usually) a copy of all or part of a table, stored in a different order, with metadata to facilitate finding the right values.&lt;/p&gt;
&lt;h4 id=&#34;what-is-event-sourcing&#34;&gt;What is event sourcing?&lt;/h4&gt;
&lt;p&gt;The concept that your application state is the end result of a sequence of events that took place since an earlier state, and the idea that the fundamental modeling construct for dealing with this is to record the sequence of events directly, &lt;em&gt;deriving&lt;/em&gt; other data structures from the event stream. This should feel very familiar to React/Redux developers.&lt;/p&gt;
&lt;p&gt;Event sourcing is loosely related to CQRS, which is the idea that the readers and the writers in your system are best served by different data representations. Our approach is not opinionated — CQRS, not ES — but you’ll often see them discussed together.&lt;/p&gt;
&lt;p&gt;Again, go &lt;a href=&#34;http://martinfowler.com/eaaDev/EventSourcing.html&#34;&gt;read Martin Fowler&lt;/a&gt;, and see &lt;a href=&#34;http://www.baeldung.com/cqrs-event-sourced-architecture-resources&#34;&gt;this list of further reading&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&#34;what-isdatomic&#34;&gt;What is Datomic?&lt;/h4&gt;
&lt;p&gt;Datomic is a closed-source database, written in Clojure and running on the JVM, and built and maintained by Cognitect. Datomic has a rich schema language, stores relational data (albeit a little more loosely than a SQL database does), and is distinguished by its attitude to time and change. The history of all changes is accessible to application code. The schema is similarly accessible. Schema definitions can evolve over time, with older definitions available just like older data. Applications can query past (and hypothetical future!) states of the system.&lt;/p&gt;
&lt;p&gt;Datomic’s record of the data it stores is — unlike traditional relational databases — very aware of time and state, in a similar way to how Clojure makes explicit the &lt;a href=&#34;http://clojure.org/about/state&#34;&gt;distinction between values and identity in state&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;All databases in consumer applications need to handle changing and growing data over time; Datomic includes this as part of its data model. By contrast, most databases entirely forget that changes ever took place, with changes only stored in a log for long enough to provide durability or replication: the stored data in the database itself at the current time is purely a snapshot, and applications that need to reflect time and change in their data model must do so explicitly. “Deletion” in Datomic is actually one of two things: &lt;strong&gt;retraction&lt;/strong&gt; (stating that an earlier fact is no longer true) and &lt;strong&gt;excision&lt;/strong&gt; (cutting out part of history as if it never took place, typically for legal or privacy reasons).&lt;/p&gt;
&lt;p&gt;There are lots of other things that are a bit special about Datomic, like the distinction between peers and the transactor.&lt;/p&gt;
&lt;p&gt;Read a conversational introduction (&lt;a href=&#34;http://gigasquidsoftware.com/blog/2015/08/15/conversations-with-datomic/&#34;&gt;part 1&lt;/a&gt;, &lt;a href=&#34;http://gigasquidsoftware.com/blog/2015/08/19/conversations-with-datomic-part-2/&#34;&gt;part 2&lt;/a&gt;), and &lt;a href=&#34;http://www.youtube.com/watch?v=RKcqYZZ9RDY&#34;&gt;watch Rich Hickey explain:&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Datomic is a service that runs alongside a broad array of existing storage systems (including AWS and Cassandra), using them to store index chunks.&lt;/p&gt;
&lt;h4 id=&#34;what-issqlite&#34;&gt;What is SQLite?&lt;/h4&gt;
&lt;p&gt;SQLite is a very stable, quite fast, extraordinarily well-tested, embedded SQL database. Embedded (also called “serverless”) databases are no longer that common; most SQL databases — indeed, most ACID databases — are relatively large hosted servers like PostgreSQL, MySQL, &lt;em&gt;etc.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We use SQLite extensively in Firefox, and Mozilla has a good relationship with its developers.&lt;/p&gt;
&lt;h4 id=&#34;what-is-projectmentat&#34;&gt;What is Project Mentat?&lt;/h4&gt;
&lt;p&gt;Mentat is an embedded &lt;em&gt;datom store&lt;/em&gt;: essentially Datomic’s data model and schema interface expressed on top of a SQLite database.&lt;/p&gt;
&lt;p&gt;Naturally, many of Datomic’s concepts — &lt;em&gt;e.g.&lt;/em&gt;, scaling reads by replicating index chunks to peers — don’t apply, and the concept of database-as-value is less relevant in an embedded system. But we preserve the ideas of a first-class transaction log, a domain-level schema, transaction listeners, and so on.&lt;/p&gt;
&lt;p&gt;The principal advantages of Mentat in applications like Tofino are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It’s natural to grow the schema and make new relations between entities. Schemas change all the time in living applications.&lt;/li&gt;
&lt;li&gt;Schema modeling is done at the domain level (“a page can have multiple visits”) not at the storage level (“the visits table has a column with a non-unique, not null foreign key constraint that refers to the pages table”).&lt;/li&gt;
&lt;li&gt;Different parts of the application can cooperatively share a single database.&lt;/li&gt;
&lt;li&gt;The transaction log is available for querying (and for synchronization purposes).&lt;/li&gt;
&lt;li&gt;The query language makes it easy to express joins, particularly graph-like self joins that are very complex in SQL. &lt;a href=&#34;http://www.learndatalogtoday.org/&#34;&gt;Here&amp;rsquo;s an introduction to the Datalog query language&lt;/a&gt; used by Datomic, &lt;a href=&#34;https://github.com/tonsky/datascript&#34;&gt;DataScript&lt;/a&gt;, and Mentat.&lt;/li&gt;
&lt;li&gt;The architecture of the database makes it natural to address performance via materializing views and indexes, either inside or outside the database itself. For example, an attribute can be marked for full-text searching just by adding “&lt;code&gt;:fulltext true&lt;/code&gt;” to the schema. Applications see every transaction as it occurs, and can thus build their own caches.&lt;/li&gt;
&lt;li&gt;Many of the mistakes made by developers adding &lt;em&gt;ad hoc&lt;/em&gt; flexibility to a database — such as a “metadata” table containing strings, resulting in inefficient storage and slow querying — have been avoided: the schema itself offers enough flexibility that stringly-typed storage is unnecessary.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Mentat uses a combination of SQLite’s own ACID properties and sequential writes to achieve ACID guarantees (&lt;a href=&#34;https://github.com/mozilla/datomish/issues/89&#34;&gt;more or less&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;It’s worth recognizing at this point that &lt;em&gt;how&lt;/em&gt; Mentat stores data in SQLite is an implementation detail. We could split up our &lt;code&gt;datoms&lt;/code&gt; table into pieces; we could store the transaction log in an &lt;code&gt;ATTACH&lt;/code&gt;ed database; we could even automatically derive traditional ‘wide’ database tables where appropriate. The abstraction boundary is quite opaque, and only the transactor and the query engine need to know about the details. Abstracting storage in this way is itself valuable: we can make significant changes in how Mentat is implemented without altering our API surface, and improvements under the surface are immediately available to all consumers.&lt;/p&gt;
&lt;h4 id=&#34;what-next&#34;&gt;What next?&lt;/h4&gt;
&lt;p&gt;This post covered some context, but doesn’t address exactly how Mentat is built. Some of the pages &lt;a href=&#34;https://github.com/mozilla/mentat/wiki&#34;&gt;on the project wiki&lt;/a&gt; cover that, but another post might be forthcoming.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Introducing Project Mentat, a flexible embedded knowledge store</title>
      <link>/post/software/storage/2016/introducing-mentat/</link>
      <pubDate>Tue, 15 Nov 2016 00:00:00 +0000</pubDate>
      
      <guid>/post/software/storage/2016/introducing-mentat/</guid>
      <description>&lt;p&gt;Evolving storage is hard. Can we make it easier?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Edit, January 2017: to avoid confusion and to better follow Mozilla’s early-stage project naming guidelines, we’ve renamed Datomish to&lt;/em&gt; &lt;strong&gt;&lt;em&gt;Project Mentat&lt;/em&gt;&lt;/strong&gt;&lt;em&gt;. This post has been altered to match.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For several months now, a small team at Mozilla has been exploring new ways of building a browser. We called that effort &lt;a href=&#34;https://medium.com/project-tofino/&#34;&gt;Tofino&lt;/a&gt;, and it’s now morphed into the &lt;a href=&#34;https://medium.com/project-tofino/re-defining-the-tofino-project-6d3c98521cc8&#34;&gt;Browser Futures Group&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As part of that, Nick Alexander and I have been working on a persistent embedded knowledge store called &lt;a href=&#34;https://github.com/mozilla/mentat&#34;&gt;Project Mentat&lt;/a&gt;. Mentat is designed to ship in client applications, storing relational data on disk with a flexible schema.&lt;/p&gt;
&lt;p&gt;It’s a little different to most of the storage systems you’re used to, so let’s start at the beginning and explain &lt;em&gt;why&lt;/em&gt;. If you’re only interested in the &lt;em&gt;what&lt;/em&gt;, skip down to just above the example code.&lt;/p&gt;
&lt;p&gt;As we began building Tofino’s data layer, we observed a few things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We knew we’d need to store new types of data as our product goals shifted: page metadata, saved content, browsing activity, location. The set of actions the user can take, and the data they generate, is bound to grow over time. We didn’t (don’t!) know what these were in advance.&lt;/li&gt;
&lt;li&gt;We wanted to support front-end innovation without being gated on some storage developer writing a complicated migration. We’ve seen database evolution become a locus of significant complexity and risk — “here be dragons” — in several other applications. Ultimately it becomes easier to store data elsewhere (a new database, simple prefs files, a key-value table, or JSON on disk) than to properly integrate it into the existing database schema.&lt;/li&gt;
&lt;li&gt;As part of that front-end innovation, sometimes we’d have two different ‘forks’ both growing the data model in two directions at once. That’s a difficult problem to address with a tool like SQLite.&lt;/li&gt;
&lt;li&gt;Front-end developers were interested in looser approaches to accessing stored data than specialized query endpoints: &lt;em&gt;e.g.&lt;/em&gt;, &lt;a href=&#34;https://medium.com/u/d3391efe481a&#34;&gt;Lin Clark&lt;/a&gt; suggested that &lt;a href=&#34;http://graphql.org/&#34;&gt;GraphQL&lt;/a&gt; might be a better fit. Only a month or two into building Tofino we already saw the number of API endpoints, parameters, and fields growing as we added features. Specialized API endpoints turn into &lt;em&gt;ad hoc&lt;/em&gt; query languages.&lt;/li&gt;
&lt;li&gt;Syncability was a constant specter hovering at the back of our minds: &lt;a href=&#34;./post/software/storage/2015/syncing-and-storage-on-three-platforms/&#34;&gt;getting the data model right&lt;/a&gt; for future syncing (or partial hosting on a service) was important.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Many of these concerns happen to be shared across other projects at Mozilla: &lt;a href=&#34;https://wiki.mozilla.org/Firefox/Activity_Stream&#34;&gt;Activity Stream&lt;/a&gt;, for example, also needs to store a growing set of page summary attributes for visited pages, and join those attributes against your main browsing history.&lt;/p&gt;
&lt;p&gt;Nick and I started out supporting Tofino with a simple store in SQLite. We knew it had to adapt to an unknown set of use cases, so we decided to follow the principles of &lt;a href=&#34;http://www.baeldung.com/cqrs-event-sourced-architecture-resources&#34;&gt;CQRS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;CQRS — Command Query Responsibility Segregation — recognizes that it’s hard to pick a single data storage model that works for all of your readers and writers… particularly the ones you don’t know about yet.&lt;/p&gt;
&lt;p&gt;As you begin building an application, it’s easy to dive head-first into storing data to directly support your first user experience. As the experience changes, and new experiences are added, your single data model is pulled in diverging directions.&lt;/p&gt;
&lt;p&gt;A common &lt;em&gt;second system syndrome&lt;/em&gt; for this is to reactively aim for maximum generality. You build a single normalized super-flexible data model (or key-value store, or document store)… and soon you find that it’s expensive to query, complex to maintain, has designed-in capabilities that will never be used, and you &lt;em&gt;still&lt;/em&gt; have tensions between different consumers.&lt;/p&gt;
&lt;p&gt;The CQRS approach, at its root, is to separate the ‘command’ from the ‘query’: store a data model that’s very close to what the writer knows (typically a stream of events), and then materialize as many query-side data stores as you need to support your readers. When you need to support a new kind of fast read, you only need to do two things: figure out how to materialize a view from history, and figure out how to incrementally update it as new events arrive. You shouldn’t need to touch the base storage schema at all. When a consumer is ripped out of the product, you just throw away their materialized views.&lt;/p&gt;
&lt;p&gt;Viewed through that lens, everything you do in a browser is an event with a context and a timestamp: “the user bookmarked page X at time T in session S”, “the user visited URL X at time T in session S for reason R, coming from visit V1”. &lt;strong&gt;Store everything you know, materialize everything you need&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;We built that with SQLite.&lt;/p&gt;
&lt;p&gt;This was a clear and flexible concept, and it allowed us to adapt, but the implementation in JS involved lots of boilerplate and was somewhat cumbersome to maintain manually: the programmer does the work of defining how events are stored, how they map to more efficient views for querying, and how tables are migrated when the schema changes. You can &lt;a href=&#34;https://github.com/mozilla/tofino/blob/4962a5411f915c1c0369fd10a65d51b7064d2d58/app/services/user-agent-service/sqlstorage.js&#34;&gt;see this starting to get painful even early in Tofino’s evolution&lt;/a&gt;, even &lt;a href=&#34;https://github.com/mozilla/tofino/blob/4962a5411f915c1c0369fd10a65d51b7064d2d58/app/services/user-agent-service/profile-schema.js&#34;&gt;without data migrations&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Quite soon it became clear that a conventional embedded SQL database wasn’t a direct fit for a problem in which the schema grows organically — particularly not one in which multiple experimental interfaces might be sharing a database. Furthermore, being elbow-deep in SQL wasn’t second-nature for Tofino’s webby team, so the work of evolving storage fell to just a few of us. (Does any project ever have enough people to work on storage?) We began to look for alternatives.&lt;/p&gt;
&lt;p&gt;We explored a range of existing solutions: key-value stores, graph databases, and document stores, as well as the usual relational databases. Each seemed to be missing some key feature.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Most good storage systems simply aren’t suitable for embedding in a client application&lt;/strong&gt;. There are lots of great storage systems that run on the JVM and scale across clusters, but we need to run on your Windows tablet! At the other end of the spectrum, most webby storage libraries aren’t intended to scale to the amount of data we need to store. Most graph and key-value stores are missing one or more of full-text indexing (crucial for the content we handle), expressive querying, &lt;a href=&#34;http://martinfowler.com/articles/schemaless/&#34;&gt;defined schemas&lt;/a&gt;, or the kinds of indexing we need (&lt;em&gt;e.g.&lt;/em&gt;, fast range queries over visit timestamps). ‘Easy’ storage systems of all stripes often neglect concurrency, or transactionality, or multiple consumers. And most don’t give much thought to how materialized views and caches would be built on top to address the tension between flexibility and speed.&lt;/p&gt;
&lt;p&gt;We found a couple of solutions that seemed to have the right shape (which I’ll discuss below), but weren’t quite something we could ship. &lt;a href=&#34;http://www.datomic.com&#34;&gt;&lt;strong&gt;Datomic&lt;/strong&gt;&lt;/a&gt; is a production-grade JVM-based clustered relational knowledge store. It’s great, as you’d expect from Cognitect, but it’s not open-source and we couldn’t feasibly embed it in a Mozilla product. &lt;a href=&#34;https://github.com/tonsky/datascript&#34;&gt;&lt;strong&gt;DataScript&lt;/strong&gt;&lt;/a&gt; is a ClojureScript implementation of Datomic’s ideas, but it’s intended for in-memory use, and we need persistent storage for our datoms.&lt;/p&gt;
&lt;p&gt;Nick and I try to be responsible engineers, so we explored the cheap solution first: adding persistence to DataScript. We thought we might be able to leverage all of the work that went into DataScript, and just flush data to disk. It soon became apparent that we couldn’t resolve the impedance mismatch between a synchronous in-memory store and asynchronous persistence, and we had concerns about memory usage with large datasets. Project Mentat was born.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mentat is built on top of SQLite&lt;/strong&gt;, so it gets all of SQLite’s reliability and features: full-text search, transactionality, durable storage, and a small memory footprint.&lt;/p&gt;
&lt;p&gt;On top of that we’ve layered ideas from DataScript and Datomic: a &lt;strong&gt;transaction log&lt;/strong&gt; with first-class transactions so we can see and annotate a history of events without boilerplate; a first-class &lt;strong&gt;mutable schema&lt;/strong&gt;, so we can easily grow the knowledge store in new directions and introspect it at runtime; Datalog for storage-agnostic querying; and an expressive strongly typed schema language.&lt;/p&gt;
&lt;p&gt;Datalog queries are translated into SQL for execution, taking full advantage of both the application’s rich schema and SQLite’s fast indices and mature SQL query planner.&lt;/p&gt;
&lt;p&gt;You can see more comparisons between Project Mentat and those storage systems &lt;a href=&#34;https://github.com/mozilla/mentat/blob/master/README.md&#34;&gt;in the README&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A proper tutorial will take more space than this blog post allows, but &lt;a href=&#34;https://github.com/mozilla/mentat/blob/ce67644fd5a62af0efbff5a82558c67a3f12ffcd/test/js/tests.js&#34;&gt;you can see a brief example in JS&lt;/a&gt;. It looks a little like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;// Open a database.  
let db = await datomish.open(&amp;quot;/tmp/testing.db&amp;quot;);

// Make sure we have our current schema.  
await db.ensureSchema(schema);

// Add some data. Note that we use a temporary ID (the real ID  
// will be assigned by Mentat).  
let txResult = await db.transact(\[  
  {&amp;quot;db/id&amp;quot;: datomish.tempid(),  
   &amp;quot;page/url&amp;quot;: &amp;quot;[https://mozilla.org/](https://mozilla.org/)&amp;quot;,  
   &amp;quot;page/title&amp;quot;: &amp;quot;Mozilla&amp;quot;}  
\]);

// Let&#39;s extend our schema. In the real world this would  
// typically happen across releases.  
schema.attributes.push({&amp;quot;name&amp;quot;:        &amp;quot;page/visitedAt&amp;quot;,  
                        &amp;quot;type&amp;quot;:        &amp;quot;instant&amp;quot;,  
                        &amp;quot;cardinality&amp;quot;: &amp;quot;many&amp;quot;,  
                        &amp;quot;doc&amp;quot;:         &amp;quot;A visit to the page.&amp;quot;});  
await db.ensureSchema(schema);

// Now we can make assertions with the new vocabulary  
// about existing entities.  
// Note that we simply let Mentat find which page  
// we&#39;re talking about by URL -- the URL is a unique property  
// -- so we just use a tempid again.  
await db.transact(\[  
  {&amp;quot;db/id&amp;quot;: datomish.tempid(),  
   &amp;quot;page/url&amp;quot;: &amp;quot;[https://mozilla.org/](https://mozilla.org/)&amp;quot;,  
   &amp;quot;page/visitedAt&amp;quot;: (new Date())}  
\]);

// When did we most recently visit this page?  
let date = (await db.q(  
  \`\[:find (max ?date) .  
    :in $ ?url  
    :where  
    \[?page :page/url ?url\]  
    \[?page :page/visitedAt ?date\]\]\`,  
  {&amp;quot;inputs&amp;quot;: {&amp;quot;url&amp;quot;: &amp;quot;[https://mozilla.org/](https://mozilla.org/)&amp;quot;}}));

console.log(&amp;quot;Most recent visit: &amp;quot; + date);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Project Mentat is implemented in ClojureScript, and currently runs on three platforms: &lt;strong&gt;Node&lt;/strong&gt;, &lt;strong&gt;Firefox&lt;/strong&gt; (using Sqlite.jsm), and the &lt;strong&gt;JVM&lt;/strong&gt;. We use DataScript’s excellent parser (thanks to &lt;strong&gt;Nikita Prokopov&lt;/strong&gt;, principal author of DataScript!).&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Addition, January 2017: we are in the process of rewriting Mentat in Rust. More blog posts to follow!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Nick has just finished porting Tofino’s &lt;a href=&#34;https://github.com/mozilla/datomish-user-agent-service&#34;&gt;User Agent Service&lt;/a&gt; to use Mentat for storage, which is an important milestone for us, and a bigger example of Mentat in use if you’re looking for one.&lt;/p&gt;
&lt;p&gt;What’s next?&lt;/p&gt;
&lt;p&gt;We’re hoping to learn some lessons. We think we’ve built a system that makes good tradeoffs: Mentat delivers schema flexibility with minimal boilerplate, and achieves similar query speeds to an application-specific normalized schema. Even the storage space overhead is acceptable.&lt;/p&gt;
&lt;p&gt;I’m sure Tofino will push our performance boundaries, and we have a few ideas about how to exploit Mentat’s schema flexibility to help the rest of the Tofino team continue to move quickly. It’s exciting to have a solution that we feel strikes a good balance between storage rigor and real-world flexibility, and I can’t wait to see where else it’ll be a good fit.&lt;/p&gt;
&lt;p&gt;If you’d like to come along on this journey with us, feel free to take a look at &lt;a href=&#34;https://github.com/mozilla/mentat&#34;&gt;the GitHub repo&lt;/a&gt;, come &lt;a href=&#34;http://tofino-slack-invite.mozilla.io/&#34;&gt;find us on Slack&lt;/a&gt; in &lt;em&gt;#mentat&lt;/em&gt;, or drop me an email with any questions. Mentat isn’t yet complete, but the API is quite stable. If you’re adventurous, consider using it for your next Electron app or Firefox add-on (there’s an example in the GitHub repository)… and please do send us feedback and file issues!&lt;/p&gt;
&lt;h3 id=&#34;acknowledgements&#34;&gt;Acknowledgements&lt;/h3&gt;
&lt;p&gt;Many thanks to Lina Cambridge, Grisha Kruglov, Joe Walker, Erik Rose, and Nicholas Alexander for reviewing drafts of this post.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Different kinds of storage</title>
      <link>/post/software/storage/2016/different-kinds-of-storage/</link>
      <pubDate>Tue, 26 Apr 2016 16:59:29 +0000</pubDate>
      
      <guid>/post/software/storage/2016/different-kinds-of-storage/</guid>
      <description>&lt;p&gt;I’ve been spending most of my time so far on &lt;a href=&#34;https://github.com/mozilla/tofino&#34;&gt;Project Tofino&lt;/a&gt; thinking about how a user agent stores data.&lt;/p&gt;
&lt;p&gt;A user agent is software that mediates your interaction with the world. A web browser is one particular kind of user agent: one that fetches parts of the web and shows them to you.&lt;/p&gt;
&lt;p&gt;(As a sidenote: browsers are incredibly complicated, not just for the obvious reasons of document rendering and navigation, but also because parts of the web need to run code on your machine and parts of it are actively trying to attack and track you. One of a browser’s responsibilities is to keep you safe from the web.)&lt;/p&gt;
&lt;p&gt;Chewing on &lt;a href=&#34;http://redux.js.org/&#34;&gt;Redux&lt;/a&gt;, separation of concerns, and &lt;a href=&#34;http://electron.atom.io/&#34;&gt;Electron&lt;/a&gt;’s process model led to us &lt;a href=&#34;https://github.com/mozilla/tofino/wiki/Profile-service&#34;&gt;drawing a distinction&lt;/a&gt; between a kind of ‘profile service’ and the front-end browser itself, with ‘profile’ defined as the data stored and used by a traditional browser window. You can see the guts of this distinction in &lt;a href=&#34;https://github.com/mozilla/tofino/blob/master/docs/profile-service.md&#34;&gt;some of our development docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The profile service stores full persistent history and data like it. The front-end, by contrast, has a pure Redux data model that’s much closer to what it needs to show UI — &lt;em&gt;e.g.&lt;/em&gt;, rather than all of the user’s starred pages, just a list of the user’s five most recent.&lt;/p&gt;
&lt;p&gt;The front-end is responsible for fetching pages and showing the UI around them. The back-end service is responsible for storing data and answering questions about it from the front-end.&lt;/p&gt;
&lt;p&gt;To build that persistent storage we opted for a mostly event-based model: simple, declarative statements about the user’s activity, stored in SQLite. SQLite gives us durability and known performance characteristics in an embedded database.&lt;/p&gt;
&lt;p&gt;On top of this we can layer various views (materialized or not). The profile service takes commands as input and pushes out diffs, and the storage itself handles writes by logging events and answering queries through views. This is the &lt;a href=&#34;http://codebetter.com/gregyoung/2010/02/16/cqrs-task-based-uis-event-sourcing-agh/&#34;&gt;CQRS concept&lt;/a&gt; applied to an embedded store: we use different representations for readers and writers, so we can think more clearly about the transformations between them.&lt;/p&gt;
&lt;p&gt;Where next?&lt;/p&gt;
&lt;p&gt;One of the reasons we have a separate service is to acknowledge that it might stick around when there are no browser windows open, and that it might be doing work other than serving the immediate needs of a browser window. Perhaps the service is pre-fetching pages, or synchronizing your data in the background, or trying to figure out what you want to read next. Perhaps you can interact with the service from something other than a browser window!&lt;/p&gt;
&lt;p&gt;Some of those things need different kinds of storage. Ad hoc integrations might be best served by a document store; recommendations might warrant some kind of graph database.&lt;/p&gt;
&lt;p&gt;When we look through that lens we no longer have just a &lt;em&gt;profile service&lt;/em&gt; wrapping &lt;em&gt;profile storage&lt;/em&gt;. We have a more general &lt;em&gt;user agent service&lt;/em&gt;, and one of the data sources it manages is your profile data.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Trivial SQL ORMs considered harmful</title>
      <link>/post/software/storage/2016/trivial-sql-orms/</link>
      <pubDate>Mon, 07 Mar 2016 17:18:24 +0000</pubDate>
      
      <guid>/post/software/storage/2016/trivial-sql-orms/</guid>
      <description>&lt;p&gt;Our team has a little “things I learned this week” tradition in our team meetings, and it just blossomed onto our mailing list (async is better!).&lt;/p&gt;
&lt;p&gt;In one such post, Michael pointed to sqldelight, a library to automatically generate Android SQL-handling code for a typed schema and a set of queries.&lt;/p&gt;
&lt;p&gt;I wrote a little screed advising caution, which Margaret suggested would make a good blog post… so here it is, unedited.&lt;/p&gt;
&lt;p&gt;Note that I have nothing against automated schema and query checking, nor against saving error-prone typing; my primary objection here is to the object mapping.&lt;/p&gt;
&lt;p&gt;Michael notes:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It’s a square library that allows you to define your tables &amp;amp; queries in a separate text file and it will auto-generate table creation and methods of querying. To do so, it creates Objects which represent the row of your DB.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;and I reply:&lt;/p&gt;
&lt;p&gt;At the risk of being a negative nelly: broadly speaking I find this kind of trivial ORM to be a terrible design anti-pattern, and I strongly discourage its use for anything but saving some typing before committing a v0. We implemented something like this on the iOS side of the house, and it was a huge pain in the ass to get rid of later.&lt;/p&gt;
&lt;p&gt;If your system is simple enough that you&amp;rsquo;re putting whole objects in and getting whole objects out — that is, a simple ORM is a good fit — &lt;a href=&#34;https://wiki.mozilla.org/Performance/Avoid_SQLite_In_Your_Next_Firefox_Feature&#34; title=&#34;Avoid SQLite&#34;&gt;you should instead be &lt;em&gt;not using SQLite&lt;/em&gt;&lt;/a&gt;.  Serialize your objects to a flat file in JSON and keep them in memory. Up to about 100KB of data, it&amp;rsquo;s better in almost every way. (There are some exceptions, but they&amp;rsquo;re exceptions.)&lt;/p&gt;
&lt;p&gt;For everyone else, your inputs and outputs will differ, or you&amp;rsquo;ll need more control, and so you should run screaming from sqldelight.&lt;/p&gt;
&lt;p&gt;There are at least five reasons why I feel this way. I&amp;rsquo;ll stop at five to avoid writing an epic.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Database tables really come into their own when you &lt;em&gt;join&lt;/em&gt; them: bookmarks against favicons, hockey players against teams and games. If you join them (particularly with left/outer/etc. joins), your ORM needs to bulk up the generated model objects with optional fields; it has to, otherwise it can&amp;rsquo;t represent the result of the join.&lt;/p&gt;
&lt;p&gt;Those optional fields leak throughout your app — hey, is that favicon ID supposed to be set here? Does it need to be set to &lt;code&gt;-1&lt;/code&gt; sometimes? — and make your life unpleasant.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;SELECT \*&lt;/code&gt; is an anti-pattern in database work. You might not need all of the fields, but requesting them all limits the indices that the storage layer can use. A smart storage engine can use compound indices to make some queries with limited projections very fast indeed. Or perhaps you want to get unique values.&lt;/p&gt;
&lt;p&gt;To take sqldelight&amp;rsquo;s example, you should not &lt;code&gt;SELECT \* FROM hockey\_player&lt;/code&gt;; if you need that, slurp a JSON file instead! When populating a list view, you probably want &lt;code&gt;SELECT name, id FROM hockey\_player ORDER BY position&lt;/code&gt;. For a name picker you want &lt;code&gt;SELECT DISTINCT name FROM hockey\_player UNION hockey\_officials&lt;/code&gt;. And so on.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Migrations are a reality when dealing with data storage. sqldelight doesn&amp;rsquo;t seem to address this at all.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Syncability (and backup, and export, and…) are also a reality. A sync system typically has a very different viewpoint on data storage than the frontend — not only does that mean you have a set of fields that only part of the application cares about (which screws up your ORM), it also often means that two parts of the system have utterly different conceptions of seemingly straightforward actions like “delete this thing”. ORMs are (almost by definition) one size fits none.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Getting SQL-based storage — hell, getting &lt;em&gt;any&lt;/em&gt; kind of storage — right is hard. Concurrency, performance, memory usage, and correctness all involve careful attention. Take a read of &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Sqlite.jsm&#34;&gt;the Sqlite.jsm docs&lt;/a&gt; or &lt;a href=&#34;https://github.com/mozilla/firefox-ios/blob/3537ddfa11277d44b98ae96e20e73c068c6feefb/Storage/ThirdParty/SwiftData.swift#L414&#34;&gt;some of Firefox for iOS&amp;rsquo;s database prep code&lt;/a&gt; if you want a hint of this. Libraries that generate data access code can slip past this attention, and that&amp;rsquo;s a bad thing.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</description>
    </item>
    
    <item>
      <title>Syncing and storage on three platforms</title>
      <link>/post/software/storage/2015/syncing-and-storage-on-three-platforms/</link>
      <pubDate>Thu, 24 Dec 2015 18:31:09 +0000</pubDate>
      
      <guid>/post/software/storage/2015/syncing-and-storage-on-three-platforms/</guid>
      <description>&lt;p&gt;As it&amp;rsquo;s Christmas, I thought I&amp;rsquo;d take a moment to write down my reflections on Firefox Sync&amp;rsquo;s iterations over the years. This post focuses on how they actually sync — not the UI, not the login and crypto parts, but how they decide that something has changed and what they do about it.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been working on Sync for more than five years now, on each of its three main client codebases: first &lt;a href=&#34;https://dxr.mozilla.org/mozilla-central/source/services/sync&#34;&gt;desktop&lt;/a&gt; (JavaScript), then &lt;a href=&#34;https://dxr.mozilla.org/mozilla-central/source/mobile/android/services/src/main/java/org/mozilla/gecko/sync&#34;&gt;Android&lt;/a&gt; (built from scratch in Java), and now on &lt;a href=&#34;https://github.com/mozilla/firefox-ios/&#34;&gt;iOS&lt;/a&gt; (in Swift). Desktop&amp;rsquo;s overall syncing strategy is unchanged from its early life as &lt;a href=&#34;https://wiki.mozilla.org/Labs/Weave&#34;&gt;Weave&lt;/a&gt;. Partly as a result of &lt;a href=&#34;https://en.wikipedia.org/wiki/Conway&#39;s_law&#34;&gt;Conway&amp;rsquo;s Law&lt;/a&gt; writ large — Sync shipped as an add-on, built by the Services team rather than the Firefox team, with essentially no changes to Firefox itself — and partly for good reasons, Sync was separate from Firefox&amp;rsquo;s storage components. It uses Firefox&amp;rsquo;s &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Observer_Notifications#Places&#34;&gt;observer notifications&lt;/a&gt; to observe changes, making a note of changed records in what it calls a &lt;a href=&#34;https://dxr.mozilla.org/mozilla-central/source/services/sync/modules/engines.js#25&#34;&gt;Tracker&lt;/a&gt;. This is convenient, but it has obvious downsides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;From an organizational perspective, it&amp;rsquo;s easy for developers to disregard changes that affect Sync, because the code that tracks changes is isolated. For example, desktop Sync still doesn&amp;rsquo;t behave correctly in the presence of fancy Firefox features like Clear Recent History, Clear Private Data, restoring bookmark backups, &lt;em&gt;etc&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Sync doesn&amp;rsquo;t get observer notifications for all events. Most notably, bulk changes sometimes roll-up or omit events, and it&amp;rsquo;s always possible for code to poke at databases directly, leaving Sync out of the loop. If a Places database is corrupt, or a user replaces it manually, Sync&amp;rsquo;s tracking will be wrong. This is almost inevitable when sync metadata doesn&amp;rsquo;t live with the data it tracks.&lt;/li&gt;
&lt;li&gt;Sync doesn&amp;rsquo;t track actual changes; it tracks changed IDs. When a sync occurs, it goes to storage to get a &lt;em&gt;current&lt;/em&gt; representation of the changed record. (If the record is missing, we assume it was deleted.) This makes it very difficult to do good conflict resolution.&lt;/li&gt;
&lt;li&gt;In order to avoid cycles, Sync stops listening for events while it&amp;rsquo;s syncing. That means it misses any changes the user makes during a sync.&lt;/li&gt;
&lt;li&gt;Similarly, it doesn&amp;rsquo;t see changes that happen before it registers its observers, &lt;em&gt;e.g.&lt;/em&gt;, during the first few seconds of using the browser.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Beyond the difficulties introduced by a reliance on observers, desktop Sync took some shortcuts&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;: it applies incoming records directly and non-transactionally to storage, so an interrupted sync leaves local storage in a partial state. That&amp;rsquo;s usually OK for unstructured data like history — it&amp;rsquo;ll try again on the next sync, and eventually catch up — but it&amp;rsquo;s a bad thing for &lt;a href=&#34;https://bugzilla.mozilla.org/show_bug.cgi?id=814801&#34;&gt;something structured like bookmarks&lt;/a&gt;, and can still be surprising elsewhere (&lt;em&gt;e.g.&lt;/em&gt;, passwords that aren&amp;rsquo;t consistent across your various intranet pages, form fields that are mismatched so you get your current street address and your previous city and postal code).&lt;/p&gt;
&lt;p&gt;During the last days of the Services team, Philipp, Greg, myself, and others were rethinking how we performed syncs. We settled on a &lt;a href=&#34;https://wiki.mozilla.org/CloudServices/Sync/FxSync/StoreRedesign&#34;&gt;repository-centric approach&lt;/a&gt;: records were piped between repositories (remote or local), abstracting away the details of how a repository figured out what had changed, and giving us the leeway to move to a better internal structure.&lt;/p&gt;
&lt;p&gt;That design never shipped on desktop, but it was the basis for our Sync implementation on Android.&lt;/p&gt;
&lt;p&gt;Android presented some unique constraints. Again, Conway&amp;rsquo;s Law applied, albeit to a lesser extent, but also the structure of the running code had to abide by Android&amp;rsquo;s ContentProvider/SyncAdapter/Activity patterns.&lt;/p&gt;
&lt;p&gt;Furthermore, Fennec was originally planning to support Android&amp;rsquo;s own internal bookmark and history storage, so its internal databases mirrored that schema. You can still see the fossilized remnants of that decision &lt;a href=&#34;https://dxr.mozilla.org/mozilla-central/source/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserHistoryDataAccessor.java&#34;&gt;in the codebase&lt;/a&gt; today. When that plan was nixed, the schema was already starting to harden. The compromise we settled on was to use modification timestamps and deletion flags in Fennec&amp;rsquo;s content providers, and use those to extract changes for Sync in a repository model.&lt;/p&gt;
&lt;p&gt;Using timestamps as the basis for tracking changes is a common error when developers hack together a synchronization system. They&amp;rsquo;re convenient, but &lt;a href=&#34;http://opensignal.com/reports/timestamps/&#34;&gt;client clocks are wrong surprisingly often&lt;/a&gt;, jump around, and lack granularity. Clocks from different devices shouldn&amp;rsquo;t be compared, but we do it anyway when reconciling conflicts. Still, it&amp;rsquo;s what we had to work with at the time.&lt;/p&gt;
&lt;p&gt;The end result is over-engineered, fundamentally flawed, still directly applies records to storage, but works well enough. We have seen dramatically fewer bugs in Android Sync than we saw in desktop Sync between 2010 and 2012. I attribute some of that simply to the code having been written for production rather than being a Labs project (the desktop bookmark sync code was particularly flawed, and Philipp and I spent a lot of time making it better), some of it to lessons learned, and some of it to better languages and tooling — Java and Eclipse produce code with fewer silly bugs&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; than JavaScript and Vim.&lt;/p&gt;
&lt;p&gt;On iOS we had the opportunity to learn from the weaknesses in the previous two implementations.&lt;/p&gt;
&lt;p&gt;The same team built the frontend, storage, and Sync, so we put logic and state in the right places. We track Sync-related metadata directly in storage. We can tightly integrate with bulk-deletion operations like Clear Private Data, and change tracking doesn&amp;rsquo;t rely on timestamps: it&amp;rsquo;s an integral part of making the change itself.&lt;/p&gt;
&lt;p&gt;We also record enough data to do proper three-way merges, which avoids a swath of quiet data loss bugs that have plagued Sync over the years (&lt;em&gt;e.g.&lt;/em&gt;, recent password changes being undone).&lt;/p&gt;
&lt;p&gt;We incrementally apply chunks of records, downloaded in batches, so we rarely need to re-download anything in the case of mid-sync failures.&lt;/p&gt;
&lt;p&gt;And we buffer downloaded records where appropriate, so the scary part of syncing — actually changing the database — can be done locally with offline data, even within a single transaction.&lt;/p&gt;
&lt;p&gt;Storage on iOS is significantly more involved as a result: we have sync_status columns on each table, and typically have two tables per datatype to track the original shared parent of a row. Bookmark sync is shaping up to involve &lt;em&gt;six&lt;/em&gt; tables. But the behavior of the system is dramatically more predictable; this is a case of modeling essential complexity, not over-complicating. So far the bug rate is low, and our visibility into the interactions between parts of the code is good — for example, it&amp;rsquo;s just not possible for Steph to implement bulk deletions of logins without having to go through the &lt;a href=&#34;https://github.com/mozilla/firefox-ios/blob/master/Storage/Logins.swift#L501&#34;&gt;BrowserLogins protocol&lt;/a&gt;, which does all the right flipping of change flags.&lt;/p&gt;
&lt;p&gt;In the future we&amp;rsquo;re hoping to see some of the work around batching, use of in-storage tracking flags, and &lt;a href=&#34;https://bugzilla.mozilla.org/show_bug.cgi?id=1098501&#34;&gt;three-way merge&lt;/a&gt; make it back to Android and eventually to desktop. Mobile first!&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;My feeling is that Weave was (at least from a practical standpoint) originally designed to sync two desktops with good network connections, using cheap servers that could die at any moment. That attitude doesn&amp;rsquo;t fit well with modern instant syncing between your phone, tablet, and laptop!&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;For example, Sync&amp;rsquo;s tab record format, defined by the desktop code, includes a time last used. &lt;a href=&#34;https://docs.services.mozilla.com/sync/objectformats.html#id8&#34; title=&#34;Sync object formats&#34;&gt;Sometimes this is a string, and sometimes it&amp;rsquo;s an integer&lt;/a&gt;. Hooray JavaScript![/&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
  </channel>
</rss>
