<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>distributed systems · michaelprimeaux.com</title><link>https://michaelprimeaux.com/en/tags/distributed-systems/</link><description>Parallel and Distributed Systems</description><generator>Hugo</generator><language>en</language><managingEditor>michael@michaelprimeaux.com (Michael Primeaux)</managingEditor><webMaster>michael@michaelprimeaux.com (Michael Primeaux)</webMaster><copyright>&#169; 2026 Michael Primeaux</copyright><lastBuildDate>Fri, 22 May 2026 00:06:00 +0000</lastBuildDate><atom:link href="https://michaelprimeaux.com/en/tags/distributed-systems/index.xml" rel="self" type="application/rss+xml"/><image><url>https://michaelprimeaux.com/sixafter-logo-150x150.png</url><title>michaelprimeaux.com</title><link>https://michaelprimeaux.com/en/</link></image><item><title>Three Identifier Constructions for a Human Research Pipeline: Hash, HMAC, NanoID</title><link>https://michaelprimeaux.com/en/posts/2026-05-09-human-research-pipeline/</link><pubDate>Fri, 08 May 2026 00:06:00 +0000</pubDate><author>michael@michaelprimeaux.com (Michael Primeaux)</author><guid>https://michaelprimeaux.com/en/posts/2026-05-09-human-research-pipeline/</guid><description>&amp;ldquo;If you think cryptography will solve your problem, then you don&amp;rsquo;t understand cryptography and you don&amp;rsquo;t understand your problem.&amp;rdquo; — Roger Needham
A few colleagues and I recently revisited a familiar debate. In most data systems, identifier generation is treated as undifferentiated infrastructure—one library, one default, applied everywhere. The implicit assumption is that a sufficiently random identifier is a sufficient answer to whatever question the system happens to ask of it; that the differences between identifier classes—surrogate keys, content fingerprints, anonymized handles, span identifiers, request correlations—are differences of usage rather than differences of construction. That assumption is convenient. It survives precisely until the system encounters constraints that aren&amp;rsquo;t engineering constraints.</description><content:encoded><![CDATA[<blockquote><p><em>&ldquo;If you think cryptography will solve your problem, then you don&rsquo;t understand cryptography and you don&rsquo;t understand your problem.&rdquo;</em>
— Roger Needham</p>
</blockquote>
<p>A few colleagues and I recently revisited a familiar debate. In most data systems, identifier generation is treated as undifferentiated infrastructure—one library, one default, applied everywhere. The implicit assumption is that a sufficiently random identifier is a sufficient answer to whatever question the system happens to ask of it; that the differences between identifier classes—surrogate keys, content fingerprints, anonymized handles, span identifiers, request correlations—are differences of <em>usage</em> rather than differences of <em>construction</em>. That assumption is convenient. It survives precisely until the system encounters constraints that aren&rsquo;t engineering constraints.</p>
<p>For the past several years, I&rsquo;ve designed and currently operate a clinical research data pipeline serving multiple concurrent 
<a href="https://en.wikipedia.org/wiki/Institutional_review_board" class="link-external" rel="noopener noreferrer" target="_blank">Institutional Review Board
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 (IRB)-approved studies against 
<a href="https://en.wikipedia.org/wiki/Electronic_health_record" class="link-external" rel="noopener noreferrer" target="_blank">Electronic Health Record
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 (EHR)-sourced data at production scale. Though, implementation specifics are abstracted from this post, I want to highlight the architectural patterns. In human-subjects research, identifier construction stops being a performance choice and becomes a <em>regulatory</em> and <em>ethical</em> primitive.</p>
<p>Why? Because the constraints are different from those that arise in commercial systems. Re-identification resistance is enforceable under the 
<a href="https://en.wikipedia.org/wiki/Health_Insurance_Portability_and_Accountability_Act" class="link-external" rel="noopener noreferrer" target="_blank">Health Insurance Portability and Accountability Act
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 (HIPAA) and the 
<a href="https://en.wikipedia.org/wiki/Common_Rule" class="link-external" rel="noopener noreferrer" target="_blank">Common Rule
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
. Per-study isolation is an IRB requirement. Provenance 
<a href="https://www.ietf.org/rfc/rfc2119.txt" class="link-external" rel="noopener noreferrer" target="_blank">MUST
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 survive transformation but MUST NOT enable cross-study linkage. Workflows that depend on the operator&rsquo;s <em>inability</em> to reverse a transformation can&rsquo;t be retrofitted onto a single global identifier scheme; they have to be built into the construction itself. None of these properties emerge from a generic randomness primitive applied uniformly to every identifier the pipeline produces.</p>
<p>So let me state the pattern plainly. A real human-research pipeline needs at least three distinct identifier constructions. Each answers a different question. Each lives at a different boundary. Treating identifier generation as a single problem with a single answer breaks one architectural pillar in service of another. The failure mode isn&rsquo;t that any single construction is wrong, but that <em>no single construction is right for every boundary the pipeline must respect.</em></p>
<h2 id="three-pillars-three-constructions" class="heading">Three Pillars, Three Constructions
    <a class="heading__anchor" href="#three-pillars-three-constructions" tabindex="-1" aria-hidden="true"
        title="Link to this section">
<svg class="icon icon--hash" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M5 9h14M5 15h14M10 3.5 8 20.5M16 3.5l-2 17"/>
</svg>
</a>
</h2>
<p>A human-subjects research pipeline must answer three questions of every row that flows through it. The questions are independent and every row is interrogated by the following pillars:</p>
<ol>
<li>
<p><strong>Fidelity</strong>. Is this row the same as before, or has it changed? A pipeline that can&rsquo;t answer this can&rsquo;t deduplicate, can&rsquo;t detect drift, and can&rsquo;t reason about whether a transformation preserved meaning. Fidelity is the substrate on which idempotency, change detection, and lineage are built. Without it, every other property the pipeline claims to provide is conjectural.</p>
</li>
<li>
<p><strong>Governance</strong>. Can this row be re-linked to a person, or to the same person across studies? In a research context, the answer must be no, and the answer must be <em>enforceable</em> rather than promised. Policy is necessary but not sufficient; if the construction permits re-linkage, then policy is the only thing preventing it, and policy can be circumvented, misconfigured, or socially engineered. Governance asks whether the construction itself denies the operations that policy forbids.</p>
</li>
<li>
<p><strong>Provenance</strong>. Which pipeline run produced this row, from which source, on which day, by which transformation? Provenance is the operational substrate of accountability. It allows any finding to be traced back to its derivation, any anomaly to be localized to a stage and a run, any audit to reconstruct what happened without depending on the cooperation of the system that produced the result.</p>
</li>
</ol>
<p>Each question is answered by a different cryptographic construction at a different boundary:</p>
<ul>
<li>Content-addressed 
<a href="https://en.wikipedia.org/wiki/SHA-2" class="link-external" rel="noopener noreferrer" target="_blank">SHA-256
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 hashing answers <strong>fidelity</strong> at the ingest boundary.</li>
<li>Per-tenant keyed 
<a href="https://en.wikipedia.org/wiki/HMAC" class="link-external" rel="noopener noreferrer" target="_blank">HMAC
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 hashing answers <strong>governance</strong> at the publication boundary.</li>
<li>
<a href="https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator" class="link-external" rel="noopener noreferrer" target="_blank">CSPRNG
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
-backed identifier generation answers <strong>provenance</strong> at the operational layer.</li>
</ul>
<p>The constructions are not interchangeable. Using any one of them outside its intended boundary either fails the question its boundary is supposed to answer or undermines a pillar that some other boundary was responsible for. Let me walk each in turn, identify the constraint it answers, and explain why the substitution of a generic identifier scheme is an architectural mistake rather than a minor inefficiency.</p>
<h2 id="fidelity-at-the-ingest-boundary" class="heading">Fidelity at the Ingest Boundary
    <a class="heading__anchor" href="#fidelity-at-the-ingest-boundary" tabindex="-1" aria-hidden="true"
        title="Link to this section">
<svg class="icon icon--hash" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M5 9h14M5 15h14M10 3.5 8 20.5M16 3.5l-2 17"/>
</svg>
</a>
</h2>
<p>At the ingest boundary, the pipeline receives raw data from a source system that doesn&rsquo;t coordinate with the pipeline about identity. Files arrive on a schedule, are written to a lake in raw form, and become the substrate from which everything downstream is derived. The question fidelity asks of every row at this boundary is whether it&rsquo;s the same row that arrived yesterday, last week, or in any prior incremental load. If the pipeline can&rsquo;t answer that question cheaply and unambiguously, then:</p>
<ul>
<li>Deduplication becomes guesswork.</li>
<li>Idempotency becomes a hope.</li>
<li>The lake&rsquo;s role as the recovery anchor for the entire pipeline is compromised.</li>
</ul>
<p>The construction that answers this question is a SHA-256 hash computed over the row&rsquo;s sorted <em>business columns</em> at the moment of lake ingestion. Simply put, &ldquo;business columns&rdquo; are all columns except for metadata columns that were added by the pipeline itself. The hash is written into a metadata column that is then carried unchanged through every downstream stage. It MUST NOT be recomputed at the warehouse boundary, must not be recomputed after foreign-key re-keying at segmentation, and must not be recomputed under any condition the pipeline operator may otherwise consider reasonable. The construction is content-addressed and immutable.</p>
<p>The non-randomness of this identifier is the point. Two rows with identical business-column content produce identical hashes. Two rows with any meaningful difference produce different hashes with overwhelming probability. The hash is unique by virtue of its <em>content</em>, not by virtue of its randomness, and that property is what makes change detection tractable across overlapping daily exports without requiring the source system to participate in a coordination protocol it was never designed to support. A randomized identifier would be wrong here in the strongest sense: it would foreclose exactly the property the boundary needs.</p>
<p>The same property that enables change detection also serves as a duplicate pre-filter. Rows that share identical business-column values are duplicates by definition, and the hash exposes that fact at the moment of ingestion rather than deferring it to a downstream reconciliation step. Duplicate detection becomes a comparison of fixed-width hashes rather than a comparison of arbitrarily wide row contents, and the cost of identifying duplicates collapses to the cost of comparing two values. The work the pipeline would otherwise have to do further downstream is done once, at the boundary, by the construction itself.</p>
<p>The hash also serves as the first link in the lineage chain that fidelity, governance, and provenance all eventually depend upon. Any row anywhere in the pipeline can be traced back to its source by following the hash. Any divergence between a stage&rsquo;s input and its output can be diagnosed by comparing hashes. Any claim that two rows in different marts derive from the same source row is verifiable without consulting either source or mart, because the hash is the same in both places. The hash is the substrate; everything else is built on top of it.</p>
<p>A subtlety worth naming is that the hash construction itself sits on a hot path. At ingest scale, tens of millions of rows pass through the pipeline on every full load, and the hash is computed for every one of them. The disciplines that apply to identifier generation under sustained concurrency apply equally here: allocation behavior MUST be controlled, primitive selection MUST avoid implicit coordination, and the cost of the construction MUST NOT become the dominant cost of ingestion. The arguments I developed in 
<a href="https://michaelprimeaux.com/posts/2024-11-12-optimizing-nano-id-generation-in-go/">my earlier post on NanoID hot-path optimization</a>
 generalize directly to hash construction in pipelines of this kind. The construction is different; the discipline is the same.</p>
<h2 id="governance-at-the-publication-boundary" class="heading">Governance at the Publication Boundary
    <a class="heading__anchor" href="#governance-at-the-publication-boundary" tabindex="-1" aria-hidden="true"
        title="Link to this section">
<svg class="icon icon--hash" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M5 9h14M5 15h14M10 3.5 8 20.5M16 3.5l-2 17"/>
</svg>
</a>
</h2>
<p>The boundary between the warehouse and the data marts is where the pipeline transitions from internal computation to <em>external publication</em>. Inside the warehouse, identity is managed by the pipeline operator, and the SHA-256 hash from the ingest boundary serves every internal purpose well. Outside the warehouse, identity becomes a research artifact, and a fundamentally different requirement asserts itself: the identifier that reaches the mart MUST NOT enable re-identification of the underlying subject, and it 
<a href="https://www.ietf.org/rfc/rfc2119.txt" class="link-external" rel="noopener noreferrer" target="_blank">MUST NOT
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 enable cross-mart linkage of the same subject across separate research projects.</p>
<p>Both requirements are properties of the <em>construction</em>, not properties of the <em>data</em>. A SHA-256 hash carried untouched into a mart would satisfy neither. Why? The hash is deterministic; identical inputs produce identical outputs; an attacker with sufficient knowledge of the source schema could attempt to reconstruct the input space and brute-force the mapping. More immediately, the same warehouse row would carry the same hash into every mart, and any researcher with access to two marts could link rows trivially by comparing hashes. The construction permits exactly the operations that governance forbids.</p>
<p>Stay with me—the answer here is a small but architecturally decisive change. The construction that answers governance at this boundary is HMAC-SHA256, keyed with a per-mart secret stored in a 
<a href="https://en.wikipedia.org/wiki/Hardware_security_module" class="link-external" rel="noopener noreferrer" target="_blank">Hardware Security Module
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 (HSM)-backed secrets manager and never exposed to the pipeline operator. At the segmentation stage that produces each mart, the warehouse-side SHA-256 hash is replaced by HMAC-SHA256(<code>warehouse-hash</code>, <code>per-mart-secret</code>). The result is a new identifier that is:</p>
<ul>
<li><strong>Deterministic</strong> with respect to the same inputs.</li>
<li><strong>Cryptographically opaque</strong> without the secret.</li>
<li><strong>Distinct across marts</strong>, because the secret is distinct across marts.</li>
</ul>
<p>This construction has a property that&rsquo;s easy to overlook and architecturally decisive: the HMAC operates on the <em>SHA-256 hash</em>, not on the <em>raw business columns</em>. An attacker who somehow obtained a per-mart secret would not face the relatively constrained problem of inverting the HMAC over a known schema of patient identifiers; they would face the problem of inverting it over the <em>full output space of SHA-256</em>, which is 2^256 possible inputs. A dictionary attack against patient identifiers, plausible in principle against a single-layer keyed hash, is eliminated by construction. The two-layer design—content hash first, keyed hash of the hash second—is what makes the cryptographic claim survive contact with a realistic threat model.</p>
<p>The architectural consequence is that the publication boundary becomes a place where identity is not merely <em>transformed</em> but <em>severed</em>. The original SHA-256 hash never reaches the mart. The mart receives the HMAC, and only the HMAC, as its identifier for that row. The pipeline operator, who has access to the warehouse and to the segmentation code, can&rsquo;t reverse the transformation, because the operator doesn&rsquo;t have the per-mart secret. The secret lives in an HSM-backed store that the operator can use but can&rsquo;t extract. The same warehouse row, written into three different marts, carries three different identifiers, and no party—including the pipeline operator—can establish that the three identifiers refer to the same subject without simultaneous access to all three secrets and the cooperation of the secrets manager.</p>
<p>This is what makes the honest-broker workflow <em>cryptographically</em> tenable rather than <em>merely operationally promised</em>. In human-subjects research, there are workflows where the operator&rsquo;s inability to reverse the transformation is structurally required: the team that operates the pipeline MUST be able to publish the data, but MUST NOT be able to re-identify the subjects whose data it published. Under a single-layer construction, the operator can always be compelled, suborned, or compromised into reversing the mapping. Under the two-layer construction described here, the operator simply doesn&rsquo;t have the capability to reverse, and therefore can&rsquo;t be compelled to exercise it. The construction enforces what policy could only request.</p>
<p>The bridge to my 
<a href="https://michaelprimeaux.com/posts/2025-07-20-aes-ctr-drbg/">earlier work on AES-CTR-DRBG</a>
 is direct. In that post I argued that deterministic cryptographic constructions belong in environments where reproducibility, auditability, and explicit state evolution are required. The HMAC-at-publication pattern is the same disposition applied to <em>identity</em> rather than to byte streams. The construction is deterministic with respect to its inputs; the state that determines its output evolves only when the per-mart secret is rotated; the auditor can reason about what the construction produces without having to reverse-engineer the library that implements it. Configuration is a contract. Boundaries are explicit. The cryptographic posture doesn&rsquo;t change mid-flight.</p>
<p>A randomized identifier would be wrong here in a different way than at the ingest boundary, but no less wrong. A randomized identifier is non-deterministic; the same row processed twice would produce different identifiers; the mart&rsquo;s referential integrity would collapse on the first re-run. What the boundary needs is <em>deterministic-but-cryptographically-opaque</em>, and the only construction that delivers both properties is a keyed hash.</p>
<h2 id="provenance-at-the-operational-layer" class="heading">Provenance at the Operational Layer
    <a class="heading__anchor" href="#provenance-at-the-operational-layer" tabindex="-1" aria-hidden="true"
        title="Link to this section">
<svg class="icon icon--hash" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M5 9h14M5 15h14M10 3.5 8 20.5M16 3.5l-2 17"/>
</svg>
</a>
</h2>
<p>The constructions described so far handle the headline identity story—the identity of the rows themselves. They&rsquo;re computed once per row and carried thereafter. They&rsquo;re also, by a substantial margin, <em>not</em> the identifiers the pipeline generates most often.</p>
<p>A pipeline of any operational seriousness produces identifiers continuously for things that <em>aren&rsquo;t</em> rows. Pipeline runs need identifiers. Stage executions need identifiers. Batch tasks under a thread pool need identifiers. Observability spans, if the pipeline exports traces, need identifiers. Synthetic sentinel rows injected at segmentation need identifiers distinguishable from real source identifiers. Project allocations need identifiers that survive across systems. Honest-broker requests need identifiers that the requesting system, the secrets manager, and the provisioning workflow can all reference unambiguously. Quality diagnostic runs need identifiers for the audit trail that links them to the pipeline run that triggered them.</p>
<p>These identifiers have a constraint regime distinct from anything the row-level constructions answer:</p>
<ol>
<li>They are allocated at high concurrency, often from many worker threads or pods simultaneously, with no central allocator available.</li>
<li>They MUST be collision-resistant by virtue of their construction, because no coordinator exists to detect and resolve a collision after the fact.</li>
<li>They MUST be URL-safe and human-tractable, because they will appear in logs, dashboards, audit trails, and the bodies of cross-system requests.</li>
<li>They MUST be allocation-cheap, because the pipeline generates them at orders of magnitude higher volume than it generates SHA-256 row hashes or per-mart HMACs.</li>
<li>They MUST NOT leak temporal patterns or sequence structure that an observer could exploit to infer pipeline behavior or correlate identifiers from independent runs.</li>
</ol>
<p>In raw call volume, this class of identifier dominates the SHA-256 and HMAC constructions by orders of magnitude. The hash is computed once per source row at ingest. The HMAC is computed once per row per mart at segmentation. The operational identifiers are generated continuously: on every notebook, on every span, on every parallel task, on every cross-system request. If the construction here is wrong, the cost is paid everywhere the pipeline does any work at all.</p>
<p>This is the design space my 
<a href="https://github.com/sixafter/nanoid" class="link-external" rel="noopener noreferrer" target="_blank">NanoID library
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 was built for, and the disposition that produced it is the disposition that earlier governed its randomness backends. The construction at the call site is identical regardless of the workload—a single function call returning a compact, URL-safe identifier—but the randomness <em>source</em> behind it is configurable, and the choice of source is made per identifier <em>class</em> rather than per <em>call site</em>. The architectural payoff is that the cryptographic posture of the identifier is determined at construction time and survives every downstream invocation without requiring the call site to be aware of it.</p>
<p>For the highest-volume operational identifiers—span identifiers, batch identifiers, stage execution identifiers, the thousands of small handles that a pipeline produces in the course of doing its work—the appropriate backend is the 
<a href="https://michaelprimeaux.com/posts/2025-12-26-prng-chacha/">ChaCha20-based PRNG</a>
 I developed in an earlier post. It&rsquo;s designed for predictable behavior under sustained concurrency, with allocation discipline on the hot path and no shared coordination between worker threads. It doesn&rsquo;t target any particular compliance regime, because none applies at this layer; what applies is the constraint that identifier generation MUST remain invisible in profiling output even at the volumes the pipeline reaches.</p>
<p>For the identifiers that participate in audit, IRB, or compliance trails—project allocations, honest-broker request identifiers, anything that ends up in a Key Vault audit log or a regulatory submission—the appropriate backend is the 
<a href="https://michaelprimeaux.com/posts/2025-07-20-aes-ctr-drbg/">AES-CTR-DRBG</a>
 construction I developed in the post before that. It targets 
<a href="https://csrc.nist.gov/publications/detail/fips/140/2/final" class="link-external" rel="noopener noreferrer" target="_blank">FIPS 140-2
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
-aligned cryptographic randomness with deterministic state evolution, allowing the identifiers it produces to be traceable, auditable, and defensible under scrutiny in ways that a general-purpose PRNG is not. The construction is the same NanoID library, configured with a different randomness source via the same <code>WithRandReader</code> option, called identically at the call site.</p>
<p>The two backends are not competing; they answer two different constraints within the same pipeline, on the same library surface, decided once at construction time. The same disposition that governs SHA-256 and HMAC at their boundaries—<em>the construction is chosen for the boundary, not for the codebase</em>—governs NanoID at the operational layer.</p>
<h2 id="one-library-three-backends" class="heading">One Library, Three Backends
    <a class="heading__anchor" href="#one-library-three-backends" tabindex="-1" aria-hidden="true"
        title="Link to this section">
<svg class="icon icon--hash" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M5 9h14M5 15h14M10 3.5 8 20.5M16 3.5l-2 17"/>
</svg>
</a>
</h2>
<p>The library design that makes this tractable isn&rsquo;t a convenience feature. It&rsquo;s an architectural commitment that allows per-boundary construction choice to coexist with a single call-site surface. NanoID exposes a <code>WithRandReader</code> option that accepts any <code>io.Reader</code> as its randomness source. The choice of source is made once, at generator construction time, and survives every subsequent identifier the generator produces. The call site doesn&rsquo;t change; the cryptographic posture does.</p>
<p>This mirrors the architectural move SHA-256 and HMAC are making at their respective boundaries. Both belong to the SHA-2 family. Both compose with the same underlying hash primitive. The difference between them is whether a per-tenant key is involved, and whether the construction can therefore enforce the cryptographic break that publication requires. The primitive is shared; the construction is chosen for what the boundary MUST guarantee. Same disposition, same shape, different layer.</p>
<p>The negation is instructive. A pipeline that uses <code>uuid_generate_v4()</code> everywhere—or NanoID with a single hardcoded randomness source applied to every identifier it produces—collapses distinctions that the architecture is doing real work to preserve. Surrogate keys for fact tables receive the same construction as project identifiers feeding IRB submissions, which receive the same construction as observability spans. Each is correct for some workload and wrong for at least one other. The pipeline that adopts a single global construction hasn&rsquo;t simplified its identifier story; it has merely declined to make decisions that its boundaries will eventually force it to revisit.</p>
<p>The pipeline that adopts the layered model described here makes those decisions once, at the boundary where each one belongs, and then stops thinking about them.</p>
<ul>
<li>The hash is content-addressed at ingest.</li>
<li>The HMAC is per-tenant keyed at publication.</li>
<li>The NanoID is randomly allocated at the operational layer, with the randomness backend chosen for the identifier class.</li>
</ul>
<p>Each construction is correct for its boundary. Each construction is composable with the others, because the boundaries are explicit and the constructions don&rsquo;t interfere. The architecture isn&rsquo;t more complex than the alternative; it&rsquo;s more honest about the complexity that was already present.</p>
<h2 id="wrapping-it-all-up" class="heading">Wrapping it all up&hellip;
    <a class="heading__anchor" href="#wrapping-it-all-up" tabindex="-1" aria-hidden="true"
        title="Link to this section">
<svg class="icon icon--hash" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M5 9h14M5 15h14M10 3.5 8 20.5M16 3.5l-2 17"/>
</svg>
</a>
</h2>
<p>The earlier articles in this sequence each followed a single constraint to its conclusion. My 
<a href="https://michaelprimeaux.com/posts/2024-11-12-optimizing-nano-id-generation-in-go/">2024 article on NanoID</a>
 followed the hot-path discipline that emerges when identifier generation moves from incidental utility to infrastructure. My 
<a href="https://michaelprimeaux.com/posts/2025-07-20-aes-ctr-drbg/">2025 article on AES-CTR-DRBG</a>
 followed the requirements of compliance, auditability, and explicit state evolution. My article on the 
<a href="https://michaelprimeaux.com/posts/2025-12-26-prng-chacha/">ChaCha20-based PRNG</a>
 followed the constraints of portability, concurrency, and operational simplicity. In each case, a single set of requirements produced a single construction.</p>
<p>This article inverts that move. Given a domain—human-subjects research—that imposes <em>multiple</em> constraint regimes simultaneously, the architectural response isn&rsquo;t a single construction but a layered one. Fidelity, governance, and provenance aren&rsquo;t competing concerns to be balanced against one another. They&rsquo;re independent questions answered by independent constructions at independent boundaries, and the pipeline that respects the independence of the questions can satisfy all three without trading any of them off against the others.</p>
<p>Make no mistake; the constructions themselves are unremarkable. SHA-256 has been a standard primitive for two decades. HMAC has been a standard primitive for longer. CSPRNG-backed identifier generation is a solved problem with well-understood implementations. The architectural work isn&rsquo;t in the <em>choice of primitives.</em> It&rsquo;s in the <em>recognition</em> that each primitive is correct only for the boundary that actually needs the property it provides, and in the <em>discipline</em> of refusing to substitute one for another where the substitution would be easier but wrong.</p>
<p>Good architecture emerges when constraints are 
<a href="https://www.ietf.org/rfc/rfc2119.txt" class="link-external" rel="noopener noreferrer" target="_blank">REQUIRED
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 and followed to their conclusions, even when those conclusions lead to different constructions under different constraints. The earlier articles argued this for randomness. The pipeline architecture described here argues it for <em>identity</em>. The answer is the same in both cases: make the boundaries explicit, choose the construction the boundary needs, and let the disposition do the rest.</p>
<p>My next post will take a step back and explore the data analytics and insights pipeline from an architectural perspective.</p>
<hr>
<p>Implementation: 
<a href="https://github.com/sixafter/nanoid" class="link-external" rel="noopener noreferrer" target="_blank">https://github.com/sixafter/nanoid
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
</p>]]></content:encoded><category>computing</category><category>distributed systems</category><category>software</category><category>cryptography</category><category>research</category><category>data science</category><category>data analytics</category></item><item><title>Randomness Under Different Constraints: A ChaCha20-Based PRNG in Go</title><link>https://michaelprimeaux.com/en/posts/2025-12-26-prng-chacha/</link><pubDate>Thu, 25 Dec 2025 00:06:00 +0000</pubDate><author>michael@michaelprimeaux.com (Michael Primeaux)</author><guid>https://michaelprimeaux.com/en/posts/2025-12-26-prng-chacha/</guid><description>“A system is not determined by the algorithms it uses, but by the assumptions it makes.”
— Leslie Lamport
Randomness appears deceptively uniform in most software systems. A call is made, bytes are returned, and execution proceeds. The details of how those bytes were produced rarely matter—until they do. As with many foundational components, the assumptions baked into a randomness source tend to surface only when systems scale, diversify, or are subjected to constraints they were not originally designed to accommodate.
In the previous article , I explored deterministic cryptographic randomness under compliance and auditability constraints, where explicit state evolution and reproducibility were not optional. That work followed inevitably from environments shaped by regulation and forensic accountability. This article explores a different design space: one where the constraints are imposed not by certification regimes, but by software portability, concurrency, and operational simplicity.
The result is a ChaCha20-based (opens in a new window) pseudorandom number generator (PRNG) designed for predictable behavior in real systems, rather than alignment with formal validation programs.</description><content:encoded><![CDATA[<blockquote><p><em>“A system is not determined by the algorithms it uses, but by the assumptions it makes.”</em><br>
— Leslie Lamport</p>
</blockquote>
<p>Randomness appears deceptively uniform in most software systems. A call is made, bytes are returned, and execution proceeds. The details of how those bytes were produced rarely matter—until they do. As with many foundational components, the assumptions baked into a randomness source tend to surface only when systems scale, diversify, or are subjected to constraints they were not originally designed to accommodate.</p>
<p>In the 
<a href="https://michaelprimeaux.com/posts/2025-07-20-aes-ctr-drbg/">previous article</a>
, I explored deterministic cryptographic randomness under compliance and auditability constraints, where explicit state evolution and reproducibility were not optional. That work followed inevitably from environments shaped by regulation and forensic accountability. This article explores a different design space: one where the constraints are imposed not by certification regimes, but by software portability, concurrency, and operational simplicity.</p>
<p>The result is a 
<a href="https://datatracker.ietf.org/doc/html/rfc8439" class="link-external" rel="noopener noreferrer" target="_blank">ChaCha20-based
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 pseudorandom number generator (PRNG) designed for predictable behavior in real systems, rather than alignment with formal validation programs.</p>
<p>What distinguishes this space from compliance-driven designs is not weaker security assumptions, but different priorities. Software systems are often judged not by formal proofs or certifications, but by how they behave under sustained load, across diverse platforms, and in the presence of concurrency. In those environments, simplicity is not aesthetic—it is operational. Every implicit dependency, hidden allocation, or global coordination point becomes a liability over time.</p>
<h2 id="constraints-shape-constructions" class="heading">Constraints Shape Constructions
    <a class="heading__anchor" href="#constraints-shape-constructions" tabindex="-1" aria-hidden="true"
        title="Link to this section">
<svg class="icon icon--hash" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M5 9h14M5 15h14M10 3.5 8 20.5M16 3.5l-2 17"/>
</svg>
</a>
</h2>
<p>Cryptographic primitives are often discussed as interchangeable building blocks. In practice, the surrounding constraints determine which properties matter and which can be safely ignored. A construction optimized for auditability and deterministic replay will look very different from one optimized for simplicity, portability, and high-throughput concurrent use.</p>
<p>In software-heavy environments—particularly those spanning multiple platforms or deployed as libraries rather than systems—certain requirements surface repeatedly. The randomness source must behave predictably under concurrency. It must avoid surprising allocation behavior. It must scale without coordination bottlenecks. And it must do so without pulling in complex dependencies or imposing policy decisions on its callers.</p>
<p>These are not compliance constraints. They are engineering constraints.</p>
<p>Engineering constraints tend to accumulate quietly. A library that is easy to use in isolation may behave very differently when embedded in larger systems, reused across services, or exercised under high concurrency. Over time, small assumptions compound: about allocation behavior, about shared state, about initialization cost. What begins as a convenient abstraction can become a systemic bottleneck. Designs that survive this environment are rarely the most general ones; they are the ones that make their tradeoffs explicit early.</p>
<p>ChaCha20 fits naturally into this space. As a stream cipher designed for efficient software implementation, it offers strong cryptographic properties without reliance on specialized hardware instructions or platform-specific optimizations. That makes it a practical foundation for a general-purpose PRNG intended to behave consistently across environments.</p>
<h2 id="state-not-entropy-does-the-work" class="heading">State, Not Entropy, Does the Work
    <a class="heading__anchor" href="#state-not-entropy-does-the-work" tabindex="-1" aria-hidden="true"
        title="Link to this section">
<svg class="icon icon--hash" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M5 9h14M5 15h14M10 3.5 8 20.5M16 3.5l-2 17"/>
</svg>
</a>
</h2>
<p>As with any PRNG, the core of the design is not entropy acquisition but state evolution. Entropy enters the system at initialization. From that point forward, output is a function of internal state advancing according to a defined algorithm.</p>
<p>This distinction matters. By treating entropy acquisition as a boundary rather than a continuous process, the generator remains honest about its dependencies. The operating system provides unpredictability at startup. The PRNG provides expansion, isolation, and repeatable behavior thereafter.</p>
<p>ChaCha20’s design reinforces this model. Once keyed and initialized, the cipher produces a stream of pseudorandom bytes derived entirely from its internal state. There are no conditional branches based on environmental input, no opportunistic refreshes, and no hidden coordination with external sources. State advances monotonically. Output follows deterministically.</p>
<p>Thinking in terms of state rather than entropy also clarifies responsibility. Once initialization completes, the generator no longer depends on the operating system or the environment for correctness. The burden shifts entirely to the implementation and its callers. That boundary is important: it allows failures, assumptions, and limitations to be localized rather than diffused across layers. When something goes wrong, there is no ambiguity about where to look.</p>
<p>This makes the generator easier to reason about, test, and integrate. The absence of implicit behavior is not a limitation; it is a deliberate choice that keeps responsibility visible.</p>
<h2 id="concurrency-without-contention" class="heading">Concurrency Without Contention
    <a class="heading__anchor" href="#concurrency-without-contention" tabindex="-1" aria-hidden="true"
        title="Link to this section">
<svg class="icon icon--hash" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M5 9h14M5 15h14M10 3.5 8 20.5M16 3.5l-2 17"/>
</svg>
</a>
</h2>
<p>Concurrency tends to expose the weaknesses of otherwise elegant designs. Shared mutable state invites contention. Global locks introduce unpredictable latency. Per-call initialization imposes overhead that scales poorly as systems grow.</p>
<p>The ChaCha20 PRNG approaches concurrency as a distribution problem rather than a synchronization problem. Independent generator instances maintain independent state. A pooling strategy amortizes initialization costs while avoiding shared counters or global coordination. Each request draws from a generator whose behavior is local, predictable, and isolated from others.</p>
<p>This approach mirrors patterns commonly used in distributed systems: partition state, avoid global serialization, and accept modest duplication in exchange for clarity and scalability.</p>
<p>In practice, this means resisting the temptation to centralize randomness behind a single shared construct. While such designs appear simpler at first, they introduce hidden coupling that surfaces under load. Contention becomes observable latency. Synchronization becomes an availability concern. By allowing each generator instance to evolve independently, the system preserves the same semantic behavior regardless of how many consumers are active.</p>
<h2 id="runtime-semantics-and-predictable-behavior" class="heading">Runtime Semantics and Predictable Behavior
    <a class="heading__anchor" href="#runtime-semantics-and-predictable-behavior" tabindex="-1" aria-hidden="true"
        title="Link to this section">
<svg class="icon icon--hash" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M5 9h14M5 15h14M10 3.5 8 20.5M16 3.5l-2 17"/>
</svg>
</a>
</h2>
<p>Allocation behavior is treated as a semantic concern rather than a micro-optimization. A PRNG that allocates unpredictably during output generation cedes part of its execution model to the runtime.</p>
<p>The effects of these choices are observable in practice. Small reads primarily reflect fixed overhead. Larger reads scale linearly with the amount of data processed, as expected from a stream cipher advancing its internal counter. Under concurrent workloads, latency remains stable because requests are serviced independently rather than serialized behind shared state.</p>
<p>What matters is not the absolute numbers, but their predictability. The behavior matches the design model, rather than fluctuating with runtime conditions.</p>
<p>This predictability has secondary effects that are easy to underestimate. Monitoring becomes more meaningful when latency distributions are stable. Capacity planning becomes tractable when throughput degrades linearly rather than catastrophically. Perhaps most importantly, anomalies become visible. When behavior deviates from expectation, it signals a real change in conditions rather than noise introduced by the runtime.</p>
<h2 id="failure-modes-and-explicit-boundaries" class="heading">Failure Modes and Explicit Boundaries
    <a class="heading__anchor" href="#failure-modes-and-explicit-boundaries" tabindex="-1" aria-hidden="true"
        title="Link to this section">
<svg class="icon icon--hash" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M5 9h14M5 15h14M10 3.5 8 20.5M16 3.5l-2 17"/>
</svg>
</a>
</h2>
<p>Boundaries are explicit. Initialization depends on a trusted entropy source. Once initialized, the generator’s behavior is deterministic and bounded by construction. Errors are surfaced rather than hidden.</p>
<p>Surfacing errors is not merely a defensive choice; it is an architectural one. Silent degradation may preserve uptime in the short term, but it undermines trust in the system over time. By making failure explicit, the generator avoids the pretense of correctness under conditions it was not designed to handle. This makes integration more demanding, but also more honest.</p>
<p>This approach does not attempt to cover every possible threat model. It does not claim resistance to state compromise beyond what the underlying primitive provides. It does not attempt to retrofit compliance semantics onto a software-oriented design.</p>
<h2 id="closing-thoughts" class="heading">Closing Thoughts
    <a class="heading__anchor" href="#closing-thoughts" tabindex="-1" aria-hidden="true"
        title="Link to this section">
<svg class="icon icon--hash" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M5 9h14M5 15h14M10 3.5 8 20.5M16 3.5l-2 17"/>
</svg>
</a>
</h2>
<p>This 
<a href="https://github.com/sixafter/prng-chacha" class="link-external" rel="noopener noreferrer" target="_blank">ChaCha20-based PRNG
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 is used as the randomness source in other systems, including my 
<a href="https://github.com/sixafter/nanoid" class="link-external" rel="noopener noreferrer" target="_blank">NanoID
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 implementation, where predictable behavior, low overhead, and concurrency safety are practical requirements rather than abstract goals.</p>
<p>Randomness is not a single problem. Different environments impose different constraints, and those constraints shape the constructions that make sense within them.</p>
<p>This ChaCha20-based PRNG exists because certain systems demand a randomness source that behaves predictably under concurrency, avoids unnecessary complexity, and makes its assumptions explicit. It treats randomness as evolving state rather than as an opaque service call.</p>
<p>The tension between generality and specificity is unavoidable in systems design. Libraries that attempt to serve every use case often end up serving none particularly well. By contrast, designs that accept their constraints openly tend to age more gracefully. They are easier to reason about, easier to integrate, and harder to misuse.</p>
<hr>
<p>Implementation: 
<a href="https://github.com/sixafter/prng-chacha" class="link-external" rel="noopener noreferrer" target="_blank">https://github.com/sixafter/prng-chacha
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
</p>]]></content:encoded><category>computing</category><category>distributed systems</category><category>software</category><category>cryptography</category></item><item><title>AES-CTR-DRBG in Go: Allocation-Free, Low-Latency, Deterministic Cryptographic Randomness</title><link>https://michaelprimeaux.com/en/posts/2025-07-20-aes-ctr-drbg/</link><pubDate>Sun, 20 Jul 2025 00:06:00 +0000</pubDate><author>michael@michaelprimeaux.com (Michael Primeaux)</author><guid>https://michaelprimeaux.com/en/posts/2025-07-20-aes-ctr-drbg/</guid><description>&amp;ldquo;The purpose of abstraction is not to be vague, but to create a new semantic level in which one can be absolutely precise.&amp;rdquo; — Edsger W. Dijkstra
Randomness occupies an unusual position in software systems. We rely on it for security, for fairness, for simulation, and yet we rarely scrutinize its behavior with the same rigor we apply to storage engines or replication protocols. We accept entropy as something that simply happens, mediated by an operating system call or a library abstraction, and move on.
That approach works—until it doesn’t.</description><content:encoded><![CDATA[<blockquote><p>&ldquo;The purpose of abstraction is not to be vague, but to create a new semantic level in which one can be absolutely precise.&rdquo;
— Edsger W. Dijkstra</p>
</blockquote>
<p>Randomness occupies an unusual position in software systems. We rely on it for security, for fairness, for simulation, and yet we rarely scrutinize its behavior with the same rigor we apply to storage engines or replication protocols. We accept entropy as something that simply <em>happens</em>, mediated by an operating system call or a library abstraction, and move on.</p>
<p>That approach works—until it doesn’t.</p>
<p>When I began working on systems that required both cryptographic correctness and operational predictability, the implicit assumptions around randomness became a liability. Latency mattered. Allocation behavior mattered. Reproducibility mattered. And in certain environments, auditability mattered most of all. The standard tools did not fail outright, but they failed quietly, by obscuring behavior that ought to be explicit.</p>
<p>At its core, the problem is not randomness itself, but how cryptographic state evolves over time—and whether that evolution remains visible, bounded, and predictable.</p>
<p>This article is about addressing that gap.</p>
<h2 id="determinism-is-not-the-enemy" class="heading">Determinism Is Not the Enemy
    <a class="heading__anchor" href="#determinism-is-not-the-enemy" tabindex="-1" aria-hidden="true"
        title="Link to this section">
<svg class="icon icon--hash" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M5 9h14M5 15h14M10 3.5 8 20.5M16 3.5l-2 17"/>
</svg>
</a>
</h2>
<p>In distributed systems, we accept a fundamental axiom: there is no way to guarantee complete knowledge of global state at any given moment. We design for tolerance, convergence, and recovery, not for omniscience; ref 
<a href="https://michaelprimeaux.com/posts/2012-08-05-parallel-and-distributed-system-design-part-1/">Parallel and Distributed System Design</a>
). Randomness, however, often escapes this discipline. It is treated as an oracle—invoked, trusted, and forgotten.</p>
<p>Cryptographic systems take a different view. They do not reject determinism; they constrain it. Given a fixed internal state and a fixed construction, output must follow. This is not a weakness but a requirement. It is precisely what allows us to reason about forward security, backtracking resistance, and compromise scenarios.</p>
<p>AES-CTR-DRBG sits squarely in this space. It provides a deterministic construction based on a well-understood primitive, AES, and defines explicit rules for how internal state evolves over time. Once you accept that premise, several consequences follow naturally. Hidden entropy sources become suspect. Implicit reseeding becomes problematic. Mutable configuration becomes dangerous.</p>
<p>The design space narrows considerably.</p>
<h2 id="deterministic-constructions-and-cryptographic-honesty" class="heading">Deterministic Constructions and Cryptographic Honesty
    <a class="heading__anchor" href="#deterministic-constructions-and-cryptographic-honesty" tabindex="-1" aria-hidden="true"
        title="Link to this section">
<svg class="icon icon--hash" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M5 9h14M5 15h14M10 3.5 8 20.5M16 3.5l-2 17"/>
</svg>
</a>
</h2>
<p>Cryptography is often discussed in terms of strength, but strength alone is not sufficient. A system can employ strong primitives and still behave in ways that are difficult to reason about. When that happens, security becomes an emergent property rather than an explicit one.</p>
<p>Deterministic constructions reject that ambiguity.</p>
<p>An 
<a href="https://github.com/sixafter/aes-ctr-drbg" class="link-external" rel="noopener noreferrer" target="_blank">AES-CTR-DRBG
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 does not attempt to surprise the reader. Given a key, a counter, and a defined update function, the output follows directly. There is no hidden mixing, no opportunistic entropy acquisition, and no conditional behavior based on environmental state. This predictability is not merely convenient; it is foundational. It allows one to reason about compromise without first reverse-engineering library behavior.</p>
<p>Once determinism is accepted as a requirement, architectural freedoms disappear. You can no longer “improve” security by quietly pulling in entropy. You can no longer treat reseeding as an implementation detail. You must decide, explicitly, when state changes and why.</p>
<p>This is not a limitation of AES-CTR-DRBG. It is a discipline imposed by cryptography itself.</p>
<p>These constraints do not arise in isolation. They emerge most clearly in regulated, multi-tenant, and distributed systems, where randomness is no longer an incidental utility but a component subject to scrutiny. In such environments, deterministic and auditable cryptographic randomness becomes a requirement rather than a preference. Reproducibility matters for forensic analysis. Isolation matters for per-tenant security. Predictable resource behavior matters when concurrency and throughput are no longer theoretical concerns.</p>
<p>The standard random primitives in Go do not address this space. They make no guarantees around deterministic output, explicit key management, or resource predictability under concurrency, nor do they target alignment with 
<a href="https://csrc.nist.gov/publications/detail/sp/800-90a/rev-1/final" class="link-external" rel="noopener noreferrer" target="_blank">NIST SP 800-90A
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 or 
<a href="https://csrc.nist.gov/publications/detail/fips/140/2/final" class="link-external" rel="noopener noreferrer" target="_blank">FIPS 140-2
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 requirements. Third-party alternatives often complicate matters further, introducing additional dependencies or obscuring allocation behavior and latency characteristics behind opaque abstractions.</p>
<p>At scale, these gaps become operational risks. System 
<a href="https://en.wikipedia.org/wiki/Pseudorandom_number_generator" class="link-external" rel="noopener noreferrer" target="_blank">PRNGs
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 do not ensure reproducibility. Key lifecycle management and tenant isolation are left to the caller. Failure modes relevant to compliance and auditability remain implicit, even as entropy sources degrade or cryptographic assumptions are violated. What is missing is not another source of randomness, but a generator whose behavior remains explicit, bounded, and inspectable over time.</p>
<p>These constraints define the space for a deterministic random bit generator based on 
<a href="https://en.wikipedia.org/wiki/Advanced_Encryption_Standard" class="link-external" rel="noopener noreferrer" target="_blank">AES
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 in counter mode (
<a href="https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_%28CTR%29" class="link-external" rel="noopener noreferrer" target="_blank">CTR
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
), as specified in NIST SP 800-90A and aligned with FIPS 140-2. The implementation discussed here relies solely on Go’s standard library cryptography and targets allocation-free output and low, predictable latency, making it suitable for high-throughput and regulated environments where correctness and observability are inseparable.</p>
<h2 id="entropy-state-and-the-shape-of-time" class="heading">Entropy, State, and the Shape of Time
    <a class="heading__anchor" href="#entropy-state-and-the-shape-of-time" tabindex="-1" aria-hidden="true"
        title="Link to this section">
<svg class="icon icon--hash" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M5 9h14M5 15h14M10 3.5 8 20.5M16 3.5l-2 17"/>
</svg>
</a>
</h2>
<p>Many practical failures in cryptographic systems arise not from broken primitives, but from blurred boundaries. Entropy acquisition is a boundary. Deterministic generation is another. When the two are conflated, responsibility becomes unclear.</p>
<p>Operating systems are good at collecting entropy. Applications are good at applying policy. A DRBG sits between the two, which is why many implementations quietly collapse the distinction.</p>
<p>I chose not to.</p>
<p>In this design, entropy enters the system at a clearly defined point and nowhere else. The generator consumes seed material and expands it deterministically. If reseeding occurs, it occurs because the caller has decided that new entropy is available and appropriate. This makes the system honest about its dependencies. It also makes it auditable. One can trace exactly when and how entropy influences output, which is a prerequisite for meaningful analysis.</p>
<p>This mirrors a lesson learned repeatedly in distributed systems: implicit coordination is coordination nonetheless. If you depend on entropy, say so.</p>
<p>It is helpful to visualize the generator not as a black box, but as a state machine evolving over time.</p>
<p>Imagine a simple diagram with three persistent elements: <strong>Key</strong>, <strong>Counter (V)</strong>, and <strong>Limits</strong>. At initialization, these are set from seed material and configuration. Each request for output advances the counter deterministically, producing a block of output via AES encryption. The key remains fixed until a reseed event occurs. Limits monotonically decrease as output is generated.</p>
<p>Time, in this model, does not pass in seconds. It passes in blocks generated.</p>
<p>A reseed event resets the key and counter and restores limits. Absent a reseed, state evolution is linear, irreversible, and predictable. There are no side paths, no hidden refreshes, and no conditional jumps based on runtime conditions. The diagram contains no feedback loops other than the explicit reseed boundary.</p>
<p>This mental model is intentionally simple. It allows one to reason about compromise, exhaustion, and recovery without hand-waving.</p>
<p>Reseeding is often presented as a binary feature: enabled or disabled. In practice, it is a temporal question. How long may a generator safely operate on a given internal state? How much output may be derived before assumptions begin to erode?</p>
<p>FIPS-aligned guidance does not answer these questions abstractly; it answers them in terms of limits. Maximum bytes per key. Maximum requests between reseeds. Defined update functions. These limits exist not because AES is fragile, but because systems operate over time, and time introduces exposure.</p>
<p>In a deterministic generator, time is measured not in seconds but in output. Each block generated advances the internal counter. Each advancement is irreversible. By enforcing explicit limits, the generator treats time as a first-class dimension rather than an afterthought.</p>
<p>This perspective aligns closely with how we reason about versioning and state evolution in distributed systems. State does not merely exist; it progresses. If you do not bound that progression, you eventually lose the ability to reason about it.</p>
<p>Prediction resistance is frequently discussed as an absolute good. In reality, it is a trade. Enabling it increases resilience against certain compromise scenarios, but it also introduces new operational dependencies. Reseeding requires entropy. Entropy requires coordination with the environment. Coordination introduces latency and failure modes.</p>
<p>Rather than treating prediction resistance as a default, this design treats it as a conscious decision. When enabled, reseeding behavior is strict and enforced. When disabled, the generator behaves as a pure deterministic expander. Neither mode is inherently superior; each corresponds to a different threat model.</p>
<p>Cryptographic systems fail when they attempt to serve all threat models simultaneously. By forcing the caller to choose, the system avoids pretending that one size fits all.</p>
<h2 id="runtime-semantics-and-predictability" class="heading">Runtime Semantics and Predictability
    <a class="heading__anchor" href="#runtime-semantics-and-predictability" tabindex="-1" aria-hidden="true"
        title="Link to this section">
<svg class="icon icon--hash" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M5 9h14M5 15h14M10 3.5 8 20.5M16 3.5l-2 17"/>
</svg>
</a>
</h2>
<p>Garbage collection is often discussed in terms of performance, but in latency-sensitive systems it also becomes a semantic concern. Allocation introduces nondeterminism: not in output, but in timing. When a generator allocates during its hot path, it cedes control of latency to the runtime.</p>
<p>The effects of these choices are observable in practice. Under serial workloads, small reads complete in tens of nanoseconds, largely reflecting fixed overhead rather than per-byte cost. Larger reads exhibit higher absolute latency, not because of inefficiency, but because AES-CTR scales linearly with the number of blocks processed. Encrypting a few blocks versus a few hundred necessarily occupies different regions of the CPU pipeline, even when the underlying primitive is highly optimized. What matters is that this behavior is predictable and stable under load, matching expectations derived from the algorithm and the memory hierarchy rather than fluctuating with runtime conditions.</p>
<p>Achieving zero allocations in the output path was, therefore, non-negotiable; read &ldquo;
<a href="https://datatracker.ietf.org/doc/html/rfc2119" class="link-external" rel="noopener noreferrer" target="_blank">MUST
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
&rdquo;.</p>
<p>What matters here is not raw speed, but control. A system that allocates implicitly hands part of its execution semantics to the runtime. In many contexts that is acceptable; in some, it is not. When randomness participates in protocol boundaries, scheduling decisions, or security-sensitive operations, latency variance becomes observable behavior. Treating allocation as a semantic choice forces the design to acknowledge that reality. Once allocation is removed from the generation path, timing becomes a property of the algorithm itself rather than a side effect of memory management. That shift restores a degree of predictability that is otherwise difficult to recover.</p>
<p>The decision to make this DRBG allocation-free during generation was not an optimization exercise; it was a correctness constraint. Once generation begins, no heap allocations occur. The caller owns the buffers. The internal state resides in fixed-size structures. The runtime remains uninvolved. Latency variance collapses. Throughput becomes predictable. The generator behaves the same under load as it does in isolation.</p>
<p>Concurrency often exposes design shortcuts. Shared mutable state invites contention; global locks invite collapse. In the context of deterministic randomness, concurrency introduces an additional risk: accidental nondeterminism caused by interleaving.</p>
<p>The solution follows a familiar distributed-systems pattern: partition the state. Each shard maintains its own independent DRBG state. Shards do not share counters, keys, or reseed thresholds. They share only configuration. This trades a small amount of memory for clarity and scalability. Concurrency improves throughput, not entropy.</p>
<p>Determinism remains intact.</p>
<p>Configuration is where many security failures quietly begin. Defaults accrete. Options multiply. Behavior changes without callers noticing. Over time, the configuration surface becomes an implicit API, understood only by convention.</p>
<p>Here, configuration is a contract.</p>
<p>All configuration occurs at construction time. Options are explicit. Once initialized, the generator does not change its behavior. There is no hidden reseeding, no dynamic mutation, no silent fallback. If prediction resistance is enabled, it is enabled because the caller said so.</p>
<p>A generator whose security posture can change mid-flight violates basic architectural discipline. A useful way to evaluate a DRBG is to ask a simple question: if an attacker learns the internal state at time <em>t</em>, what can they infer? With a deterministic construction, the answer is precise. Past output remains secure if the construction provides backtracking resistance. Future output becomes predictable unless new entropy is introduced. These are not surprising conclusions; they are the expected consequences of the model.</p>
<p>What matters is that the model makes these consequences explicit. By avoiding hidden entropy and implicit reseeding, the generator ensures that compromise scenarios are analyzable rather than speculative. An auditor does not need to guess whether the library refreshed itself “recently.” The code tells the story plainly.</p>
<p>In cryptography, clarity is often mistaken for weakness. In practice, it is the opposite.</p>
<h2 id="closing-thoughts" class="heading">Closing Thoughts
    <a class="heading__anchor" href="#closing-thoughts" tabindex="-1" aria-hidden="true"
        title="Link to this section">
<svg class="icon icon--hash" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M5 9h14M5 15h14M10 3.5 8 20.5M16 3.5l-2 17"/>
</svg>
</a>
</h2>
<p>
<a href="https://github.com/sixafter/aes-ctr-drbg" class="link-external" rel="noopener noreferrer" target="_blank">This
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 AES-CTR-DRBG implementation exists because certain systems demand more from randomness than opacity and convenience. They demand predictability, auditability, and explicit control. They demand that design choices surface in code rather than hide behind defaults.</p>
<p>If you need a general-purpose source of entropy, the standard library remains an excellent choice.</p>
<p>If you need deterministic cryptographic randomness whose behavior you can reason about—whose latency you can predict and whose state evolution you can audit—then the design presented here follows inevitably from that requirement. Once randomness is treated as state evolving over time, rather than as an oracle invoked on demand, many familiar shortcuts stop making sense. What remains is a system whose behavior is explicit, bounded, and knowable. Good architectures do not happen by accident. They emerge when constraints are taken seriously and followed to their logical conclusions—even when those conclusions lead to different constructions under different constraints.</p>
<p>This design is not theoretical; the AES-CTR-DRBG implementation described here is used as the underlying randomness source in my 
<a href="https://github.com/sixafter/nanoid" class="link-external" rel="noopener noreferrer" target="_blank">NanoID
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 library, where deterministic behavior and predictable performance are equally important.</p>
<hr>
<p>Implementation: 
<a href="https://github.com/sixafter/aes-ctr-drbg" class="link-external" rel="noopener noreferrer" target="_blank">https://github.com/sixafter/aes-ctr-drbg
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
</p>]]></content:encoded><category>computing</category><category>distributed systems</category><category>software</category></item><item><title>Optimizing Nano ID Generation in Go: Concurrency, Memory, and Precomputation Strategies</title><link>https://michaelprimeaux.com/en/posts/2024-11-12-optimizing-nano-id-generation-in-go/</link><pubDate>Thu, 07 Nov 2024 00:06:00 +0000</pubDate><author>michael@michaelprimeaux.com (Michael Primeaux)</author><guid>https://michaelprimeaux.com/en/posts/2024-11-12-optimizing-nano-id-generation-in-go/</guid><description>“All problems in computer science can be solved by another level of indirection.” — David J. Wheeler
Identifier generation is one of those concerns that quietly disappears into the background of a system. At small scale, it is effectively free. A call to a random number generator, a string conversion, and the work is done. There is little reason to revisit it, and even less reason to question its design. This holds largely because identifier generation tends to be invisible: it sits at the edges of requests, happens quickly, and rarely shows up in profiling output. When it does, it is usually dismissed as noise.</description><content:encoded><![CDATA[<blockquote><p>“All problems in computer science can be solved by another level of indirection.”
— David J. Wheeler</p>
</blockquote>
<p>Identifier generation is one of those concerns that quietly disappears into the background of a system. At small scale, it is effectively free. A call to a random number generator, a string conversion, and the work is done. There is little reason to revisit it, and even less reason to question its design. This holds largely because identifier generation tends to be invisible: it sits at the edges of requests, happens quickly, and rarely shows up in profiling output. When it does, it is usually dismissed as noise.</p>
<p>That assumption holds only as long as the surrounding system remains small. As concurrency increases and identifier generation moves onto hot paths, what was once incidental becomes part of the system’s observable behavior. Identifiers are created everywhere: at request boundaries, inside storage layers, across services that never coordinate with one another. They are infrastructure in the most literal sense—pervasive, unavoidable, and relied upon precisely because they are assumed to be cheap. When they stop being cheap, the cost is paid everywhere.</p>
<p>
<a href="https://github.com/sixafter/nanoid" class="link-external" rel="noopener noreferrer" target="_blank">NanoID
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 fits neatly into this mental model. It produces compact, URL-safe identifiers with reasonable entropy and a simple interface, and in most environments it behaves exactly as expected. It is easy to adopt and difficult to misuse, which reinforces the idea that identifier generation is a solved problem. The difficulty is not with NanoID itself, but with the way its cost profile changes when it is exercised continuously and concurrently.</p>
<p>In Go, the most direct implementation of NanoID draws randomness from <code>crypto/rand.Reader</code>. From a correctness standpoint, this is beyond reproach. The operating system provides high-quality entropy, the guarantees are well understood, and the resulting identifiers are unpredictable in exactly the ways they should be. Under light use, the cost of doing so is effectively invisible, which further entrenches the assumption that there is nothing here worth examining.</p>
<p>Under sustained concurrency, that assumption no longer holds. Each call to <code>crypto/rand</code> crosses into the kernel, sources entropy, fills a buffer, and returns. Temporary buffers are allocated and discarded. None of this is problematic in isolation, but taken together and repeated at scale, it becomes the dominant cost of identifier generation. At that point, “generating an ID” is no longer a small operation. It is a collection of system calls, allocations, and coordination that happens to terminate in a string.</p>
<p>This is not a criticism of <code>crypto/rand</code>. It is doing exactly what it is designed to do. The issue is one of proximity. Entropy acquisition and identifier construction are tightly coupled, even though they serve different purposes. Correctness requires the former. Performance becomes sensitive when it occupies the hottest path of the system.</p>
<p>Concurrency has a way of making these relationships visible. Throughput flattens earlier than expected. Latency becomes uneven. Profilers begin to attribute meaningful time to what was assumed to be trivial. In practice, the profiler does not report time spent “generating IDs”; it reports time spent in the kernel, allocation paths, and garbage collection. Identifier generation becomes visible not because it is conceptually complex, but because it is structurally misplaced.</p>
<h2 id="separating-entropy-from-generation" class="heading">Separating Entropy from Generation
    <a class="heading__anchor" href="#separating-entropy-from-generation" tabindex="-1" aria-hidden="true"
        title="Link to this section">
<svg class="icon icon--hash" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M5 9h14M5 15h14M10 3.5 8 20.5M16 3.5l-2 17"/>
</svg>
</a>
</h2>
<p>At that point, there are two broad directions a system can move. One is to accept the cost and design capacity around it. The other is to separate concerns that were previously implicit. The former tends to entrench friction around an otherwise unremarkable operation. The latter introduces an architectural boundary.</p>
<p>NanoID does not require fresh entropy for every identifier. What it requires is unpredictability. Those two properties are related, but they are not equivalent. Entropy must originate from a trusted source, but once acquired, it can be expanded safely using a cryptographically sound construction. There is no requirement to consult the operating system on every invocation, and doing so ties correctness to cost in a way that becomes increasingly visible under load.</p>
<p>This observation reshapes the implementation. Instead of treating identifier generation as a single indivisible operation, it becomes possible to draw a boundary between entropy acquisition and identifier construction. In practical terms, this means seeding a generator once from <code>crypto/rand</code> and then producing random bytes locally. The operating system remains the root of trust, but it no longer sits in the inner loop.</p>
<h2 id="the-shape-of-the-hot-path" class="heading">The Shape of the Hot Path
    <a class="heading__anchor" href="#the-shape-of-the-hot-path" tabindex="-1" aria-hidden="true"
        title="Link to this section">
<svg class="icon icon--hash" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M5 9h14M5 15h14M10 3.5 8 20.5M16 3.5l-2 17"/>
</svg>
</a>
</h2>
<p>A stream cipher such as ChaCha20 fits this role naturally. It is designed to generate large volumes of pseudorandom output from a fixed key and nonce, and its properties are well understood. Once initialized, it produces random bytes without further system interaction. Used this way, it does not replace the operating system’s entropy source; it amortizes it.</p>
<p>Each generator instance is seeded once using <code>crypto/rand</code> and then used to satisfy multiple NanoID generations. The generator itself is not shared across callers. Sharing would introduce contention and obscure the very boundary the design is trying to establish. Instead, generator instances are treated as independent state machines that can be reused opportunistically.</p>
<p>This is where <code>sync.Pool</code> becomes useful, not as a micro-optimization, but as a way to preserve locality without introducing coordination. Generator instances are pooled so that goroutines can borrow them briefly, generate the required bytes, and return them. There is no shared mutable state and no locking around generation itself. The pool exists solely to reduce repeated setup cost when reuse is inexpensive.</p>
<p>Once entropy is decoupled, other sources of variability become apparent. NanoID generation requires temporary byte buffers—first for random data, then for mapping those bytes into an alphabet. These buffers are uniform in size and short-lived. Allocating and discarding them repeatedly introduces allocation pressure that is unrelated to the semantics of identifier generation. Pooling these buffers follows the same reasoning as pooling generator state: reuse when convenient, allow reclamation when not.</p>
<p>The same discipline applies to configuration. NanoID relies on derived values such as alphabet size, bit masks, and the number of random bytes required to generate an identifier of a given length. These values do not change per call, yet they are often computed as part of the generation path. Computing them once and fixing them at construction time pushes variability outward and keeps the inner loop small and predictable. The generation path becomes a straight-line transformation from random bytes to characters, without branching or recomputation.</p>
<p>At this stage, the hot path is deliberately unremarkable. There are no system calls, no locks, and no dynamic allocation beyond the final string itself. The work performed is exactly the work required to produce an identifier, and nothing else. What remains visible in benchmarks is not overhead, but the irreducible cost of string construction in Go.</p>
<p>The effect of these changes is straightforward to measure. Allocation counts collapse to a single allocation per identifier. Latency stabilizes. Throughput scales with available CPU until saturation. More importantly, behavior becomes predictable. Identifier generation stops appearing as a source of variance and resumes its role as infrastructure.</p>
<p>An implementation of the approach described here is available as an open-source Go module, along with a small command-line tool built on top of it:</p>
<ul>
<li>
<a href="https://github.com/sixafter/nanoid" class="link-external" rel="noopener noreferrer" target="_blank">https://github.com/sixafter/nanoid
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
</li>
<li>
<a href="https://github.com/sixafter/nanoid-cli" class="link-external" rel="noopener noreferrer" target="_blank">https://github.com/sixafter/nanoid-cli
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
</li>
</ul>
<p>What does not change is the security model. Entropy still originates from the operating system. Generator state is seeded from a cryptographically secure source. Unpredictability is preserved. There is no attempt to weaken guarantees in exchange for speed. The improvement comes from respecting boundaries, not from relaxing them.</p>
<p>Identifier generation stops being trivial when it becomes infrastructure. At that point, it deserves the same architectural treatment as any other component that occupies a hot path. Once those boundaries are made explicit, the problem largely resolves itself.</p>
<p>At that point, the choice of entropy source becomes an explicit design decision rather than an incidental one. The NanoID implementation described here does not assume a single generator, nor does it require that entropy be sourced directly from the operating system on every invocation. Instead, it is structured to accept a well-defined pseudorandom generator that is seeded from a trusted source and then exercised locally.</p>
<p>For environments where throughput and steady behavior under concurrency are the primary concerns, a 
<a href="https://michaelprimeaux.com/posts/2025-12-26-prng-chacha/">ChaCha20-based</a>
 generator provides a practical balance. Seeded once from <code>crypto/rand</code>, it offers high-quality pseudorandom output with predictable performance characteristics, making it well suited for systems where identifier generation sits on a hot path and must remain invisible.</p>
<p>In environments where regulatory or compliance requirements apply, particularly those that mandate 
<a href="https://csrc.nist.gov/publications/detail/fips/140/2/final" class="link-external" rel="noopener noreferrer" target="_blank">FIPS 140-2
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 validation, an 
<a href="https://michaelprimeaux.com/posts/2025-07-20-aes-ctr-drbg/">AES-CTR-DRBG</a>
 construction becomes the appropriate choice. In that context, the same architectural separation applies: entropy is sourced from the operating system, expanded via a standards-aligned deterministic generator, and consumed locally. The difference is not architectural, but contractual—the guarantees are defined externally rather than operationally.</p>
<p>The important point is that these choices do not alter the shape of the system. Whether the generator is ChaCha20-based or an AES-CTR-DRBG, the boundary between entropy acquisition and identifier construction remains intact. The cost model is stable, the hot path is predictable, and the guarantees are explicit rather than implicit.</p>
<hr>
<p>Implementation: 
<a href="https://github.com/sixafter/nanoid" class="link-external" rel="noopener noreferrer" target="_blank">https://github.com/sixafter/nanoid
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
</p>]]></content:encoded><category>computing</category><category>distributed systems</category><category>software</category></item><item><title>Parallel and Distributed System Design, Part 2</title><link>https://michaelprimeaux.com/en/posts/2012-09-08-parallel-and-distributed-system-design-part-2/</link><pubDate>Sat, 08 Sep 2012 00:06:00 +0000</pubDate><author>michael@michaelprimeaux.com (Michael Primeaux)</author><guid>https://michaelprimeaux.com/en/posts/2012-09-08-parallel-and-distributed-system-design-part-2/</guid><description>The map is not the territory. &amp;ndash; Alfred Korzybsky in Science and Sanity, 1933
This is the second post in a series where I&amp;rsquo;ll discuss various aspects of parallel and distributed system design. I&amp;rsquo;d like to provide a brief introduction to taxonomy (or classification of data) and eventual consistency as these building blocks are fundamental to parallel and distributed system design.</description><content:encoded><![CDATA[<blockquote><p>The map is not the territory. &ndash; Alfred Korzybsky in Science and Sanity, 1933</p>
</blockquote>
<p>This is the second post in a 
<a href="/posts/2012-08-05-parallel-and-distributed-system-design-part-1/">series</a>
 where I&rsquo;ll discuss various aspects of parallel and distributed system design.  I&rsquo;d like to provide a brief introduction to taxonomy (or classification of data) and eventual consistency as these building blocks are fundamental to parallel and distributed system design.</p>
<p>Extending my thoughts from a 
<a href="/posts/2007-06-03-information-modeling/">post I made some time ago</a>
, in computer science and information modeling, an ontology formally represents knowledge as a set of concepts within a domain, and the relationships among those concepts. It can be used to reason about the entities within that domain and may be used to describe the domain.  Why do we need to know this? Because all parallel and distributed systems require data to operate and, in turn, operate on data.</p>
<h2 id="semantic-ontologies" class="heading">Semantic Ontologies
    <a class="heading__anchor" href="#semantic-ontologies" tabindex="-1" aria-hidden="true"
        title="Link to this section">
<svg class="icon icon--hash" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M5 9h14M5 15h14M10 3.5 8 20.5M16 3.5l-2 17"/>
</svg>
</a>
</h2>
<p>A semantic ontology, or ontology, is a formal explicit description of concepts in a domain of discourse, properties of each concept describing various features and attributes of the concept, and restrictions on properties. An ontology together with a set of individual instances of classes [or objects] constitutes a knowledge base. In reality, there is a very fine line where the ontology ends and the knowledge base begins but let&rsquo;s put that aside for a moment.</p>
<p>An implementation of conceptual ontologies is the Semantic Web [
<a href="http://www.w3.org/standards/semanticweb/" class="link-external" rel="noopener noreferrer" target="_blank">URI
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
]. Simply put, the Semantic Web is the representation of data on the web in which information is given well-defined meaning—“to be a universal medium for the exchange of data”.</p>
<p>The principal technologies of the Semantic Web fit into a set of layered specifications called the 
<a href="http://www.w3.org/RDF/" class="link-external" rel="noopener noreferrer" target="_blank">Resource Description Framework
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 (RDF). The current components of that framework are the RDF Core Model, the RDF Vocabulary Description Language and the Web Ontology Language (
<a href="http://www.w3.org/2007/OWL/wiki/OWL_Working_Group" class="link-external" rel="noopener noreferrer" target="_blank">OWL
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
).  OWL is a descriptive layer built on top of RDF used to model classes, properties, and objects. These languages all build on the foundation of URIs, XML, and XML namespaces.</p>
<p>Classes are the focus of most semantic ontologies. Outlined in most ontology research are the following observations:</p>
<ul>
<li>There is no one correct way to model a domain— there are always viable alternatives. The best solution almost always depends on the application that you have in mind and the extensions that you anticipate.</li>
<li>Ontology development is necessarily an iterative process.</li>
<li>Concepts in the ontology should be close to objects (physical or logical) and relationships in your domain of interest. These are most likely to be nouns (objects) or verbs (relationships) in sentences that describe your domain.</li>
</ul>
<p>Solution domains require, at times, different classifications. Regardless of which classification the solution domain requires, the following rules and considerations have proven effective in designing an efficient information model:</p>
<ul>
<li>Effectively understand the information.</li>
<li>Describe it unambiguously.</li>
<li>Enforce structure and style guidelines.</li>
<li>Allow for efficient storage and retrieval of information.</li>
<li>Keep network communication to a minimum. Don’t over engineer.</li>
</ul>
<p>I cannot overstate the need to understand your information model but always with an understanding of your delivery time frames. All too many times, we are caught up in the perfect model only in the end to take shortcuts to satisfy deadlines. The solution is acceptance and understanding that change is an integral part of any information model.  Temporal and transient taxonomies may be an integral part of your solution domain. Semantic ontologies may be created at runtime to address a special-purpose need where the need itself is transient in nature. For example, in order to fulfill a set of calculations with a high time and computational complexity rating a transient taxonomy might need to be created to provide global access to intermediate calculation results.  This is all too common in financial simulation.</p>
<p>Not surprising is that classes are the focus of most ontologies. So with that, let&rsquo;s move on to a more usual and enjoyable example of a semantic ontology: wine. Wine is a potable liquid produced by at least one maker of type winery, and is made from at least one type of grape. For example, a class of <strong>Wine</strong> represents all wines. Specific wines are instances of this class. The Bordeaux wine in the glass in front of you while you read this post is an instance of the class of Bordeaux wine. A class can have subclasses that represent concepts that are more specific than the superclass. For example, we can divide the class of all wines into red, white, and rose wines. Alternatively, we can divide a class of all wines into sparkling and non-sparkling wines.</p>
<p>We can further describe properties of classes and instances: Chateau Lafite Rothschild Pauillac wine has a full body; it is produced by the Chateau Lafite Rothschild winery. We can have two properties describing the wine, the property <strong>body</strong> with the value <strong>full</strong> and the property <strong>maker</strong> with the value <strong>Chateau Lafite Rothschild</strong> winery. At the class level, we can say that instances of the class <em>Wine</em> will have properties describing their flavor, body, sugar level, the maker of the wine and so on.</p>
<p>All instances of the class Wine, and its subclass Pauillac, have a property <strong>maker</strong> the value of which is an instance of the class Winery. All instances of the class Winery have a property <strong>produces</strong> that refers to all the wines (instances of the class Wine and its subclasses) that the winery produces. The entire example OWL ontology derived for <strong>Wine</strong> can be found 
<a href="http://www.w3.org/TR/owl-guide/wine.rdf" class="link-external" rel="noopener noreferrer" target="_blank">here
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
. I am of course a huge fan.</p>
<p>The strength of your application requires you to understand the data on which it operates—unambiguosly. Though perfection is not key, it is second to paramount. Once you fully understand your information model, then you are able to understand eventual consistency for your application. And with this in mind, let&rsquo;s discuss eventual consistency.</p>
<h2 id="eventual-consistency" class="heading">Eventual Consistency
    <a class="heading__anchor" href="#eventual-consistency" tabindex="-1" aria-hidden="true"
        title="Link to this section">
<svg class="icon icon--hash" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M5 9h14M5 15h14M10 3.5 8 20.5M16 3.5l-2 17"/>
</svg>
</a>
</h2>
<p>Eventual consistency is one of the consistency models used in the domain of parallel programming. It means that given a sufficiently long period of time over which no changes are sent, all updates can be expected to propagate eventually through the system and all the replicas will be consistent. I will remind you from a 
<a href="/posts/2012-08-05-parallel-and-distributed-system-design-part-1/">previous blog entry</a>
 there is no way to guarantee complete knowledge of the current or future state of a distributed system, because knowledge of state changes must be propagated and propagation takes time, during which more state changes may occur. This is an axiom of distributed computing. So how can we begin to reconcile eventual consistency with this axiom? Well, we don&rsquo;t really [
<a href="http://queue.acm.org/detail.cfm?id=1466448" class="link-external" rel="noopener noreferrer" target="_blank">URI
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
].  We design for loose coupling. We design for tolerance. We design for symmetry of algorithms.</p>
<p>At a high level, most mission- and safety-critical distributed applications 
<a href="http://www.ietf.org/rfc/rfc2119.txt" class="link-external" rel="noopener noreferrer" target="_blank">SHOULD
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 strive to achieve these high-level technical requirements:</p>
<ul>
<li>Provide high availability read and write access to information.</li>
<li>Minimize network communication.</li>
<li>Provide a highly available and fault tolerant system that can support an annual uptime guarantee of 99.999 percent , which is equal to 5.256 minutes of annual unscheduled downtime.</li>
<li>Provide instrumentation to aid implementers and customers in their hardware sizing estimates.  Scalability 
<a href="http://www.ietf.org/rfc/rfc2119.txt" class="link-external" rel="noopener noreferrer" target="_blank">MUST
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 be measurable.</li>
<li>Devise a software architecture that scales up by a factor of 10^6. That is, an application&rsquo;s storage and processing capacity can automatically grow by a factor of a million, doing jobs faster 10^6x speed up or doing 10^6 larger jobs in the same time 10^6 scale up, just by adding more resources.</li>
<li>Provide for conflict resolution. Common implementations in this area leverage 
<a href="http://en.wikipedia.org/wiki/Lamport_timestamps" class="link-external" rel="noopener noreferrer" target="_blank">Lamport timestamps
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 and 
<a href="http://en.wikipedia.org/wiki/Vector_clock" class="link-external" rel="noopener noreferrer" target="_blank">vector clocks
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
.</li>
<li>Provide for transient node failure.</li>
</ul>
<p>While the goal is to devise a software architecture that scales up without limits, there has to be some kind of limit: billions of dollars, or giga watts, or just space. So, the more realistic goal is to be able to scale from one node to a million nodes all working on the same problem or same set of problems.</p>
<p>While fundamentally a 
<a href="http://en.wikipedia.org/wiki/Distributed_hash_table" class="link-external" rel="noopener noreferrer" target="_blank">Distributed Hash Table (DHT)
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 is <em>generally</em> well suited for specific classes of decentralized distributed systems, it is certainly not well suited for all. For example, a DHT is well suited for problems where a highly reliable network between nodes is usual. But a DHT is not <em>necessarily</em> well suited for problem domains where frequent node joins and leaves are the norm.  That said, DHTs have been used for <em>routing</em> in many Peer-to-Peer (P2P) implementations [
<a href="/blog/2007/06/04/peer-to-peer-p2p/">URI</a>
]. Even some of the more well known NoSQL implementations leverage DHTs.</p>
<p>But as I mentioned, DHTs are not necessarily well suited for all implementations. When we designed the core replication algorithms for the Microsoft Active Directory (AD) service, a DHT was not chosen. Not only did we want to account for 
<a href="http://en.wikipedia.org/wiki/Byzantine_fault_tolerance" class="link-external" rel="noopener noreferrer" target="_blank">Byzantine
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 failures but we also needed to account for replica failures that lasted for long periods of time (30+ days).  AD subscribes to eventual consistency. Roughly speaking, the replication model of AD is <em>multi-master loose consistency with convergence (eventual consistency)</em>.    In this model, the directory can have many replicas; a replication system propagates changes made at any given replica to all other replicas. The replicas are not guaranteed to be consistent with each other at any particular time (&ldquo;loose consistency&rdquo;), because changes can be applied to any replica at any time (&ldquo;multi-master&rdquo;). If the system is allowed to reach a steady state, in which no new updates are occurring and all previous updates have been completely replicated, all replicas are guaranteed to converge on the same set of values (&ldquo;convergence or eventual consistency&rdquo;).</p>
<p>In addition to the technical requirements listed earlier in this post, we imposed the following requirements on the design of AD replication:</p>
<ul>
<li>To adapt to customer networks, provide flexibility in replication topology including choice of transports.</li>
<li>Provide a fully asynchronous and highly scalable architecture.</li>
<li>Minimize network communication in terms of size and round-trips, support encryption of data, and support transitive (store/forward) transportation of data.</li>
<li>Work well over high-latency communication links.</li>
<li>Operate correctly when encountering changes to the distinguished name (DN) of an object and to discriminate between a deleted object and a new object with the same DN. In other words, we replicate information based on object&rsquo;s universally unique identifier (
<a href="http://en.wikipedia.org/wiki/Universally_unique_identifier" class="link-external" rel="noopener noreferrer" target="_blank">UUID
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
) and not based on DN.</li>
<li>Automatic system topology generation. The system automatically creates (and destroys) &ldquo;short cut&rdquo; replication links between nodes in order to maintain a specific number of hops between any two points.</li>
</ul>
<p>Active Directory uses a state-based approach to replication. This approach is easier to appreciate in contrast with an alternative, log-based replication. In a typical log-based system each master keeps a log of the updates that it originated. The goal of each master is to communicate its log to every other replica. Once a log arrives at a replica, the replica applies the log, bringing its state more up to date.</p>
<p>In state-based replication each master applies updates, both originating and replicated, to its replica as they arrive. As such, replication is not driven from logs stored with the source replica but from current state of the source replica. This state includes information for resolving conflicts (also needed in the log-based approach) and information to avoid sending the full replica on each replication cycle (inherent in the log-based approach.) A state-based approach uses a single mechanism for incremental and full sync, and performs fewer database updates since repeated or conflicting updates to an attribute are collapsed into a single state. The resulting design has several stability-enhancing properties, such as:</p>
<ul>
<li>No matter how long two replication partners have been out of communication, they can always build upon any previous replication they have accomplished. They never have to start over from scratch because somebody&rsquo;s log &ldquo;wrapped around.&rdquo;</li>
<li>If a replica has a &ldquo;hot spot&rdquo; attribute that&rsquo;s being updated frequently for some reason (perhaps a bug in an application or an implementation similar to a performance counter), only the value that&rsquo;s current at the time of replication is sent to a replication partner. Some designs send the full history of all intermediate values thereby multiplying the problem.</li>
<li>The very same mechanism is used to initialize a new replica as is used for bringing a replica up to date with recent changes. Some other designs require two mechanisms.</li>
<li>AD has a simple and robust solution to the &ldquo;time went backwards due to restore from backup&rdquo; problem (also called the &ldquo;back sync&rdquo; problem) that plagues some other systems.</li>
<li>In a replication relationship the destination (i.e. the replica being updated) takes all responsibility for keeping track of how up to date it is. The source (i.e. the replica supplying the updates) takes no responsibility. This design choice avoids a lot of complexity and potential sources of failure; some other systems don&rsquo;t work this way.</li>
<li>In a replication relationship the destination always &ldquo;pulls&rdquo; changes from the source. The source may notify the destination &ldquo;now would be a good time to pull,&rdquo; but if the notification is lost (e.g. because the destination is overloaded or down) the result is longer replication latency, not incorrectness.</li>
<li>When two replicas establish a new replication relationship, that relationship is established in an incremental fashion, so not much work is lost should one replica go down before the relationship becomes complete.</li>
</ul>
<h2 id="impact-on-distributed-applications" class="heading">Impact on Distributed Applications
    <a class="heading__anchor" href="#impact-on-distributed-applications" tabindex="-1" aria-hidden="true"
        title="Link to this section">
<svg class="icon icon--hash" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M5 9h14M5 15h14M10 3.5 8 20.5M16 3.5l-2 17"/>
</svg>
</a>
</h2>
<p>A multi-master distributed system induces several problems on applications.</p>
<ul>
<li><strong>Version Skew</strong>. Version skew occurs when applications read the same object(s) from different replicas before a change has replicated.  Applications reading the remote replica see the unchanged object.  Version skew is an issue when a given application or set of applications use the information in the directory to interoperate.</li>
<li><strong>Partial Updates</strong>. Partial Update occurs when applications read the same set of objects from different replicas while replication is in progress.  Applications at the remote replica see some of the changes but not all.  Note that there is a small window in which partial update can affect an application: the application must start reading objects while inbound replication is in progress, after one or more of the related, changed objects have been received but before all have been received. The time between the updates at the source replica directly affects the size of this window—updates that occur close together in time will be replicated close together in time. Partial update is an issue when an application uses a related set of objects.</li>
<li><strong>Collisions</strong>. Collisions occur when the same properties of two or more replicas of a given object are changed during the same replication interval.  The replication process reconciles the collision; because of reconciliation a user or application may “see” a value other than the one they wrote. A simple example is user address information—if a user changes their mailing address at replica <strong>R-a</strong> and an administrator changes the same mailing address at replica <strong>R-b</strong>, the value ultimately propagated to (<strong>R-a</strong>, <strong>R-b</strong>), and all other replicas will be the value selected by the collision reconciliation mechanism. Collision resolution is an issue for applications that make assumptions about the internal consistency of objects or sets of objects.</li>
</ul>
<p>Though not in scope of this post, applications must accommodate replication latency.  The best way to accommodate replication latency is to design applications to minimize the effects—to tolerate latency.  The ideal distributed application is of course unaffected by replication latency induced state. Other applications must adopt an avoidance or detection strategy as a mechanism to tolerate replication latency.</p>
<p>Many NoSQL systems, such as MongoDB, employ a master/slave topology and so writes are accepted on a single node. Even the newer replica sets in MongoDB adhere to this model. Sharding (or partitioning), of course, is a key consideration in these types of systems. Sharding enables horizontal scaling across multiple nodes. A sharded MongoDB cluster performs automated leader election for the <em>primary</em> node.  Leader election in a ring is another topic in and of itself. So let&rsquo;s wrap this up&hellip;</p>
<h2 id="wrapping-it-all-up" class="heading">Wrapping it all up&hellip;
    <a class="heading__anchor" href="#wrapping-it-all-up" tabindex="-1" aria-hidden="true"
        title="Link to this section">
<svg class="icon icon--hash" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M5 9h14M5 15h14M10 3.5 8 20.5M16 3.5l-2 17"/>
</svg>
</a>
</h2>
<p>Regardless of the underlying durable storage technology, it is of paramount responsibility that you properly classify your information model, understand your consistency model, and develop your application to be highly resilient to failures. It is this latter point—being highly resilient to failures—that is not an easy task and that is precisely why I&rsquo;ll discuss fault tolerance and recovery in my next post.</p>]]></content:encoded><category>distributed systems</category><category>computing</category></item><item><title>Parallel and Distributed System Design, Part 1</title><link>https://michaelprimeaux.com/en/posts/2012-08-05-parallel-and-distributed-system-design-part-1/</link><pubDate>Sun, 05 Aug 2012 00:06:00 +0000</pubDate><author>michael@michaelprimeaux.com (Michael Primeaux)</author><guid>https://michaelprimeaux.com/en/posts/2012-08-05-parallel-and-distributed-system-design-part-1/</guid><description>…since my intention is to write something useful for anyone who understands it, it seemed more suitable to me to search after the effectual truth of the matter, rather than its imagined one. &amp;ndash; Niccolo Machiavelli in The Prince, 1532</description><content:encoded><![CDATA[<blockquote><p>…since my intention is to write something useful for anyone who understands it, it seemed more suitable to me to search after the effectual truth of the matter, rather than its imagined one. &ndash; Niccolo Machiavelli in The Prince, 1532</p>
</blockquote>
<p>While conducting a bit of research on scalability and efficiently storing 
<a href="http://en.wikipedia.org/wiki/Petabyte" class="link-external" rel="noopener noreferrer" target="_blank">large amounts
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 of information, I ran across an 
<a href="http://cacm.acm.org/magazines/2011/6/108666-if-you-have-too-much-data-then-good-enough-is-good-enough/abstract" class="link-external" rel="noopener noreferrer" target="_blank">abstract
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 in the Communications of the ACM written by a friend and former colleague Pat Helland. Pat and I worked at Microsoft together during the late 90s. Our paths naturally crossed given his core research in high performance transaction systems (
<a href="http://www.hpts.ws/" class="link-external" rel="noopener noreferrer" target="_blank">HPTS
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
) and my core work in parallel and distributed systems. Pat recently moved back to San Francisco after 15 years with Microsoft [
<a href="http://blogs.msdn.com/b/pathelland/archive/2011/09/30/leaving-microsoft-and-moving-to-san-francisco.aspx" class="link-external" rel="noopener noreferrer" target="_blank">URI
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
]. For the two years prior to leaving, Pat worked on 
<a href="http://blogs.msdn.com/b/seliot/archive/2010/11/05/cosmos-petabytes-perfectly-processed-perfunctorily.aspx" class="link-external" rel="noopener noreferrer" target="_blank">Cosmos
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
, some of the plumbing for 
<a href="http://www.bing.com" class="link-external" rel="noopener noreferrer" target="_blank">Bing
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
.  It stores hundreds of petabytes of data on tens of thousands of computers.</p>
<p>The 
<a href="http://cacm.acm.org/magazines/2011/6/108666-if-you-have-too-much-data-then-good-enough-is-good-enough/abstract" class="link-external" rel="noopener noreferrer" target="_blank">paper
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
, &ldquo;If You Have Too Much Data, then &lsquo;Good Enough&rsquo; Is Good Enough&rdquo;, is brilliantly written. Pat has always been a wonderful and capable writer with an ability to reduce complex problems into a simple presentation. Similar to his clear and concise description of application models using concepts known as &ldquo;fiefdoms&rdquo; and &ldquo;emissaries&rdquo; in 2002 [
<a href="http://www.pcmag.com/article2/0,2817,31952,00.asp" class="link-external" rel="noopener noreferrer" target="_blank">URI
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
] [
<a href="http://msdn.microsoft.com/en-us/magazine/cc164125.aspx" class="link-external" rel="noopener noreferrer" target="_blank">URI
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
], Pat identifies a world where many of our classic SQL database principles are being eroded by combining too much data each with disparate characteristics.</p>
<p>The non-relational storage technology (a.k.a. &ldquo;NoSQL&rdquo;) movement has of course given rise to the likes of 
<a href="http://www.mongodb.org" class="link-external" rel="noopener noreferrer" target="_blank">MongoDB
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
, 
<a href="http://couchdb.apache.org" class="link-external" rel="noopener noreferrer" target="_blank">CouchDB
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
, and 
<a href="http://redis.io" class="link-external" rel="noopener noreferrer" target="_blank">Redis
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
. Each of these NoSQL technologies have their own unique advantage so judicious evaluation is a 
<a href="http://www.ietf.org/rfc/rfc2119.txt" class="link-external" rel="noopener noreferrer" target="_blank">MUST
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
. The vast amounts of information we process each day in turn has forced us to consider alternate ways to process this information else risk not being able to translate this data into actionable information. Applied technologies such as 
<a href="http://hadoop.apache.org" class="link-external" rel="noopener noreferrer" target="_blank">Hadoop
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 and 
<a href="http://hadoop.apache.org/mapreduce/" class="link-external" rel="noopener noreferrer" target="_blank">MapReduce
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 are fundamental to processing &ldquo;big data&rdquo;. We often employ practices and technologies to enable timely dissemination, such as data analytics, business performance management, data warehousing, dashboards and key performance indicators (KPIs). Regardless of what we choose, the fundamentals of how we react to this vast amount of information must be swift, decisive, and&ndash;more importantly&ndash;accurate in today&rsquo;s global economy.  Being capable of pivoting is essential.</p>
<p><strong>Mobile + Cloud</strong></p>
<p>Mobile and cloud technologies are a winning distributed system solution for many problem domains. But let&rsquo;s not forget this combination is a distributed system. When designing and implementing distributed systems and the data models on which a distributed system operates, organization is indeed paramount; and so is synchronization. For safety- and mission-critical applications, high availability has always been a paramount concern and recent experience with large Internet sites has underscored the need for availability in that domain as well.  Traditional approaches to the problem have made three implicit assumptions:</p>
<ol>
<li>Failure rates of hardware and software are low and improving.</li>
<li>Systems can be modeled for reliability analysis and their failure modes can be predicted.</li>
<li>Human error during maintenance is not a major source of failures.</li>
</ol>
<p>The result is an emphasis on failure avoidance as the path to high availability.  These assumptions are in many cases based on incorrect perceptions of today&rsquo;s environment, and that renewed emphasis should be given to failure recovery.  Even the most highly tested systems occasionally exhibit &ldquo;
<a href="http://en.wikipedia.org/wiki/Uncertainty_principle" class="link-external" rel="noopener noreferrer" target="_blank">Heisenbugs
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
&rdquo;  and suffer from transient or permanent hardware failure and software aging, and human error has empirically been found to account for a nontrivial fraction of catastrophic failures.  The most successful systems have been those that can recover from these unexpected errors because they were designed for recovery.</p>

