<?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 on Mini Fish</title>
    <link>https://blog.minifish.org/tags/distributed-systems/</link>
    <description>Recent content in Distributed-Systems on Mini Fish</description>
    <image>
      <title>Mini Fish</title>
      <url>https://blog.minifish.org/android-chrome-512x512.png</url>
      <link>https://blog.minifish.org/android-chrome-512x512.png</link>
    </image>
    <generator>Hugo -- 0.165.0</generator>
    <language>en-US</language>
    <copyright>Mini Fish 2014-present. Licensed under CC-BY-NC</copyright>
    <lastBuildDate>Sat, 15 Aug 2026 16:11:02 +0800</lastBuildDate>
    <atom:link href="https://blog.minifish.org/tags/distributed-systems/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>agentd: A Transport-Neutral Runtime for Personal Agents</title>
      <link>https://blog.minifish.org/posts/agentd-transport-neutral-runtime/</link>
      <pubDate>Sat, 15 Aug 2026 16:11:02 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/agentd-transport-neutral-runtime/</guid>
      <description>A systems-level look at agentd, a durable single-host runtime for personal agents with scoped execution, explicit capabilities, inspectable traces, and transactional delivery.</description>
      <content:encoded><![CDATA[<p>Most personal-agent demos begin with a chat surface: receive a message, call a
model, perhaps execute a tool, and send the answer back. That is enough to show
the interaction. It does not define what should happen when two messages arrive
for the same conversation, a process restarts, a delivery attempt times out, or
a model invokes a capability with side effects.</p>
<p><a href="https://github.com/minifish-org/agentd"><code>agentd</code></a> is my attempt to put those
questions behind a small runtime contract. It is an experimental, multi-tenant,
single-agent runtime for one host. The model still decides what to do. The host
runtime owns the less glamorous parts: persistence, scheduling, scoped
serialization, capability boundaries, raw traces, and a pull delivery outbox.</p>
<p>The goal is not to build a universal agent framework. The narrower goal is to
make one turn durable and inspectable without coupling execution to Telegram,
a browser, a game, or any other user interface.</p>
<h2 id="the-boundary-starts-with-a-turn">The boundary starts with a turn</h2>
<p>External systems submit work through one transport-neutral endpoint:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>POST /v1/tenants/:tenant/turns
</span></span></code></pre></div><p>A request identifies an agent, a scope, a payload, and optionally a delivery
destination and <code>request_id</code>. Submission returns <code>202</code> with a queued <code>run_id</code>.
The caller can wait for the canonical result, inspect the trace, cancel the run,
or consume delivery through a separate adapter.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>REST turn / due schedule
</span></span><span style="display:flex;"><span>          |
</span></span><span style="display:flex;"><span>          v
</span></span><span style="display:flex;"><span>      queued run -- transactional (tenant, agent, scope) claim
</span></span><span style="display:flex;"><span>          |
</span></span><span style="display:flex;"><span>          v
</span></span><span style="display:flex;"><span>  context -&gt; model &lt;-&gt; allowed built-in/MCP tools
</span></span><span style="display:flex;"><span>          |
</span></span><span style="display:flex;"><span>          v
</span></span><span style="display:flex;"><span> transaction: output + terminal trace + context + optional delivery
</span></span></code></pre></div><p>This separation matters because a transport and a runtime answer different
questions. Telegram knows webhook authentication, chat identifiers, message
formatting, and Telegram&rsquo;s retry behavior. The runtime knows run state, context,
capabilities, and final output. Neither component needs to absorb the other&rsquo;s
failure model.</p>
<h2 id="scope-is-both-context-ownership-and-a-concurrency-key">Scope is both context ownership and a concurrency key</h2>
<p>Every run moves through five states:</p>
<table>
	<thead>
			<tr>
					<th>State</th>
					<th>Meaning</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>queued</code></td>
					<td>Durable work exists but has not acquired its execution lane.</td>
			</tr>
			<tr>
					<td><code>running</code></td>
					<td>The run owns its <code>(tenant, agent, scope)</code> lane.</td>
			</tr>
			<tr>
					<td><code>succeeded</code></td>
					<td>Canonical output and related durable state were finalized.</td>
			</tr>
			<tr>
					<td><code>failed</code></td>
					<td>Execution ended without a successful result.</td>
			</tr>
			<tr>
					<td><code>cancelled</code></td>
					<td>Cancellation won and successful finalization is no longer allowed.</td>
			</tr>
	</tbody>
</table>
<p>The tuple <code>(tenant, agent, scope)</code> is the serialization boundary. Runs for the
same tuple execute one at a time; unrelated scopes can run concurrently. The
claim is transactional, and the database also enforces that at most one run for
a scope is <code>running</code>.</p>
<p>Using scope for both rolling context and serialization is deliberate. If two
turns can modify the same conversation history concurrently, the runtime needs
a merge policy or a clear winner. <code>agentd</code> avoids inventing one: a scope has one
active owner. A Telegram chat can use <code>tg:&lt;chat_id&gt;</code>; a game can use a match and
seat identifier; another caller can define a different stable boundary.</p>
<p>This is not distributed locking. It is single-database coordination for one
host. There is no multi-process lease protocol, failover, or replication claim.</p>
<p>A process restart also has an explicit, conservative outcome. On startup,
persisted runs that were <code>running</code> become <code>failed</code> with an <code>agentd restarted</code>
error, while durable queued work remains available to the dispatcher. The
runtime does not try to reconstruct the interrupted model/tool loop or silently
execute it again. That avoids pretending an in-flight external effect is safe to
repeat, at the cost of requiring a caller or operator to decide whether a failed
turn should be resubmitted. More sophisticated recovery would need an explicit
side-effect protocol, not just a different status update.</p>
<h2 id="submission-idempotency-is-not-replay">Submission idempotency is not replay</h2>
<p>An optional <code>request_id</code> is unique within a tenant. Repeating the same
submission returns the existing run instead of creating another one. This is
useful when an HTTP client cannot tell whether its first request succeeded.</p>
<p>But request deduplication does not make the whole agent execution replayable.
During a run, the model can call tools, an MCP server can affect another system,
and a transport can deliver a message. Those effects do not become reversible
because the initial request had an idempotency key.</p>
<p>The raw <code>run_log</code> records model, tool, output, status, and error observations.
It is an inspection trace, not a deterministic re-execution engine. There is no
side-effect ledger, rollback protocol, or replay simulator. Calling these
different properties by different names prevents a comforting but false
exactly-once story.</p>
<h2 id="the-model-chooses-the-host-constrains">The model chooses; the host constrains</h2>
<p>An agent is configuration rather than executable plugin code: persona, model,
allowed capability families, limits, temperature, token budget, and context
window. The native loop sends visible schemas to an OpenAI-compatible provider,
validates the returned tool call, executes it in host code, records the result,
and continues until the model produces final JSON or reaches a boundary.</p>
<p>The public alpha has 15 built-in capabilities across artifacts, memory,
schedules, clock, bounded public-web access, and arithmetic. It intentionally
has no generic shell, arbitrary HTTP request, hidden filesystem handle, approval
queue, or operator execute endpoint.</p>
<p>MCP extends the tool catalog without changing that ownership model. Servers
belong to a tenant, discovery is explicit, exposed names must be unique, and an
allowlist can narrow the discovered catalog. Secret configuration stores the
names of environment variables rather than their values.</p>
<p>That is a capability boundary, not a sandbox. An operator-configured stdio MCP
process runs with the <code>agentd</code> OS user&rsquo;s host and network permissions. A model
can also be persuaded by a malicious prompt or tool result to call any capability
it can see. The safe rule is therefore simple: expose only side effects that are
acceptable without human approval, and isolate untrusted MCP servers outside
the runtime.</p>
<h2 id="successful-finalization-has-one-transaction-boundary">Successful finalization has one transaction boundary</h2>
<p>The most important write path happens after the model loop has produced a
successful result. One database transaction commits:</p>
<ul>
<li>the canonical output and <code>succeeded</code> run status;</li>
<li>the terminal trace event;</li>
<li>the bounded rolling context;</li>
<li>and, when explicitly requested, one pending delivery referencing the run.</li>
</ul>
<p>If cancellation has already changed the run state, successful finalization is
rejected. A cancelled run cannot leave behind successful output, updated
context, or a delivery row. A second finalization is rejected as well.</p>
<p>This transaction does not roll back external tool effects that occurred before
it. Its guarantee is narrower: readers do not observe a successful run whose
output, terminal trace, context, and delivery intent disagree with one another.
That is the consistency boundary the runtime can actually own.</p>
<h2 id="delivery-is-durable-intent-not-a-callback-hidden-in-execution">Delivery is durable intent, not a callback hidden in execution</h2>
<p>Every successful result is pullable. Delivery exists only when the caller
provides an explicit destination such as <code>tg:42</code>; scope is never silently
treated as an address.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Telegram -&gt; webhook -&gt; turn with explicit tg:&lt;chat_id&gt; destination
</span></span><span style="display:flex;"><span>agentd    -&gt; atomic run finalization -&gt; delivery outbox
</span></span><span style="display:flex;"><span>adapter   -&gt; claim -&gt; Telegram API -&gt; acknowledge
</span></span></code></pre></div><p>Delivery rows reference the run instead of copying its output. An adapter
claims pending work with a random token and expiry, performs the remote side
effect, and acknowledges one of <code>delivered</code>, <code>retry</code>, or <code>failed</code>. An expired
claim can be issued again; a retry updates the same row rather than inserting a
new delivery.</p>
<p>The independent
<a href="https://github.com/minifish-org/agentd-telegram-adapter"><code>agentd-telegram-adapter</code></a>
uses exactly this interface. Its webhook path submits a turn and asks for an
explicit Telegram destination. It does not send a direct reply from the webhook
handler and does not fall back to polling run output as a second delivery path.
This removes two easy sources of duplicate messages.</p>
<p>The outbox still cannot prove that a remote system performed an effect if the
adapter crashes before acknowledgement. Claim expiry and retry make recovery
possible; they do not manufacture exactly-once semantics from an external API.</p>
<h2 id="state-is-separated-by-meaning-not-by-service-count">State is separated by meaning, not by service count</h2>
<p>The runtime uses one libSQL database. The schema separates responsibilities:</p>
<ul>
<li>runs own activation, status, canonical output, and requested destination;</li>
<li><code>run_log</code> owns raw execution observations;</li>
<li>contexts own recent messages for a scope;</li>
<li>memory owns explicit durable facts plus lexical and semantic indexes;</li>
<li>artifacts own larger payloads;</li>
<li>deliveries own remote-delivery state and retry fields;</li>
<li>schedules and MCP registrations remain tenant-scoped resources.</li>
</ul>
<p>This is intentionally less infrastructure than earlier prototypes. There is no
separate object store, vector service, audit database, or replay database. The
memory implementation keeps canonical text, an FTS5 index, and a pinned
384-dimension E5 embedding together; lexical and semantic ranks are fused with
RRF.</p>
<p>The trade-off is equally explicit: this pre-1.0 runtime does not preserve schema
or HTTP compatibility. Runtime data is treated as disposable. A schema mismatch
requires <code>--reset-data</code>, not a migration. That is acceptable for the current
personal-runtime scope, but it would not be an acceptable default for a mature
multi-user platform.</p>
<h2 id="design-claims-should-point-to-executable-evidence">Design claims should point to executable evidence</h2>
<p>The repository includes a
<a href="https://github.com/minifish-org/agentd/blob/main/docs/reliability.md"><code>reliability matrix</code></a>
that maps each important property to a named test. The current suite covers
tenant-scoped request deduplication, same-scope serialization, failed-agent lane
release, atomic finalization, cancellation races, pull-only success, delivery
reclaim and acknowledgement, MCP scoping, public-web network boundaries, memory
scoping, schema mismatch, and non-loopback authentication requirements.</p>
<p>There is also a credential-free deterministic demo:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>git clone https://github.com/minifish-org/agentd.git
</span></span><span style="display:flex;"><span>cd agentd
</span></span><span style="display:flex;"><span>./scripts/demo-e2e.sh
</span></span></code></pre></div><p>It starts a loopback OpenAI-compatible fixture, starts <code>agentd</code> with a fresh
database, creates a tenant and agent, submits a turn, waits for the result,
inspects the trace, verifies pull-only behavior, and exercises artifact storage.
The first run downloads about 448 MiB of revision- and checksum-pinned embedding
assets.</p>
<p>The fixture proves the documented runtime and API path. It does not prove model
quality, real-provider tool compatibility, prompt-injection resistance,
streaming behavior, or production latency. The release workflow separately runs
the real embedding smoke test and builds and inspects the complete container
image.</p>
<h2 id="the-gaps-are-part-of-the-design-document">The gaps are part of the design document</h2>
<p>The public alpha does not claim evidence for:</p>
<ul>
<li>process-kill fault injection at every transaction boundary;</li>
<li>multi-process or multi-host coordination, failover, or replication;</li>
<li>compatibility across database schema or HTTP versions;</li>
<li>load, soak, latency, quota, or denial-of-service limits;</li>
<li>deterministic replay or rollback of external tool effects;</li>
<li>sandboxing of operator-configured MCP processes;</li>
<li>behavior against every provider, MCP server, or transport adapter;</li>
<li>model safety or factual correctness.</li>
</ul>
<p>Its bearer token is an instance-wide operator credential, not end-user
authentication. Tenants provide resource namespaces; they do not isolate
mutually distrustful users who share that token. TLS termination, rate limits,
backups, and host-level resource isolation remain deployment responsibilities.
The full boundary is documented in the
<a href="https://github.com/minifish-org/agentd/blob/main/docs/threat-model.md"><code>threat model</code></a>.</p>
<p>Publishing these gaps is useful because an experimental system is easiest to
overstate precisely where its tests end. A reliability matrix should show both
the properties under test and the empty cells.</p>
<h2 id="what-building-it-changed-for-me">What building it changed for me</h2>
<p>My distributed-database background makes me look for ownership, monotonic
boundaries, and failure states before adding features. <code>agentd</code> applies those
habits at a smaller scale:</p>
<ol>
<li>A conversation scope needs an execution owner, not just a string in a prompt.</li>
<li>Idempotent submission, deterministic replay, and external-effect recovery are
separate problems.</li>
<li>Successful output is not complete until related durable state agrees at one
transaction boundary.</li>
<li>Transport integration belongs outside the runtime, but its delivery intent
must still participate in finalization.</li>
<li>A tool catalog is a security boundary only when the host validates it and the
deployment respects what the host cannot isolate.</li>
<li>Tests and explicit omissions are stronger project documentation than a long
feature list.</li>
</ol>
<p><code>agentd</code> remains experimental. It is not a general workflow engine, an HA agent
platform, or a sandbox for untrusted code. It is a public attempt to make one
personal-agent turn durable, scoped, inspectable, and deliverable—and to state
precisely where those guarantees stop.</p>
<p>Source, release notes, and the reproducible demo are available in the
<a href="https://github.com/minifish-org/agentd"><code>minifish-org/agentd</code></a> repository under
Apache-2.0.</p>
]]></content:encoded>
    </item>
    <item>
      <title>scale-kv: A Storage Experiment About Complexity</title>
      <link>https://blog.minifish.org/posts/scale-kv-storage-experiment/</link>
      <pubDate>Sat, 30 May 2026 09:00:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/scale-kv-storage-experiment/</guid>
      <description>A project note on scale-kv, a Rust experiment with transactional KV storage, page B&#43;trees, quorum commit paths, Cap&amp;#39;n Proto RPC, and storage-system design notes.</description>
      <content:encoded><![CDATA[<p><code>scale-kv</code> is a Rust storage experiment. It contains transactional KV code, page structures, quorum client paths, Cap&rsquo;n Proto RPC, performance notes, and a growing set of documents about capacity, observability, fault drills, and roadmap choices.</p>
<p>The most useful lesson from the project is not a feature. It is how quickly storage complexity expands.</p>
<h2 id="what-it-explores">What it explores</h2>
<p>The codebase touches several storage-system ideas:</p>
<ul>
<li>transactional KV</li>
<li>page-based B+tree structures</li>
<li>secondary indexes</li>
<li>WAL encoding and replay</li>
<li>embedded compute paths</li>
<li>quorum commit experiments</li>
<li>RPC schema generation</li>
<li>redb and sled comparisons</li>
<li>performance baselines</li>
</ul>
<p>Each of these is reasonable on its own. Together, they create a system that needs strong boundaries to stay understandable.</p>
<h2 id="why-this-is-hard">Why this is hard</h2>
<p>Storage systems are full of cross-cutting constraints:</p>
<ul>
<li>write durability affects read visibility</li>
<li>page layout affects compaction and recovery</li>
<li>transactions affect indexing</li>
<li>indexes affect write amplification</li>
<li>replication affects latency and failure handling</li>
<li>observability affects whether any benchmark can be trusted</li>
</ul>
<p>It is easy to add one more subsystem and hard to know when the conceptual model has become too large.</p>
<h2 id="documents-as-part-of-the-project">Documents as part of the project</h2>
<p>One good part of <code>scale-kv</code> is that it includes design notes: API drafts, capacity models, fault drills, observability notes, and performance snapshots.</p>
<p>For this kind of experiment, documents are not decoration. They are the only way to keep track of what the system is supposed to prove.</p>
<h2 id="what-i-learned">What I learned</h2>
<p>The project reinforced a simple rule: decide what a storage experiment is testing before building the next layer.</p>
<p>If the goal is page structure, do not also invent distributed commit. If the goal is quorum behavior, use the simplest storage backend that can support the test. If the goal is API design, avoid mixing it with WAL recovery work.</p>
<p>That does not mean the project failed. It means the project taught the lesson it was supposed to teach: complexity is the main resource being spent.</p>
<h2 id="current-status">Current status</h2>
<p><code>scale-kv</code> is private and experimental. It may remain a lab rather than a product, which is a valid outcome for a systems project.</p>
]]></content:encoded>
    </item>
    <item>
      <title>OceanBase Internals: Transactions, Replay, SQL Engine, and Unit Placement</title>
      <link>https://blog.minifish.org/posts/oceanbase-internals-transaction-replay-sql-unit-placement/</link>
      <pubDate>Sat, 17 Jan 2026 10:00:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/oceanbase-internals-transaction-replay-sql-unit-placement/</guid>
      <description>OceanBase targets high availability and scalability in a shared-nothing cluster. The core engineering challenge is to make four critical subsystems work together with predictable...</description>
      <content:encoded><![CDATA[<h2 id="why-these-paths-matter">Why These Paths Matter</h2>
<p>OceanBase targets high availability and scalability in a shared-nothing cluster. The core engineering challenge is to make four critical subsystems work together with predictable latency and correctness:</p>
<ul>
<li>Write transactions must be durable, replicated, and efficiently committed.</li>
<li>Tablet replay must recover state quickly and safely.</li>
<li>SQL parse to execute must optimize well while respecting multi-tenant constraints.</li>
<li>Unit placement must map tenants to physical resources without fragmentation.</li>
</ul>
<p>This article focuses on motivation, design, implementation highlights, and tradeoffs, using concrete code entry points from the OceanBase codebase.</p>
<h2 id="system-architecture">System Architecture</h2>
<p>OceanBase adopts a shared-nothing architecture where each node is equal and runs its own SQL engine, storage engine, and transaction engine. Understanding the overall architecture is essential before diving into implementation details.</p>
<h3 id="cluster-zone-and-node-organization">Cluster, Zone, and Node Organization</h3>
<pre tabindex="0"><code class="language-mermaid" data-lang="mermaid">graph TB
    subgraph Cluster[&#34;OceanBase Cluster&#34;]
        subgraph Z1[&#34;Zone 1&#34;]
            N1[&#34;OBServer Node 1&#34;]
        end
        subgraph Z2[&#34;Zone 2&#34;]
            N2[&#34;OBServer Node 2&#34;]
        end
        subgraph Z3[&#34;Zone 3&#34;]
            N3[&#34;OBServer Node 3&#34;]
        end
    end
    
    subgraph Proxy[&#34;obproxy Layer&#34;]
        P1[&#34;obproxy 1&#34;]
        P2[&#34;obproxy 2&#34;]
    end
    
    Client[&#34;Client Applications&#34;] --&gt; Proxy
    Proxy --&gt; N1
    Proxy --&gt; N2
    Proxy --&gt; N3
</code></pre><p><strong>Key Concepts:</strong></p>
<ul>
<li><strong>Cluster</strong>: A collection of nodes working together</li>
<li><strong>Zone</strong>: Logical availability zones for high availability and disaster recovery</li>
<li><strong>OBServer</strong>: Service process on each node handling SQL, storage, and transactions</li>
<li><strong>obproxy</strong>: Stateless proxy layer routing SQL requests to appropriate OBServer nodes</li>
</ul>
<h3 id="data-organization-partition-tablet-and-log-stream">Data Organization: Partition, Tablet, and Log Stream</h3>
<pre tabindex="0"><code class="language-mermaid" data-lang="mermaid">graph TB
    subgraph Table[&#34;Table&#34;]
        P1[&#34;Partition 1&#34;]
        P2[&#34;Partition 2&#34;]
        P3[&#34;Partition 3&#34;]
    end
    
    subgraph LS1[&#34;Log Stream 1&#34;]
        T1[&#34;Tablet 1&#34;]
        T2[&#34;Tablet 2&#34;]
    end
    
    subgraph LS2[&#34;Log Stream 2&#34;]
        T3[&#34;Tablet 3&#34;]
    end
    
    subgraph LS3[&#34;Log Stream 3&#34;]
        T4[&#34;Tablet 4&#34;]
    end
    
    P1 --&gt; T1
    P1 --&gt; T2
    P2 --&gt; T3
    P3 --&gt; T4
    
    T1 --&gt; LS1
    T2 --&gt; LS1
    T3 --&gt; LS2
    T4 --&gt; LS3
</code></pre><p><strong>Key Concepts:</strong></p>
<ul>
<li><strong>Partition</strong>: Logical shard of a table (hash, range, list partitioning)</li>
<li><strong>Tablet</strong>: Physical storage object storing ordered data records for a partition</li>
<li><strong>Log Stream (LS)</strong>: Replication unit using Multi-Paxos for data consistency</li>
<li><strong>Replication</strong>: Each tablet has multiple replicas across zones, with one leader accepting writes. Log streams replicate data via Multi-Paxos protocol across different zones.</li>
</ul>
<h3 id="multi-tenant-resource-model">Multi-Tenant Resource Model</h3>
<pre tabindex="0"><code class="language-mermaid" data-lang="mermaid">graph TB
    subgraph Tenant[&#34;Tenant&#34;]
        T1[&#34;Tenant 1 MySQL Mode&#34;]
        T2[&#34;Tenant 2 Oracle Mode&#34;]
        T3[&#34;System Tenant&#34;]
    end
    
    subgraph Pool[&#34;Resource Pool&#34;]
        RP1[&#34;Pool 1&#34;]
        RP2[&#34;Pool 2&#34;]
        RP3[&#34;Pool 3&#34;]
    end
    
    subgraph Unit[&#34;Resource Unit&#34;]
        U1[&#34;Unit 1 CPU Memory Disk&#34;]
        U2[&#34;Unit 2 CPU Memory Disk&#34;]
        U3[&#34;Unit 3 CPU Memory Disk&#34;]
    end
    
    subgraph Server[&#34;Physical Server&#34;]
        S1[&#34;Server 1&#34;]
        S2[&#34;Server 2&#34;]
        S3[&#34;Server 3&#34;]
    end
    
    T1 --&gt; RP1
    T2 --&gt; RP2
    T3 --&gt; RP3
    
    RP1 --&gt; U1
    RP2 --&gt; U2
    RP3 --&gt; U3
    
    U1 --&gt; S1
    U2 --&gt; S2
    U3 --&gt; S3
</code></pre><p><strong>Key Concepts:</strong></p>
<ul>
<li><strong>Tenant</strong>: Isolated database instance (MySQL or Oracle compatibility)</li>
<li><strong>Resource Pool</strong>: Groups resource units for a tenant across zones</li>
<li><strong>Resource Unit</strong>: Virtual container with CPU, memory, and disk resources</li>
<li><strong>Unit Placement</strong>: Rootserver schedules units to physical servers based on resource constraints</li>
</ul>
<h3 id="layered-architecture">Layered Architecture</h3>
<pre tabindex="0"><code class="language-mermaid" data-lang="mermaid">graph TB
    subgraph App[&#34;Application Layer&#34;]
        Client[&#34;Client Applications&#34;]
    end
    
    subgraph Proxy[&#34;Proxy Layer&#34;]
        ODP[&#34;obproxy Router&#34;]
    end
    
    subgraph OBServer[&#34;OBServer Layer&#34;]
        subgraph Node[&#34;OBServer Node&#34;]
            SQL[&#34;SQL Engine&#34;]
            TX[&#34;Transaction Engine&#34;]
            ST[&#34;Storage Engine&#34;]
        end
    end
    
    subgraph Storage[&#34;Storage Layer&#34;]
        subgraph LS[&#34;Log Stream&#34;]
            Tablet[&#34;Tablet&#34;]
            Memtable[&#34;Memtable&#34;]
            SSTable[&#34;SSTable&#34;]
        end
        Palf[&#34;Paxos Log Service&#34;]
    end
    
    subgraph Resource[&#34;Resource Layer&#34;]
        Tenant[&#34;Tenant&#34;]
        Unit[&#34;Resource Unit&#34;]
        Pool[&#34;Resource Pool&#34;]
        RS[&#34;Rootserver&#34;]
    end
    
    Client --&gt; ODP
    ODP --&gt; SQL
    SQL --&gt; TX
    TX --&gt; ST
    ST --&gt; LS
    LS --&gt; Palf
    Tenant --&gt; Unit
    Unit --&gt; Pool
    Pool --&gt; RS
    RS --&gt; Node
</code></pre><p><strong>Key Concepts:</strong></p>
<ul>
<li><strong>SQL Engine</strong>: Parses, optimizes, and executes SQL statements</li>
<li><strong>Transaction Engine</strong>: Manages transaction lifecycle, commit protocols, and consistency</li>
<li><strong>Storage Engine</strong>: Handles data organization, memtables, and SSTables</li>
<li><strong>Log Service</strong>: Provides Paxos-based replication and durability</li>
<li><strong>Rootserver</strong>: Manages cluster metadata, resource scheduling, and placement</li>
</ul>
<h2 id="design-overview">Design Overview</h2>
<p>At a high level, each node runs a full SQL engine, storage engine, and transaction engine. Data is organized into tablets, which belong to log streams. Log streams replicate changes using Paxos-based log service. Tenants slice resources via unit configurations and pools, while rootserver components place those units on servers.</p>
<p>The following sections walk through each path with the relevant implementation anchors.</p>
<h2 id="architecture-diagrams">Architecture Diagrams</h2>
<h3 id="transaction-write-path">Transaction Write Path</h3>
<pre tabindex="0"><code class="language-mermaid" data-lang="mermaid">flowchart LR
  subgraph A[&#34;Transaction Write Path&#34;]
    C[&#34;Client&#34;] --&gt; S[&#34;SQL Engine&#34;]
    S --&gt; T[&#34;Transaction Context&#34;]
    T --&gt; M[&#34;Memtable Write&#34;]
    M --&gt; R[&#34;Redo Buffer&#34;]
    R --&gt; L[&#34;Log Service&#34;]
    L --&gt; P[&#34;Replicated Log&#34;]
    P --&gt; K[&#34;Commit Result&#34;]
  end
</code></pre><h3 id="tablet-replay-path">Tablet Replay Path</h3>
<pre tabindex="0"><code class="language-mermaid" data-lang="mermaid">flowchart LR
  subgraph B[&#34;Tablet Replay Path&#34;]
    L[&#34;Log Service&#34;] --&gt; RS[&#34;Replay Service&#34;]
    RS --&gt; E[&#34;Tablet Replay Executor&#34;]
    E --&gt; LS[&#34;Log Stream&#34;]
    LS --&gt; TB[&#34;Tablet&#34;]
    TB --&gt; CK[&#34;Replay Checks&#34;]
    CK --&gt; AP[&#34;Apply Operation&#34;]
    AP --&gt; ST[&#34;Updated Tablet State&#34;]
  end
</code></pre><h3 id="sql-compile-and-execute">SQL Compile and Execute</h3>
<pre tabindex="0"><code class="language-mermaid" data-lang="mermaid">flowchart LR
  subgraph C[&#34;SQL Compile and Execute&#34;]
    Q[&#34;SQL Text&#34;] --&gt; P[&#34;Parser&#34;]
    P --&gt; R[&#34;Resolver&#34;]
    R --&gt; W[&#34;Rewriter&#34;]
    W --&gt; O[&#34;Optimizer&#34;]
    O --&gt; LP[&#34;Logical Plan&#34;]
    LP --&gt; CG[&#34;Code Generator&#34;]
    CG --&gt; PP[&#34;Physical Plan&#34;]
    PP --&gt; EX[&#34;Executor&#34;]
  end
</code></pre><h3 id="unit-placement">Unit Placement</h3>
<pre tabindex="0"><code class="language-mermaid" data-lang="mermaid">flowchart LR
  subgraph D[&#34;Unit Placement&#34;]
    UC[&#34;Unit Config&#34;] --&gt; RP[&#34;Resource Pool&#34;]
    RP --&gt; PS[&#34;Placement Strategy&#34;]
    PS --&gt; CS[&#34;Candidate Servers&#34;]
    CS --&gt; CH[&#34;Chosen Server&#34;]
    CH --&gt; UN[&#34;Unit Instance&#34;]
  end
</code></pre><h2 id="write-transaction-from-memtable-to-replicated-log">Write Transaction: From Memtable to Replicated Log</h2>
<h3 id="motivation">Motivation</h3>
<p>A write transaction must be both fast and durable. OceanBase uses memtables for in-memory writes, and a log stream for redo replication. The design must allow low-latency commit while supporting parallel redo submission and multi-participant (2PC) transactions.</p>
<h3 id="design-sketch">Design Sketch</h3>
<ul>
<li>Each transaction is represented by a per-LS context (<code>ObPartTransCtx</code>).</li>
<li>Redo is flushed based on pressure or explicit triggers.</li>
<li>Commit chooses one-phase or two-phase based on participants.</li>
<li>Logs are submitted via a log adapter backed by logservice.</li>
</ul>
<h3 id="implementation-highlights">Implementation Highlights</h3>
<ul>
<li>Transaction context lifecycle and commit logic are in <code>src/storage/tx/ob_trans_part_ctx.cpp</code>.</li>
<li>Redo submission is driven by <code>submit_redo_after_write</code>, which switches between serial and parallel logging based on thresholds.</li>
<li>Commit decides between one-phase and two-phase commit depending on participant count.</li>
<li>The log writer (<code>ObTxLSLogWriter</code>) submits serialized logs via <code>ObITxLogAdapter</code>, which is wired to logservice (<code>ObLogHandler</code>).</li>
</ul>
<h3 id="tradeoffs">Tradeoffs</h3>
<ul>
<li><strong>Serial vs parallel redo</strong>: Serial logging is simpler and cheaper for small transactions, but parallel logging reduces tail latency for large transactions at the cost of more coordination.</li>
<li><strong>1PC vs 2PC</strong>: 1PC is fast for single-participant transactions; 2PC is required for distributed consistency but increases coordination overhead.</li>
<li><strong>In-memory batching vs durability</strong>: Larger batching improves throughput but can delay durability and increase replay time.</li>
</ul>
<h2 id="tablet-replay-reconstructing-state-safely">Tablet Replay: Reconstructing State Safely</h2>
<h3 id="motivation-1">Motivation</h3>
<p>Recovery needs to be deterministic and safe: the system must replay logs to reconstruct tablet state without violating invariants or applying obsolete data.</p>
<h3 id="design-sketch-1">Design Sketch</h3>
<ul>
<li>Logservice schedules replay tasks per log stream.</li>
<li>Tablet replay executor fetches the LS, locates the tablet, validates replay conditions, and applies the log.</li>
<li>Specialized replay executors handle different log types (e.g., schema updates, split operations).</li>
</ul>
<h3 id="implementation-highlights-1">Implementation Highlights</h3>
<ul>
<li>Replay orchestration lives in <code>src/logservice/replayservice/ob_log_replay_service.cpp</code>.</li>
<li>Tablet replay logic is in <code>src/logservice/replayservice/ob_tablet_replay_executor.cpp</code>.</li>
<li>Specific tablet operations are applied in dedicated executors, such as <code>ObTabletServiceClogReplayExecutor</code> in <code>src/storage/tablet/ob_tablet_service_clog_replay_executor.cpp</code>.</li>
</ul>
<h3 id="tradeoffs-1">Tradeoffs</h3>
<ul>
<li><strong>Strictness vs throughput</strong>: Replay barriers enforce ordering for correctness but can reduce parallelism.</li>
<li><strong>Tablet existence checks</strong>: Allowing missing tablets can speed recovery but requires careful validation to avoid partial state.</li>
<li><strong>MDS synchronization</strong>: Metadata state updates improve correctness but add contention via locks.</li>
</ul>
<h2 id="sql-parse-to-execute-compile-pipeline-for-performance">SQL Parse to Execute: Compile Pipeline for Performance</h2>
<h3 id="motivation-2">Motivation</h3>
<p>OceanBase supports MySQL and Oracle compatibility with rich SQL features. The compile pipeline must be fast, cache-friendly, and yield efficient execution plans.</p>
<h3 id="design-sketch-2">Design Sketch</h3>
<ul>
<li>SQL text enters the engine via <code>ObSql::stmt_query</code>.</li>
<li>Parsing produces a parse tree.</li>
<li>Resolution turns the parse tree into a typed statement tree.</li>
<li>Rewrite and optimization generate a logical plan.</li>
<li>Code generation produces a physical plan and execution context.</li>
</ul>
<h3 id="implementation-highlights-2">Implementation Highlights</h3>
<ul>
<li>Entry and query handling: <code>src/sql/ob_sql.cpp</code> (<code>stmt_query</code>, <code>handle_text_query</code>).</li>
<li>Resolver: <code>ObResolver</code> in <code>src/sql/resolver/ob_resolver.h</code>.</li>
<li>Transform and optimize: <code>ObSql::transform_stmt</code> and <code>ObSql::optimize_stmt</code> in <code>src/sql/ob_sql.cpp</code>.</li>
<li>Code generation: <code>ObSql::code_generate</code> in <code>src/sql/ob_sql.cpp</code>.</li>
</ul>
<h3 id="tradeoffs-2">Tradeoffs</h3>
<ul>
<li><strong>Plan cache vs compile accuracy</strong>: Plan caching reduces latency but may reuse suboptimal plans under changing data distributions.</li>
<li><strong>Rewrite aggressiveness</strong>: More transformations can yield better plans but increase compile cost.</li>
<li><strong>JIT and rich formats</strong>: Faster execution for some workloads, but added complexity and memory pressure.</li>
</ul>
<h2 id="unit-placement-scheduling-tenant-resources">Unit Placement: Scheduling Tenant Resources</h2>
<h3 id="motivation-3">Motivation</h3>
<p>Multi-tenancy requires predictable isolation and efficient resource utilization. Unit placement must respect CPU, memory, and disk constraints while minimizing fragmentation.</p>
<h3 id="design-sketch-3">Design Sketch</h3>
<ul>
<li>Unit config defines resource demands.</li>
<li>Resource pool groups units by tenant and zone.</li>
<li>Placement strategy scores candidate servers to pick a host for each unit.</li>
</ul>
<h3 id="implementation-highlights-3">Implementation Highlights</h3>
<ul>
<li>Resource types and pools: <code>src/share/unit/ob_unit_config.h</code>, <code>src/share/unit/ob_resource_pool.h</code>, <code>src/share/unit/ob_unit_info.h</code>.</li>
<li>Placement policy: <code>src/rootserver/ob_unit_placement_strategy.cpp</code> uses a weighted dot-product of remaining resources to choose a server.</li>
<li>Orchestration: <code>src/rootserver/ob_unit_manager.cpp</code> handles creation, alteration, and migration of units and pools.</li>
</ul>
<h3 id="tradeoffs-3">Tradeoffs</h3>
<ul>
<li><strong>Greedy placement vs global optimality</strong>: Dot-product scoring is efficient and practical but may not be globally optimal.</li>
<li><strong>Capacity normalization</strong>: Assuming comparable server capacities simplifies scoring but may bias placement in heterogeneous clusters.</li>
<li><strong>Latency vs stability</strong>: Fast placement decisions can lead to more churn; conservative placement improves stability but can reduce utilization.</li>
</ul>
<h2 id="closing-thoughts">Closing Thoughts</h2>
<p>These four paths demonstrate how OceanBase balances correctness, performance, and operability. The code structure follows clear separation of responsibilities: transaction logic is in <code>storage/tx</code>, replication and replay are in <code>logservice</code>, SQL compilation is in <code>sql</code>, and scheduling is in <code>rootserver</code> and <code>share/unit</code>. The tradeoffs are explicit and largely encoded in thresholds and policies, which makes tuning feasible without invasive rewrites.</p>
<p>If you are extending OceanBase, start with the entry points highlighted above and follow the call chains into the relevant subsystem. It is the fastest way to build a mental model grounded in the actual implementation.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Understanding the CAP Theorem</title>
      <link>https://blog.minifish.org/posts/understanding-the-cap-theorem/</link>
      <pubDate>Wed, 20 Dec 2017 22:21:06 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/understanding-the-cap-theorem/</guid>
      <description>The CAP theorem has become one of the hottest theorems in recent years; when discussing distributed systems, CAP is inevitably mentioned. However, I feel that I haven&amp;#39;t thoroughly...</description>
      <content:encoded><![CDATA[<h2 id="background">Background</h2>
<p>The CAP theorem has become one of the hottest theorems in recent years; when discussing distributed systems, CAP is inevitably mentioned. However, I feel that I haven&rsquo;t thoroughly understood it, so I wanted to write a blog post to record my understanding. I will update the content as I gain new insights.</p>
<h2 id="understanding">Understanding</h2>
<p>I read the first part of this <a href="https://static.googleusercontent.com/media/research.google.com/zh-CN//pubs/archive/45855.pdf">paper</a>.</p>
<blockquote>
<p>The CAP theorem [Bre12] says that you can only have two of the three desirable properties of:</p>
<ul>
<li>C: Consistency, which we can think of as serializability for this discussion;</li>
<li>A: 100% availability, for both reads and updates;</li>
<li>P: tolerance to network partitions.</li>
</ul>
<p>This leads to three kinds of systems: CA, CP and AP, based on what letter you leave out.</p>
</blockquote>
<p>Let me share my understanding, using a network composed of three machines (x, y, and z) as an example:</p>
<ul>
<li>
<p><strong>C (Consistency)</strong>: The three machines appear as one. Operations of addition, deletion, modification, and query on any one machine should always be consistent. That is, if you read data from x and then read from y, the results are the same. If you write data to x and then read from y, you should also read the newly written data. Wikipedia also specifically mentions that it&rsquo;s acceptable to read the data just written to x from y after a short period of time (eventual consistency).</p>
</li>
<li>
<p><strong>A (Availability)</strong>: The three machines, as a whole, must always be readable and writable; even if some parts fail, it must be readable and writable.</p>
</li>
<li>
<p><strong>P (Partition Tolerance)</strong>: If the network between x, y, and z is broken, any machine cannot or refuses to provide services; it is neither readable nor writable.</p>
</li>
</ul>
<p>Here, <strong>C</strong> is the easiest to understand. The concepts of <strong>A</strong> and <strong>P</strong> are somewhat vague and easy to confuse.</p>
<p>Now let&rsquo;s discuss the three combinations:</p>
<p>If the network between x, y, and z is disconnected:</p>
<ul>
<li>
<p><strong>CA</strong>: Ensure data consistency (<strong>C</strong>). When x writes data, y can read it (<strong>C</strong>). Allow the system to continue providing services—even if only x and y are operational—ensuring it is readable and writable (<strong>A</strong>). We can only tolerate z not providing service; it cannot read or write, nor return incorrect data (losing <strong>P</strong>).</p>
</li>
<li>
<p><strong>CP</strong>: Ensure data consistency (<strong>C</strong>). Allow all three machines to provide services (even if only for reads) (<strong>P</strong>). We can only tolerate that x, y, and z cannot write (losing <strong>A</strong>).</p>
</li>
<li>
<p><strong>AP</strong>: Allow all three machines to write (<strong>A</strong>). Allow all three machines to provide services (reads count) (<strong>P</strong>). We can only tolerate that the data written by x and y doesn&rsquo;t reach z; z will return data inconsistent with x and y (losing <strong>C</strong>).</p>
</li>
</ul>
<p><strong>CA</strong> is exemplified by Paxos/Raft, which are majority protocols that sacrifice <strong>P</strong>; minority nodes remain completely silent. <strong>CP</strong> represents a read-only system; if a system is read-only, whether there&rsquo;s a network partition doesn&rsquo;t really matter—the tolerance to network partitions is infinitely large. <strong>AP</strong> is suitable for systems that only append and do not update—only inserts, no deletes or updates. Finally, by merging the results together, it can still function.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