<blockquote><p>If a problem has no solution, it may not be a problem, but a fact—not to be solved, but to be coped with over time. &ndash; Shimon Peres, 1923</p>
</blockquote>
<p>There is no way to guarantee complete knowledge of the current or future state of a distributed system, because knowledge of state changes must be propagated and propagation takes time, during which more state changes may occur.  This is an axiom of distributed computing.</p>
<p>Tightly coupled systems deal with uncertainty by attempting to eliminate it.  This is done through constraints on updates, requiring all nodes or some majority of nodes to be available before updates can be performed; using distributed locking schemes or single mastering for critical resources, constraining all nodes to be well connected, or some combination of these techniques. “Majority” often involves weighted voting schemes; so “big” nodes can be more influential than “small” nodes. The more tightly coupled the computing nodes in a distributed system are, the lower the scaling limit.</p>
<p>Loosely coupled systems deal with uncertainty by tolerating it. A loosely coupled system allows participating nodes to have differing views of the overall system state and provides algorithms for resolving conflicts.</p>
<p>My choice for parallel and distributed system design is loosely coupled because:</p>
<ol>
<li>Customers  require a highly distributed solution in which parts of the infrastructure can be spread across the internal and public networks and administered locally.</li>
<li>Large customers need to grow in capacity to many millions of transactions per day or to hundreds or thousands of nodes, or both.</li>
<li>Many networks provide only intermittent connectivity to some locations, for example remote oil drilling platforms and ships at sea, so the system 
<a href="http://www.ietf.org/rfc/rfc2119.txt" class="link-external" rel="noopener noreferrer" target="_blank">MUST
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 be tolerant of partly connected or disconnected operation.</li>
</ol>
<p>Tightly coupled solutions are unsuitable for parallel and distributed system design because of the requirements for scalability to a very large numbers of nodes and disconnected operation. The loosely coupled model satisfies all of the above requirements.</p>
<p>I’d like to quickly point out an observation regarding tolerance as it relates to most Internet-aware software. As the World Wide Web Consortium (
<a href="http://www.w3.org" class="link-external" rel="noopener noreferrer" target="_blank">W3C
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
) points out with respect to tolerance, the principle of tolerance does not blunt the need for a perfectly clear specification which draws a precise distinction between conformance and non-conformance. The principle of tolerance is no excuse for a product which contravenes a standard. Naturally, the aphorism &ldquo;any problem in computer science can be solved with another level of indirection&rdquo; still rings true but to a varying degree. The quantification of that degree is a difficult balance to achieve as it involves many variables with the most notable, in my opinion, being scalability, flexibility, and reliability.</p>
<p>I plan to discuss the merits of loose coupling in the larger fabric of parallel and distributed system design more concretely over the next several posts.</p>]]></content:encoded><category>distributed systems</category><category>computing</category></item><item><title>Fabric of a Distributed System</title><link>https://michaelprimeaux.com/en/posts/2010-12-02-fabric-of-a-distributed-system/</link><pubDate>Thu, 02 Dec 2010 00:06:00 +0000</pubDate><author>michael@michaelprimeaux.com (Michael Primeaux)</author><guid>https://michaelprimeaux.com/en/posts/2010-12-02-fabric-of-a-distributed-system/</guid><description>” It has been said that art is a collaboration between God and the artist, which works best when the artist contributes as little as possible; so too with designing distributed systems ” –André Gide
Try to imagine the strategic direction of distributed computing frameworks. Just for a moment transition into a state where you think of nothing and yet listen to everything. What key technologies (disruptive or otherwise) have we experienced over recent years?</description><content:encoded><![CDATA[<blockquote><p>” It has been said that art is a collaboration between God and the artist, which works best when the artist contributes as little as possible; so too with designing distributed systems ” –André Gide</p>
</blockquote>
<p>Try to imagine the strategic direction of distributed computing frameworks. Just for a moment transition into a state where you think of nothing and yet listen to everything. What key technologies (disruptive or otherwise) have we experienced over recent years?</p>
<h1 id="notion-of-autonomous-computing" class="heading">Notion of Autonomous Computing
</h1>
<p>Arguably, one of the most well known papers on the subject of 
<a href="http://en.wikipedia.org/wiki/Autonomic_computing" class="link-external" rel="noopener noreferrer" target="_blank">autonomic computing
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 to be recently released is titled “Autonomic Computing: IBM’s Perspective on the State of Information Technology” [
<a href="http://www.research.ibm.com/autonomic" class="link-external" rel="noopener noreferrer" target="_blank">URI
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
]; authored by 
<a href="http://www.research.ibm.com/about/pmhorn.shtml" class="link-external" rel="noopener noreferrer" target="_blank">Paul Horn
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
, Senior Vice President of 
<a href="http://research.ibm.com/" class="link-external" rel="noopener noreferrer" target="_blank">IBM Research
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
. As outlined by Dr. Horn’s manifest, there are many challenges we face over the next decade within Information Technology.</p>
<p>Within the autonomic landscape, each system node must satisfy the following criteria:</p>
<ul>
<li>self-identification, self-knowing</li>
<li>self-(re)configuration</li>
<li>self-recovery (from perturbations)</li>
<li>self-protection (security)</li>
<li>self-learning (including from errors)</li>
<li>self-regulating (to open standards)</li>
<li>self-resource-allocation</li>
</ul>
<h1 id="notion-of-grid-computing" class="heading">Notion of Grid Computing
</h1>
<p>Wouldn’t it be nice to economically provide a highly available and fault tolerant system that can support an annual uptime guarantee of 99.999 percent—which equates to 5.256 minutes of annual unscheduled downtime—and that scales up by a factor of 106? That is, an application’s storage and processing capacity can automatically grow by a factor of a million, doing jobs faster (106x speed up) or doing 106 larger jobs in the same time (106x scale up), just by adding more resources. Stay focused.</p>
<p>The concept of Grid (or Utility) computing was coined in the mid 1990s and is best defined—to quote 
<a href="http://www.globus.org/alliance/publications/papers/anatomy.pdf" class="link-external" rel="noopener noreferrer" target="_blank">The Anatomy of the Grid
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
—the real and specific problem that underlies the Grid concept is coordinated resource sharing and problem solving in dynamic, multi-institutional virtual organizations.</p>
<p>The 
<a href="http://www.gridforum.org/" class="link-external" rel="noopener noreferrer" target="_blank">Grid Global Forum (GGF
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
) has made great headway; so has OGSA (Open Grid Services Architecture). 
<a href="http://www.mpi-forum.org/" class="link-external" rel="noopener noreferrer" target="_blank">Message Passing Interface (MPI)
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 is widely accepted. Microsoft entered the 
<a href="http://en.wikipedia.org/wiki/High_Performance_Computing" class="link-external" rel="noopener noreferrer" target="_blank">HPC
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 game with the 
<a href="http://www.microsoft.com/windowsserver2003/ccs/default.mspx" class="link-external" rel="noopener noreferrer" target="_blank">Microsoft Compute Cluster Server
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
, which I installed many months ago and is now consuming most of my free time.</p>
<h1 id="notion-of-adaptive-autonomous-agents" class="heading">Notion of Adaptive Autonomous Agents
</h1>
<p>An agent is a system that tries to fulfill a set of goals of goals in a complex, dynamic environment. An agent it situated in the environment; it can sense the environment through its sensors and act upon the environment using its actuators. An agent’s goal can take many different forms: they can be “end goals”, or particular states the agent tries to achieve; they can be selective reinforcement or reward that the agent attempts to maximize; they can be internal needs or motivations that the agent has to keep within certain viability zones and so on. An agent is called autonomous if it operates completely autonomously, i.e. if it decides itself how to relate its sensor data to motor commands in such a way that its goals are attended to successfully. An agent it said to be adaptive, if it is able to improve over time, i.e. if the agent becomes better at achieving its goals with experience.</p>
<p>The study of Adaptive Autonomous Agents is grounded in two important insights, which serve as “guiding principles” for most of the current research performed:</p>
<ul>
<li>Looking at complete systems changes the problems often in a favorable way.</li>
<li>Interaction dynamics can lead to emergent complexity.</li>
</ul>
<p>Essentially, an agent is viewed as a set of competence modules (often called behaviors). These modules are responsible for a particular small task-oriented competence. Each of the modules is directly connected to its relevant sensors and actuators. Modules interface to one another via extremely simple messages rather than a common representation of beliefs, and so on. The communication between modules is almost never of a “broadcast” nature, but happens rather on a point-to-point (or one-to-one) bases. Typically, the messages consist of activation energy, or simple suppression and inhibition signals, or simple tokens in a restricted language. In addition to communication via simple messages, modules also communicate “via the environment”. One module may change some aspect of the environment, which will trigger another module, etc.</p>
<h1 id="notion-of-the-semantic-web" class="heading">Notion of the Semantic Web
</h1>
<p>Simply put, the 
<a href="http://www.w3.org/2001/sw/" class="link-external" rel="noopener noreferrer" target="_blank">Semantic Web
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 is the representation of data on the web in which information is given well-defined meaning—“to be a universal medium for the exchange of data”.</p>
<p>The principal technologies of the Semantic Web fit into a set of layered specifications called the 
<a href="http://www.w3.org/RDF/" class="link-external" rel="noopener noreferrer" target="_blank">Resource Description Framework (RDF)
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
. The current components of that framework are the RDF Core Model, the RDF Vocabulary Description Language and the Web Ontology Language, which all build on the foundation of URIs, XML, and XML namespaces.</p>
<p>The most interesting of these languages is the 
<a href="http://www.w3.org/2004/OWL/" class="link-external" rel="noopener noreferrer" target="_blank">Web Ontology Language (OWL)
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
, which is a descriptive layer built on top of RDF used to model classes, properties, and objects.</p>
<p>Ontology is also a term borrowed from philosophy that refers to the science of describing the kinds of entities in the world and how they are related. Stated another way, an ontology defines the terms used to describe and represent an area of knowledge.</p>
<h1 id="notion-of-service-oriented-architecture" class="heading">Notion of Service-Oriented Architecture
</h1>
<p>I think everyone has been beaten of the head with the “SOA stick”, which is why you won’t feel a thing; keep reading.</p>
<p>In computing, the term Service-Oriented Architecture (SOA) expresses a software architectural concept that defines the use of services to support the requirements of software users. In a SOA environment, nodes on a network make resources available to other participants in the network as independent services that the participants access in a standardized way. Most definitions of SOA identify the use of Web services (i.e. using SOAP or REST) in its implementation. 
<a href="http://www.intertwingly.net/stories/2002/07/20/restSoap.html" class="link-external" rel="noopener noreferrer" target="_blank">Here
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 and 
<a href="http://www.prescod.net/rest/rest_vs_soap_overview/" class="link-external" rel="noopener noreferrer" target="_blank">here
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 are two links worth reading. However, one can implement SOA using any service-based technology.</p>
<p>The WS* specifications have gained wide adoption but other advances are needed.</p>
<p>One last point as related to service orientation; I caution everyone to not forget about the elegance in design of class libraries as they are the underpinnings of every SOA. Design for the in-process consumer first with an eye to areas of the API surface that might benefit from hosting within an SOA.</p>
<p>Other advances…</p>
<p>There are many others such as advances in peer-to-peer algorithms, discrete-event simulation and the event horizon, social networking, recovery-oriented systems, storage and machine virtualization, 
<a href="http://en.wikipedia.org/wiki/Bio_informatics" class="link-external" rel="noopener noreferrer" target="_blank">bioinformatics
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
, 
<a href="http://en.wikipedia.org/wiki/Biotechnology" class="link-external" rel="noopener noreferrer" target="_blank">biotechnology
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
, and 
<a href="http://en.wikipedia.org/wiki/Quantum_computing" class="link-external" rel="noopener noreferrer" target="_blank">quantum computing
<svg class="icon icon--external" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
    stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
    <path d="M14 4h6v6M20 4l-8.5 8.5"/>
    <path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>
</svg>
<span class="visually-hidden"> (opens in a new window)</span>
</a>
 to name but a few. In my mind, the two most important questions to answer are which of these variables are of significant weighting in the equation of strategic direction and which are “noise”?  I certainly have my opinion…but then again, you know what those are like.</p>
<p>Most readers and programmers have little patience to read discussions of this length. However, the length (at least to me) seems disproportionate to the importance of the topic. There is so much more to say and even more to ponder. But, before you run off to save the world let me leave you with one additional thought:</p>

<blockquote><p>“The map is not the territory.”
Alfred Korzybsky
Science and Sanity, 1933</p>
</blockquote>]]></content:encoded><category>computing</category><category>distributed systems</category></item></channel></rss>