<?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>Mini Fish</title>
    <link>https://blog.minifish.org/</link>
    <description>Recent content 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.161.1</generator>
    <language>en-US</language>
    <copyright>Mini Fish 2014-present. Licensed under CC-BY-NC</copyright>
    <lastBuildDate>Fri, 12 Jun 2026 09:00:00 +0800</lastBuildDate>
    <atom:link href="https://blog.minifish.org/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>mood-midi-mlx: A Small MLX Loop for Symbolic MIDI Generation</title>
      <link>https://blog.minifish.org/posts/mood-midi-mlx-symbolic-generation/</link>
      <pubDate>Fri, 12 Jun 2026 09:00:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/mood-midi-mlx-symbolic-generation/</guid>
      <description>A project note on mood-midi-mlx, a local Apple MLX symbolic MIDI generation experiment with toy data, tokenization, training, generation, evaluation, MAESTRO import, and a small UI server.</description>
      <content:encoded><![CDATA[<p><code>mood-midi-mlx</code> is a first-pass Apple MLX project for symbolic MIDI generation. It builds a closed loop: generate or import MIDI, tokenize it, train a small decoder-only Transformer, generate tokens from mood controls, export MIDI, and evaluate the result.</p>
<p>It is not trying to solve music generation all at once. It is trying to make the loop inspectable.</p>
<h2 id="why-symbolic-midi">Why symbolic MIDI</h2>
<p>Audio generation is expensive and hard to inspect. MIDI is smaller, structured, and easier to evaluate. For early experiments, that matters.</p>
<p>The project starts with controls such as:</p>
<ul>
<li>mood</li>
<li>energy</li>
<li>density</li>
<li>brightness</li>
</ul>
<p>Those controls are simple, but they are useful enough to test whether the model responds to conditioning at all.</p>
<h2 id="training-path">Training path</h2>
<p>The first phase can generate toy piano MIDI without external datasets. It then tokenizes MIDI events, trains with MLX on Apple Silicon, and writes checkpoints plus training logs.</p>
<p>The repo also includes a MAESTRO import path. Since MAESTRO does not have mood labels, the importer creates heuristic weak labels from features such as tempo, density, velocity, silence, repetition, and pitch range.</p>
<p>That is the right level of honesty for the project: the labels are weak labels, not ground truth.</p>
<h2 id="generation-and-preview">Generation and preview</h2>
<p>Generated tokens can be exported back to MIDI. The project also has a local UI server that can generate and download MIDI files, with an optional audio-rendering path using FluidSynth and a local SoundFont.</p>
<p>This makes the experiment audible without turning it into a full production app.</p>
<h2 id="what-i-learned">What I learned</h2>
<p>For music ML projects, the training loop is only one part of the system. The useful loop includes:</p>
<ul>
<li>dataset construction</li>
<li>tokenization</li>
<li>checkpointing</li>
<li>generation controls</li>
<li>structural evaluation</li>
<li>audible preview</li>
</ul>
<p>If any part is missing, it becomes hard to tell whether the model is improving or just producing plausible-looking tokens.</p>
<h2 id="current-status">Current status</h2>
<p><code>mood-midi-mlx</code> is private and experimental. It is a research workbench for Apple Silicon rather than a finished listening product. That separation is useful: <code>mood-midi-mlx</code> can explore models, while <code>driftloop</code> can focus on a coherent listening experience.</p>
]]></content:encoded>
    </item>
    <item>
      <title>driftloop: Browser Generative Music Without a Backend</title>
      <link>https://blog.minifish.org/posts/driftloop-browser-generative-music/</link>
      <pubDate>Thu, 11 Jun 2026 09:00:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/driftloop-browser-generative-music/</guid>
      <description>A project note on driftloop, a browser-based generative music app using algorithmic composition, FluidSynth in WebAssembly, and optional Magenta melody generation.</description>
      <content:encoded><![CDATA[<p>driftloop is a pure-frontend web app that streams continuous generative background music in the browser. Click play, and the music starts. Switch style buttons, and the arrangement morphs without a hard cut.</p>
<p>The important constraint is that every note is composed and synthesized live in the browser. There are no prerecorded loops, no server, no account, and no backend generation job.</p>
<h2 id="why-it-is-not-mainly-an-ai-app">Why it is not mainly an AI app</h2>
<p>The interesting origin story is that driftloop came after an earlier model-first attempt. That earlier direction tried to distill a multi-instrument music model and sample from it for the listening experience. The output was structurally chaotic in a way that sampling tweaks did not fix.</p>
<p>driftloop starts from a different premise: for continuous background music, structure matters more than model capacity.</p>
<p>Algorithmic composition handles the load-bearing parts:</p>
<ul>
<li>chord progression</li>
<li>voice leading</li>
<li>bass motion</li>
<li>rhythm</li>
<li>drum pattern</li>
<li>section shape</li>
</ul>
<p>A small model can still help with lead melody, but it should not be responsible for making the whole piece coherent.</p>
<h2 id="runtime-shape">Runtime shape</h2>
<p>The app has two layers:</p>
<ol>
<li>Vanilla JavaScript composition logic that keeps the music in key, on grid, and structurally coherent.</li>
<li>An optional MelodyRNN path through TensorFlow.js for lead melody.</li>
</ol>
<p>Synthesis uses <code>js-synthesizer</code>, a WebAssembly port of FluidSynth, with a General MIDI SoundFont through the WebAudio API.</p>
<p>The SoundFont is large, so deployment has a practical constraint: Cloudflare Pages has a single-file size limit smaller than the SoundFont. The app fetches the SoundFont from a public upstream mirror on first launch, then the service worker caches it for offline reuse.</p>
<h2 id="what-i-learned">What I learned</h2>
<p>Generative music is not only generation. It is arrangement, continuity, transitions, repetition management, and sound rendering.</p>
<p>The most important product question is not “is it AI?” It is “can I leave this playing without getting annoyed?” That favors simple, controllable structure over model novelty.</p>
<h2 id="current-status">Current status</h2>
<p>driftloop is private but product-shaped. It is a static PWA candidate, and the current architecture is intentionally deployable without backend secrets or runtime infrastructure.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Nightfall: A Browser Referee for AI Werewolf</title>
      <link>https://blog.minifish.org/posts/nightfall-ai-werewolf-referee/</link>
      <pubDate>Wed, 10 Jun 2026 09:00:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/nightfall-ai-werewolf-referee/</guid>
      <description>A project note on Nightfall, an AI Werewolf prototype with a browser referee, projected per-seat views, an agentd-backed turn API, and a shared engine for CLI and UI runs.</description>
      <content:encoded><![CDATA[<p>Nightfall is a web-based AI Werewolf platform. Every seat is an AI player; the human is an operator or spectator. The browser is the referee.</p>
<p>That referee boundary is the most important design choice. The game engine holds the authoritative state and decides what each seat is allowed to see. The AI players only receive projected views.</p>
<h2 id="why-the-browser-is-the-referee">Why the browser is the referee</h2>
<p>Werewolf is an information game. The system must know the full state, but each player must see only a slice of it. If the AI seat receives hidden information by accident, the whole experiment is meaningless.</p>
<p>Nightfall keeps that boundary explicit:</p>
<ul>
<li><code>engine/</code> owns state, phase transitions, view projection, resolution, victory checks, and deterministic replay</li>
<li><code>orchestrator/</code> runs the environment-independent game loop</li>
<li><code>agentd-client/</code> calls <code>POST /v1/turns</code></li>
<li><code>app/</code> renders the spectator UI without owning game rules</li>
</ul>
<p>The same engine can drive a Node CLI transcript or the browser spectator.</p>
<h2 id="agentd-integration">agentd integration</h2>
<p>Nightfall does not embed agent logic. It calls an external <code>agentd</code> runtime. The werewolf, seer, and villager agents live on the agentd side as personas and model choices. Nightfall only maps roles to <code>agent_ref</code> values and sends the projected view for the current seat and phase.</p>
<p>That separation matters. The game should own rules; agentd should own agent execution.</p>
<h2 id="game-shape">Game shape</h2>
<p>The current board is intentionally small:</p>
<ul>
<li>6 seats</li>
<li>2 wolves</li>
<li>1 seer</li>
<li>3 villagers</li>
<li>no role reveal on death</li>
<li>loop: night seer, night wolf, day discussion, day vote</li>
</ul>
<p>Victory is checked after deaths: wolves eliminated means good wins; all villagers dead means wolves win. Killing the seer does not end the game, it only removes the good side&rsquo;s information source.</p>
<h2 id="what-i-learned">What I learned</h2>
<p>Games are useful agent testbeds because they force state discipline. A chat bot can hide vague behavior behind language. A game cannot. The engine needs phases, legal actions, projected views, victory rules, and reproducible transcripts.</p>
<p>Nightfall is also a reminder that a good agent integration is often just a clean boundary. The AI seat should not know the whole game. It should know what that player would know.</p>
<h2 id="current-status">Current status</h2>
<p>Nightfall is private and experimental. It is useful as an AI interaction lab, especially for testing information hiding, role-specific personas, and multi-agent turn orchestration.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Canopy: Local MCP Routing for Singapore PCN Loops</title>
      <link>https://blog.minifish.org/posts/canopy-singapore-pcn-routing/</link>
      <pubDate>Tue, 09 Jun 2026 09:00:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/canopy-singapore-pcn-routing/</guid>
      <description>A project note on Canopy, a self-hosted MCP routing service for Singapore cycling and walking route planning with GraphHopper, OneMap, GPX export, and route audits.</description>
      <content:encoded><![CDATA[<p>Canopy is a local MCP server for Singapore cycling and walking route planning. It is built for agents, not for end users. Claude, Codex, LangChain, or another MCP client can call Canopy tools; the agent remains responsible for conversation and presentation.</p>
<p>The useful distinction is that Canopy does not try to be a map app. It is a route-planning capability.</p>
<h2 id="why-this-exists">Why this exists</h2>
<p>Singapore cycling routes are not only about shortest path. A good route often means:</p>
<ul>
<li>prefer PCN-like paths</li>
<li>avoid high-car roads</li>
<li>keep loops close to a target distance</li>
<li>export a track that can be followed elsewhere</li>
<li>explain how much of the route is car-free or on-road</li>
</ul>
<p>General routing APIs are not always good at expressing those preferences. Canopy uses a self-hosted GraphHopper service with a custom bike profile that strongly prefers cycleways, PCN-like paths, and low-car links while penalizing motor roads.</p>
<p>OneMap is used for geocoding and POIs, not routing.</p>
<h2 id="tool-surface">Tool surface</h2>
<p>The MCP server exposes tools such as:</p>
<ul>
<li><code>geocode(query)</code></li>
<li><code>plan_loop(start, distance_km, direction?, prefer=&quot;pcn&quot;)</code></li>
<li><code>route(origin, destination, prefer=&quot;pcn&quot;)</code></li>
<li><code>pois_along(geometry, types=[...])</code></li>
<li><code>audit_route(geometry)</code></li>
<li><code>export_gpx(geometry, name)</code></li>
</ul>
<p>That gives an agent a clean workflow: resolve a place, plan a route, audit the result, find POIs along the corridor, and export GPX.</p>
<h2 id="honest-boundaries">Honest boundaries</h2>
<p>Canopy optimizes for low-car and away-from-traffic routing. It does not avoid pedestrians, because Singapore PCNs are shared paths.</p>
<p>Round-trip distance is also approximate in GraphHopper. Canopy tries multiple seeds and scaled requests, then returns the actual distance instead of pretending the target was exact.</p>
<p>The project also intentionally avoids building turn-by-turn navigation. A downstream app can load the generated GPX track into something like OsmAnd or CoMaps.</p>
<h2 id="what-i-learned">What I learned</h2>
<p>MCP is a good fit when the tool is specific and verifiable. Route planning should not be hidden inside a chat answer. The agent should call a tool that returns geometry, distance, audit percentages, and a GPX path.</p>
<p>For location work, the agent&rsquo;s job is orchestration. The route engine should still be a route engine.</p>
<h2 id="current-status">Current status</h2>
<p>Canopy is private and active. It is tied to local Singapore routing data, OneMap credentials, and a local GraphHopper runtime, so the repo is not a clean general-purpose hosted service.</p>
]]></content:encoded>
    </item>
    <item>
      <title>good-fishing-day: A Fishing Weather Dashboard</title>
      <link>https://blog.minifish.org/posts/good-fishing-day-dashboard/</link>
      <pubDate>Fri, 05 Jun 2026 09:00:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/good-fishing-day-dashboard/</guid>
      <description>A project note on good-fishing-day, a Cloudflare Workers and Astro dashboard that combines tide, weather, caching, and a simple fishing suitability score.</description>
      <content:encoded><![CDATA[<p><code>good-fishing-day</code> is a personal fishing-weather dashboard. It combines tide data and weather forecasts to answer a practical question: is today, or the next few days, good for fishing?</p>
<p>It is a small application, but it has the shape of a real product: backend data fetching, caching, scoring, frontend display, and deployment.</p>
<h2 id="why-build-it">Why build it</h2>
<p>Fishing decisions depend on several signals:</p>
<ul>
<li>tide timing</li>
<li>weather</li>
<li>wind</li>
<li>rain</li>
<li>forecast window</li>
<li>saved locations</li>
</ul>
<p>Generic weather apps show the raw pieces, but they do not answer the combined question I care about. A personal dashboard can make that tradeoff directly.</p>
<h2 id="architecture">Architecture</h2>
<p>The backend is a Cloudflare Worker. It fetches marine/tide data and weather forecasts, computes a score, and caches results in Workers KV.</p>
<p>The frontend is an Astro and React app deployed to Cloudflare Pages. Shared TypeScript code keeps constants and data shapes aligned.</p>
<p>This is a good fit for Cloudflare because the workload is small, read-heavy, and cacheable.</p>
<h2 id="privacy-and-scope">Privacy and scope</h2>
<p>The app is intentionally personal and ad-free. It does not need accounts, social features, or public rankings. It needs to answer a small question reliably.</p>
<p>The main external dependency risk is data quality and API availability. That makes caching and clear failure states important.</p>
<h2 id="what-i-learned">What I learned</h2>
<p>Personal dashboards are worth building when they compress repeated decision-making. The value is not that the app is complicated. The value is that it turns several tabs and mental calculations into one screen.</p>
<p>The scoring model should also stay humble. A “good fishing day” score is a decision aid, not a guarantee. The app should make the inputs visible enough that I can override the score with judgment.</p>
<h2 id="current-status">Current status</h2>
<p><code>good-fishing-day</code> is private because it is tied to personal usage and deployment details. The architecture pattern is reusable: small Worker backend, cached external data, static frontend, and a domain-specific score.</p>
]]></content:encoded>
    </item>
    <item>
      <title>map-ask: Natural-Language Geospatial Search</title>
      <link>https://blog.minifish.org/posts/map-ask-natural-language-geosearch/</link>
      <pubDate>Thu, 04 Jun 2026 09:00:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/map-ask-natural-language-geosearch/</guid>
      <description>A project note on map-ask, a natural-language geospatial search prototype using open map data, POI search, routing, environment lookup, and weather enrichment.</description>
      <content:encoded><![CDATA[<p><code>map-ask</code> is a natural-language geospatial search prototype. The user asks a map question in plain English, and the system converts it into structured actions such as POI search, routing, environment lookup, or weather enrichment.</p>
<p>The interesting constraint is that it aims to use open data and avoid API keys where possible.</p>
<h2 id="why-maps-are-hard-for-language-models">Why maps are hard for language models</h2>
<p>Map questions sound simple:</p>
<ul>
<li>find coffee near me</li>
<li>show quiet parks nearby</li>
<li>route to a place with shade</li>
<li>find restaurants within walking distance</li>
</ul>
<p>But those questions mix language, location, distance, categories, freshness, and user context. A language model can understand the request, but it should not invent geography.</p>
<p>The system needs tools.</p>
<h2 id="open-data-shape">Open-data shape</h2>
<p>The project uses sources such as OpenStreetMap, Nominatim, Overpass, Wikimedia REST, and related open endpoints. The map UI is built around Leaflet and React.</p>
<p>The model&rsquo;s role is to translate intent into geospatial operations:</p>
<ul>
<li>what is the user looking for?</li>
<li>what radius or location matters?</li>
<li>is this a route, POI search, or environment query?</li>
<li>what enrichment should be added?</li>
</ul>
<p>The tools then fetch actual data.</p>
<h2 id="product-boundary">Product boundary</h2>
<p>The app should be honest about uncertainty. Open geospatial data is uneven. Names change, POI tags are inconsistent, and “quiet” or “hipster” are semantic hints rather than guaranteed database fields.</p>
<p>That means the UI should show results as interpreted matches, not absolute truth.</p>
<h2 id="what-i-learned">What I learned</h2>
<p>Natural-language interfaces work best when they produce inspectable structured plans. If the app can show the interpreted action, it becomes much easier to debug:</p>
<ul>
<li>searched category</li>
<li>center point</li>
<li>radius</li>
<li>data source</li>
<li>filters</li>
<li>enrichment steps</li>
</ul>
<p>This is especially important for maps because wrong answers can look visually convincing.</p>
<h2 id="current-status">Current status</h2>
<p><code>map-ask</code> is private and prototype-stage. It is safe to discuss as an architecture pattern: use an LLM for intent shaping, then rely on real geospatial tools for facts.</p>
]]></content:encoded>
    </item>
    <item>
      <title>AeroPlay: A Flight-Mode Game Hub</title>
      <link>https://blog.minifish.org/posts/aeroplay-offline-game-hub/</link>
      <pubDate>Wed, 03 Jun 2026 09:00:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/aeroplay-offline-game-hub/</guid>
      <description>A project note on AeroPlay, a pure frontend offline game hub with mini games, localStorage progress, service worker caching, and add-to-home-screen support.</description>
      <content:encoded><![CDATA[<p>AeroPlay is a pure-frontend, mobile-first game hub optimized for flight-mode use. It includes small games like Snake, Tetris, 2048, Flappy Bird, Maze, Match-3, Sudoku, and Lights Out.</p>
<p>The whole point is that it should keep working with no network.</p>
<h2 id="why-build-it">Why build it</h2>
<p>Airplane mode is a useful product constraint. It removes a lot of lazy assumptions:</p>
<ul>
<li>no backend calls</li>
<li>no CDN dependency during play</li>
<li>no login</li>
<li>no remote save</li>
<li>no ads</li>
<li>no analytics requirement</li>
</ul>
<p>What remains is the app itself: local assets, local state, and games that are worth playing in short sessions.</p>
<h2 id="template-shape">Template shape</h2>
<p>AeroPlay is also designed as a template. The repo can be used to bootstrap another offline game hub without carrying old issues or unrelated history.</p>
<p>That makes the project more useful than a single app. It becomes a base pattern:</p>
<ul>
<li>Vite and TypeScript</li>
<li>local game modules</li>
<li>service worker</li>
<li>manifest</li>
<li><code>localStorage</code> progress</li>
<li>mobile-first layout</li>
</ul>
<h2 id="product-boundary">Product boundary</h2>
<p>The project should not become a general gaming platform. Its strength is the opposite: a small set of local games, predictable input, and no network requirement.</p>
<p>For this kind of app, “more features” can easily make the experience worse. If the user opens it in flight mode, the app should not show half-broken online features.</p>
<h2 id="what-i-learned">What I learned</h2>
<p>Offline-first is easiest when it is a requirement from the beginning. Retrofitting offline behavior onto an app that assumed servers and remote assets is much harder.</p>
<p>AeroPlay also shows that simple games still need polish:</p>
<ul>
<li>touch controls</li>
<li>stable layout</li>
<li>saved state</li>
<li>fast startup</li>
<li>no accidental scroll</li>
<li>clear pause and restart behavior</li>
</ul>
<p>Those details matter more on a phone than in a desktop demo.</p>
<h2 id="open-source-status">Open source status</h2>
<p>AeroPlay is public because it is self-contained, harmless to share, and useful as a template. It is a good example of a small project whose constraints make it more reusable.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Bedrock Lab: A Behavior Pack as a Small Rules Sandbox</title>
      <link>https://blog.minifish.org/posts/bedrock-lab-behavior-pack/</link>
      <pubDate>Tue, 02 Jun 2026 09:00:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/bedrock-lab-behavior-pack/</guid>
      <description>A project note on Bedrock Lab, a Minecraft Bedrock behavior pack with sprint regeneration, random effects, custom drops, and fishing loot changes.</description>
      <content:encoded><![CDATA[<p>Bedrock Lab is a behavior-only sandbox for Minecraft Bedrock Edition. It packages small gameplay rule experiments into a behavior pack that can be imported and activated without experimental toggles.</p>
<p>The project is not meant to be a large mod. It is a rules sandbox.</p>
<h2 id="what-it-changes">What it changes</h2>
<p>The pack includes features such as:</p>
<ul>
<li>sprint regeneration</li>
<li>random player effects</li>
<li>torch drops on non-player entity death</li>
<li>local snow effect on player death</li>
<li>random material drops from broken blocks</li>
<li>adjusted fishing loot weights</li>
</ul>
<p>These are intentionally playful mechanics. The point is to test how small rule changes affect the feel of a world.</p>
<h2 id="why-behavior-only-matters">Why behavior-only matters</h2>
<p>Keeping the pack behavior-only lowers the setup cost. There is no custom client asset pipeline and no experimental toggle requirement.</p>
<p>That makes it easier to share across devices:</p>
<ul>
<li>package as <code>.mcpack</code></li>
<li>import on iOS or Windows</li>
<li>activate in world settings</li>
<li>play immediately</li>
</ul>
<p>For a small personal pack, install friction matters more than architectural elegance.</p>
<h2 id="the-design-lesson">The design lesson</h2>
<p>Game modding is a good reminder that systems can be fun without being complicated. A simple rule like “breaking blocks produces random materials” changes the entire resource economy.</p>
<p>The engineering lesson is to make each rule easy to remove. If a mechanic stops being fun, it should not be tangled with the rest of the pack.</p>
<h2 id="what-i-learned">What I learned</h2>
<p>Small game experiments should be packaged early. A mechanic that only works in a local development folder is not really tested. It needs to be imported, activated, and played in the actual environment.</p>
<p>Bedrock Lab&rsquo;s value is that it turns ideas into a package quickly. That makes it easier to decide whether a mechanic is worth keeping.</p>
<h2 id="current-status">Current status</h2>
<p>Bedrock Lab is private and small. It is the kind of project that is more useful as a personal sandbox than as a maintained public mod.</p>
]]></content:encoded>
    </item>
    <item>
      <title>tegdb-server: Putting a PostgreSQL-Compatible Protocol in Front of TegDB</title>
      <link>https://blog.minifish.org/posts/tegdb-server-postgres-compatible/</link>
      <pubDate>Mon, 01 Jun 2026 09:00:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/tegdb-server-postgres-compatible/</guid>
      <description>A project note on tegdb-server, a Rust server that puts a PostgreSQL-compatible protocol and network boundary in front of the TegDB embedded database.</description>
      <content:encoded><![CDATA[<p><code>tegdb-server</code> is a PostgreSQL-compatible database server built on top of TegDB. It explores what changes when an embedded database becomes a networked service.</p>
<p>The project is small, but the architectural jump is large.</p>
<h2 id="why-add-a-server">Why add a server</h2>
<p>An embedded database can keep many assumptions local:</p>
<ul>
<li>one process owns access</li>
<li>file paths are local</li>
<li>query state is in-process</li>
<li>errors are library errors</li>
<li>clients are application code</li>
</ul>
<p>A server changes the boundary. Now there are connections, protocol messages, authentication questions, configuration, network errors, allocation pressure, and client compatibility expectations.</p>
<p>That is why this project exists: to study that boundary.</p>
<h2 id="postgresql-compatibility">PostgreSQL compatibility</h2>
<p>PostgreSQL compatibility is useful because it gives the project a familiar client surface. If a normal client can connect, the database becomes easier to test and demonstrate.</p>
<p>But compatibility is also a trap. The PostgreSQL ecosystem expects a lot:</p>
<ul>
<li>protocol behavior</li>
<li>type handling</li>
<li>error responses</li>
<li>transaction semantics</li>
<li>SQL behavior</li>
<li>metadata queries</li>
</ul>
<p>The right goal for a small project is progressive compatibility, not pretending to be PostgreSQL.</p>
<h2 id="storage-boundary">Storage boundary</h2>
<p><code>tegdb-server</code> keeps TegDB as the storage engine and adds a server layer in front. That separation is useful:</p>
<ul>
<li>TegDB owns on-disk behavior</li>
<li>the server owns connections and protocol handling</li>
<li>configuration stays explicit</li>
<li>future distribution experiments have a place to attach</li>
</ul>
<p>The project README frames this as progressive scalability from single-node to more distributed deployment shapes. That is ambitious, but it starts with a concrete first step: one server in front of one storage engine.</p>
<h2 id="what-i-learned">What I learned</h2>
<p>The protocol path has different performance concerns from the storage path. Per-query allocations, message parsing, response construction, and connection lifecycle can dominate before the storage engine becomes interesting.</p>
<p>This is a useful reminder: a database is not only its storage engine. It is also a wire protocol, client contract, operations surface, and set of compatibility promises.</p>
<h2 id="open-source-status">Open source status</h2>
<p><code>tegdb-server</code> is public because it pairs naturally with the public TegDB experiment. It is best read as a learning project about database server boundaries.</p>
]]></content:encoded>
    </item>
    <item>
      <title>cta-guard: Risk Controls Before Trading Logic</title>
      <link>https://blog.minifish.org/posts/cta-guard-trading-risk-system/</link>
      <pubDate>Sun, 31 May 2026 09:00:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/cta-guard-trading-risk-system/</guid>
      <description>A sanitized project note on cta-guard, a Rust trading runtime and research tool focused on risk controls, ingestion, backtesting, and operational guardrails.</description>
      <content:encoded><![CDATA[<p><code>cta-guard</code> is a private trading-system project. It includes a runtime loop, ingestion tools, backtesting, walk-forward validation, health checks, and venue integration.</p>
<p>This is the kind of project where the most useful public topic is not the trading strategy. It is the guardrail design.</p>
<h2 id="why-risk-first">Why risk first</h2>
<p>Trading code has a dangerous failure mode: a bug can become a position.</p>
<p>That changes the engineering priority. Before clever strategy logic, the system needs operational controls:</p>
<ul>
<li>dry-run mode</li>
<li>explicit venue adapters</li>
<li>heartbeat checks</li>
<li>halted state</li>
<li>read-only mode</li>
<li>data freshness checks</li>
<li>position and exposure constraints</li>
<li>clear separation between research and live execution</li>
</ul>
<p>The project name reflects that priority. The guard matters before the CTA.</p>
<h2 id="system-shape">System shape</h2>
<p>The maintained path is a modular trader stack:</p>
<ul>
<li>runtime orchestration</li>
<li>risk app service</li>
<li>strategy app service</li>
<li>backtest app service</li>
<li>domain models and ports</li>
<li>SQLite repositories</li>
<li>venue adapters and stubs</li>
</ul>
<p>There are separate binaries for live or dry-run runtime, health checks, ingestion, historical import, backtest, and walk-forward validation.</p>
<p>That separation is important. A backtest binary should not have the same operational authority as a live execution process.</p>
<h2 id="what-should-stay-private">What should stay private</h2>
<p>Some details are not appropriate for a public blog:</p>
<ul>
<li>strategy parameters</li>
<li>production configuration</li>
<li>account or venue details</li>
<li>exact deployment topology</li>
<li>operational thresholds</li>
<li>anything that could imply a trade recommendation</li>
</ul>
<p>The safe public layer is architecture: how to keep execution constrained, observable, and stoppable.</p>
<h2 id="what-i-learned">What I learned</h2>
<p>Risk control is not one module. It is a state machine that should shape the whole runtime.</p>
<p>A good trading system needs to answer boring questions quickly:</p>
<ul>
<li>is data fresh?</li>
<li>is the venue reachable?</li>
<li>is the runtime allowed to trade?</li>
<li>what happens after an error?</li>
<li>can the system stop without making things worse?</li>
<li>can research code accidentally reach execution?</li>
</ul>
<p>If those answers are unclear, the system is not ready for live use.</p>
<h2 id="current-status">Current status</h2>
<p><code>cta-guard</code> is private. I may continue writing about its engineering lessons, but only at the level of risk design, runtime isolation, and validation workflow.</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>QuackLake: A DuckDB-WASM Analytics Prototype</title>
      <link>https://blog.minifish.org/posts/quacklake-wasm-analytics-prototype/</link>
      <pubDate>Fri, 29 May 2026 09:00:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/quacklake-wasm-analytics-prototype/</guid>
      <description>A project note on QuackLake, a prototype combining DuckDB-WASM compute, MinIO object storage, a Rust API, and an HTTP range gateway.</description>
      <content:encoded><![CDATA[<p>QuackLake is a WASM-first analytics prototype. It combines DuckDB-WASM compute, MinIO object storage, a Rust control plane, and an HTTP range gateway.</p>
<p>The project is deliberately prototype-shaped. It asks what an analytics stack looks like when browser or WASM compute is treated as a real part of the system rather than only a demo surface.</p>
<h2 id="components">Components</h2>
<p>The local stack is split into small services:</p>
<ul>
<li>API control plane</li>
<li>HTTP range gateway over object storage</li>
<li>Node runner using DuckDB-WASM</li>
<li>browser query UI</li>
<li>MinIO for S3-compatible storage</li>
</ul>
<p>That separation makes the data flow easier to inspect:</p>
<ol>
<li>data lives in object storage</li>
<li>range requests expose only needed bytes</li>
<li>DuckDB-WASM runs queries</li>
<li>lineage or job metadata can be tracked by the control plane</li>
</ol>
<h2 id="why-duckdb-wasm">Why DuckDB-WASM</h2>
<p>DuckDB is already a strong fit for local analytics. DuckDB-WASM adds a different deployment shape: query execution can move closer to the browser, a worker, or a constrained runtime.</p>
<p>The attraction is not that every analytics job should run in the browser. The attraction is that the compute boundary becomes flexible.</p>
<h2 id="what-the-prototype-tests">What the prototype tests</h2>
<p>QuackLake is useful for testing system questions:</p>
<ul>
<li>how painful are range requests in practice?</li>
<li>where should metadata live?</li>
<li>what should the API own?</li>
<li>when does browser compute become awkward?</li>
<li>how much can object storage simplify the backend?</li>
</ul>
<p>These are architectural questions, not UI questions.</p>
<h2 id="what-i-learned">What I learned</h2>
<p>WASM analytics is promising, but the hard parts move around. Query execution can be surprisingly capable, while data access, file layout, caching, and observability become the real design surface.</p>
<p>The range gateway is a good example. It sounds like plumbing, but it shapes whether the system feels smooth or fragile.</p>
<h2 id="current-status">Current status</h2>
<p>QuackLake is private and experimental. I would not present it as a finished product. Its value is as a small lab for object-storage-first analytics and DuckDB-WASM deployment patterns.</p>
]]></content:encoded>
    </item>
    <item>
      <title>ob-skills: Packaging OceanBase Knowledge as Agent Skills</title>
      <link>https://blog.minifish.org/posts/ob-skills-oceanbase-agent-skills/</link>
      <pubDate>Thu, 28 May 2026 09:00:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/ob-skills-oceanbase-agent-skills/</guid>
      <description>A project note on ob-skills, a set of OceanBase Cursor agent skills for solution architecture, DDL syntax checking, and embedded validation workflows.</description>
      <content:encoded><![CDATA[<p><code>ob-skills</code> packages OceanBase knowledge as agent skills. It is not an application in the usual sense. It is a set of reusable instructions and workflows for database architecture, DDL review, and tooling guidance.</p>
<p>The project exists because raw prompting is not enough for repeated technical work.</p>
<h2 id="what-is-inside">What is inside</h2>
<p>The repo contains skills such as:</p>
<ul>
<li>OceanBase solution architect guidance</li>
<li>OceanBase DDL syntax checking</li>
<li>seekdb-backed DDL validation</li>
</ul>
<p>Each skill tries to narrow the agent&rsquo;s behavior for a specific kind of work. Instead of asking a general model to “help with OceanBase,” the skill defines what kind of answer is expected, which constraints matter, and which validation path should be used.</p>
<h2 id="why-skills-instead-of-notes">Why skills instead of notes</h2>
<p>Traditional notes help humans. Skills help agents act more consistently.</p>
<p>That difference matters. A good skill should:</p>
<ul>
<li>define the task boundary</li>
<li>mention common failure modes</li>
<li>route to validation where possible</li>
<li>make assumptions explicit</li>
<li>avoid generic advice when concrete checks exist</li>
</ul>
<p>For database work, this is especially useful because the cost of vague answers is high. A DDL suggestion that sounds plausible but violates grammar or deployment constraints is worse than no suggestion.</p>
<h2 id="the-useful-pattern">The useful pattern</h2>
<p>The best skills are not huge knowledge dumps. They are small operational wrappers around repeatable judgment.</p>
<p>For example, a DDL validation skill should not merely explain DDL. It should push the agent toward an actual validation path and make clear when offline validation is possible.</p>
<p>That is the difference between “LLM as autocomplete” and “LLM as a guided operator.”</p>
<h2 id="what-i-learned">What I learned</h2>
<p>Agent quality improves when domain knowledge is packaged close to the workflow. It is not enough to rely on the model remembering a product correctly.</p>
<p>Skills are also easier to maintain than long prompts embedded in many tools. If a rule changes, update the skill. If a validation command improves, update the skill. The agent workflows that use it inherit the better behavior.</p>
<h2 id="open-source-status">Open source status</h2>
<p><code>ob-skills</code> is public because the project is mostly guidance and validation workflow. It is a good fit for sharing: others can inspect the assumptions, reuse the structure, or adapt the skill pattern to their own database work.</p>
]]></content:encoded>
    </item>
    <item>
      <title>TegDB: Notes from Building a Small Embedded Database</title>
      <link>https://blog.minifish.org/posts/tegdb-embedded-database/</link>
      <pubDate>Wed, 27 May 2026 09:00:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/tegdb-embedded-database/</guid>
      <description>A project note on TegDB, a lightweight embedded database engine with ACID transactions, WAL recovery, B&#43;tree indexing, and a SQL-like interface.</description>
      <content:encoded><![CDATA[<p>TegDB is a lightweight embedded database engine with a SQL-like interface. It is written in Rust and focuses on simplicity, reliability, and predictable behavior.</p>
<p>The project is not trying to compete with SQLite. It is a learning and systems-design project: what happens when you build the parts of a small database yourself?</p>
<h2 id="design-philosophy">Design philosophy</h2>
<p>The most important choice is simplicity. TegDB uses a deliberately constrained architecture:</p>
<ul>
<li>embedded engine</li>
<li>single-process access</li>
<li>write-ahead logging</li>
<li>crash recovery</li>
<li>B+tree-style indexing</li>
<li>schema and constraint checks</li>
<li>SQL-like query surface</li>
</ul>
<p>Avoiding broad concurrency is not a weakness for this kind of project. It reduces the number of failure modes and makes the storage path easier to reason about.</p>
<h2 id="why-build-a-database">Why build a database</h2>
<p>Database internals are easier to understand when you have to implement the boring parts:</p>
<ul>
<li>how records are serialized</li>
<li>how pages are found</li>
<li>how an index points to storage</li>
<li>when a transaction becomes durable</li>
<li>how recovery decides what to replay</li>
<li>how a query stops early with <code>LIMIT</code></li>
</ul>
<p>Reading database code is useful. Writing a small one makes the tradeoffs harder to ignore.</p>
<h2 id="reliability-work">Reliability work</h2>
<p>The most interesting code in a database is often not the happy path. TegDB spends attention on:</p>
<ul>
<li>write-ahead log commit markers</li>
<li>rollback behavior</li>
<li>partial write handling</li>
<li>file locking</li>
<li>recovery after crash</li>
<li>corruption boundaries</li>
</ul>
<p>These are the parts that turn “data structure on disk” into “database.”</p>
<h2 id="what-i-learned">What I learned</h2>
<p>The database abstraction is expensive because it hides many coordinated guarantees. Even a small embedded engine has to care about durability, serialization, schema behavior, query planning shortcuts, and operational failure.</p>
<p>The lesson is not that every app should have a custom database. The lesson is the opposite: after building even a small one, you gain more respect for mature storage engines.</p>
<h2 id="open-source-status">Open source status</h2>
<p>TegDB is public because the project is mainly educational and architectural. It is useful as a record of experiments in storage design, not as a claim that it should replace established embedded databases.</p>
]]></content:encoded>
    </item>
    <item>
      <title>agentd: A Transport-Neutral Runtime for Personal Agents</title>
      <link>https://blog.minifish.org/posts/agentd-wasm-agent-runtime/</link>
      <pubDate>Tue, 26 May 2026 09:00:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/agentd-wasm-agent-runtime/</guid>
      <description>A project note on agentd, a single-host agent runtime with a transport-neutral turn API, a built-in generic agent, SeekDB-backed state, and explicit host tools.</description>
      <content:encoded><![CDATA[<p><code>agentd</code> is a single-host agent runtime and control plane. Its current shape is transport-neutral: external systems drive agents through <code>POST /v1/turns</code>, then read results back through the run output API. The runtime owns state, tools, scheduling, memory, artifacts, and the generic agent loop.</p>
<p>That is a different direction from the first version of this note, which described <code>agentd</code> mainly as a Wasm-native harness. The pushed version is now more concrete: an <code>Agent</code> is a manifest with a system prompt, visible tool catalog, resource limits, and a model choice. The actual handler is the built-in generic agent.</p>
<h2 id="turn-api">Turn API</h2>
<p>A caller submits:</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/turns
</span></span></code></pre></div><p>with a tenant, <code>agent_ref</code>, scope, and payload. The runtime creates an <code>AgentRun</code>, resolves a lane, assembles the visible tools, invokes the generic agent, and records a structured <code>final_decision</code>.</p>
<p>There is no push side. A Telegram bot, a browser game, or another transport bridge should call the API and render the result itself. That keeps <code>agentd</code> from becoming coupled to a specific messaging system.</p>
<h2 id="capability-surface">Capability surface</h2>
<p>The current public tool surface includes:</p>
<ul>
<li><code>plan.*</code></li>
<li><code>run.*</code></li>
<li><code>schedule.*</code></li>
<li><code>artifact.*</code></li>
<li><code>memory.*</code></li>
<li><code>context.*</code></li>
<li><code>web.*</code></li>
<li><code>clock.*</code></li>
<li><code>llm.*</code></li>
<li><code>output.*</code></li>
</ul>
<p>The important pattern is that tools carry reliability metadata: maturity, idempotency, side-effect scope, failure classes, replay expectations, and delivery semantics. That metadata is part of making agent behavior inspectable instead of magical.</p>
<h2 id="storage-model">Storage model</h2>
<p>Durable state now lives in SeekDB, a MySQL-compatible OceanBase-derived database. Control-plane state, context, artifacts, and memory all use that one database boundary. Memory is per tenant and uses native vector plus full-text retrieval.</p>
<p>This is a pragmatic simplification. There is no MinIO, S3, Litestream, or separate object store in the core path.</p>
<h2 id="what-i-learned">What I learned</h2>
<p>Agents need less magic and more runtime discipline. Even a personal agent runtime has to answer boring operational questions:</p>
<ul>
<li>context scopes matter</li>
<li>retries can duplicate work</li>
<li>memory needs indexing and pruning</li>
<li>tools need permission boundaries</li>
<li>failures need artifacts and logs</li>
<li>callers need a stable way to wait for output</li>
</ul>
<p>The useful boundary is no longer just Wasm. It is the turn contract plus explicit host capabilities.</p>
<h2 id="current-status">Current status</h2>
<p><code>agentd</code> is experimental and private. It is useful as a lab for runtime shape, but I do not consider it a public platform yet. The public lesson is the architecture: keep transports outside the core runtime, keep tool capabilities explicit, and make every run produce inspectable output.</p>
]]></content:encoded>
    </item>
    <item>
      <title>OB Sizer: Turning Migration Sizing into a Browser Tool</title>
      <link>https://blog.minifish.org/posts/ob-sizer-capacity-estimator/</link>
      <pubDate>Sun, 24 May 2026 08:25:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/ob-sizer-capacity-estimator/</guid>
      <description>A project note on OB Sizer, a browser-based OceanBase migration capacity estimator for common source databases and workload assumptions.</description>
      <content:encoded><![CDATA[<p>OB Sizer is an OceanBase migration capacity estimator. It helps solution architects turn rough source-system facts into an initial OceanBase cluster sizing conversation.</p>
<p>It is a pure frontend tool. All calculations run in the browser, and the estimate can be shared through encoded URLs.</p>
<h2 id="why-build-it">Why build it</h2>
<p>Sizing discussions often begin with incomplete information:</p>
<ul>
<li>source database type</li>
<li>data size</li>
<li>growth expectations</li>
<li>workload style</li>
<li>read/write ratio</li>
<li>availability requirement</li>
<li>target deployment shape</li>
</ul>
<p>The first estimate is rarely final. But without a structured model, the conversation quickly becomes a spreadsheet with hidden assumptions.</p>
<p>OB Sizer makes the assumptions visible.</p>
<h2 id="supported-shape">Supported shape</h2>
<p>The tool covers common migration sources:</p>
<ul>
<li>MySQL</li>
<li>Oracle</li>
<li>PostgreSQL</li>
<li>TiDB</li>
<li>Aurora</li>
<li>SQL Server</li>
</ul>
<p>It models OceanBase replica choices such as <code>2F1A</code> and <code>3F</code>, real OB Cloud SKU shapes, and workload differences across OLTP, HTAP, and mixed patterns.</p>
<p>The important design choice is that it is not only a calculator. It includes methodology in the app, so users can see why a number appears.</p>
<h2 id="product-boundary">Product boundary</h2>
<p>OB Sizer should not pretend to replace a real sizing engagement. The correct role is earlier:</p>
<ul>
<li>make first-pass estimates consistent</li>
<li>expose assumptions</li>
<li>help compare scenarios</li>
<li>create a common language for follow-up questions</li>
</ul>
<p>That boundary keeps the tool useful without making it misleading.</p>
<h2 id="why-browser-only">Why browser-only</h2>
<p>A browser-only tool is easy to share and easy to run in customer-facing discussions. It also avoids collecting workload information on a backend.</p>
<p>The tradeoff is that the model and SKU tables have to ship with the app. That is acceptable because the tool is a discussion aid, not a real-time pricing or provisioning system.</p>
<h2 id="what-i-learned">What I learned</h2>
<p>Capacity estimation tools are part engineering and part communication. A technically clever formula is not useful if the user cannot see its assumptions.</p>
<p>The best parts of OB Sizer are the ones that make uncertainty explicit:</p>
<ul>
<li>workload multipliers</li>
<li>replica mode impact</li>
<li>CPU and memory assumptions</li>
<li>storage overhead</li>
<li>write amplification</li>
<li>safety margins</li>
</ul>
<p>For database migration work, that transparency matters more than pretending the first number is exact.</p>
]]></content:encoded>
    </item>
    <item>
      <title>TapSurge: An iPad Tap-Speed Tool Built for Competition Use</title>
      <link>https://blog.minifish.org/posts/tapsurge-ipad-tap-test/</link>
      <pubDate>Sun, 24 May 2026 08:20:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/tapsurge-ipad-tap-test/</guid>
      <description>A project note on TapSurge, a Vite and TypeScript tap-speed competition tool designed for iPad, offline use, and raw timestamp export.</description>
      <content:encoded><![CDATA[<p>TapSurge is an offline-first multi-finger click-speed test for iPad competition use. It is built with Vite, TypeScript, native DOM APIs, and Canvas.</p>
<p>The product sounds tiny: count taps for 10, 30, or 60 seconds. The interesting part is making that count credible.</p>
<h2 id="why-a-custom-tool">Why a custom tool</h2>
<p>Most tap-speed apps are built for casual play. Competition use has different requirements:</p>
<ul>
<li>work offline</li>
<li>run well on an iPad</li>
<li>avoid accidental browser gestures</li>
<li>count multi-finger input correctly</li>
<li>preserve enough raw data to audit a run</li>
<li>keep history locally without accounts</li>
</ul>
<p>For this use case, simplicity matters more than visual novelty.</p>
<h2 id="input-model">Input model</h2>
<p>TapSurge uses <code>pointerdown</code> and active pointer tracking. The key rule is that holding a finger down should not count repeatedly. A tap is a new press, not a frame, animation event, or repeated touch state.</p>
<p>The app records:</p>
<ul>
<li>total taps</li>
<li>average CPS</li>
<li>one-second live CPS</li>
<li>maximum live CPS</li>
<li>maximum simultaneous fingers</li>
<li>raw timestamps and pointer IDs</li>
</ul>
<p>That raw data is important. It lets a run be inspected after the fact instead of trusting only the final number.</p>
<h2 id="offline-design">Offline design</h2>
<p>The app is intended to work in a PWA-like mode. It has a local app shell, a service worker, and no backend dependency during play.</p>
<p>This is one of those projects where offline support is not a nice extra. It is part of the user story. If the tool is used at a booth, in a classroom, or during a small event, network dependency is unnecessary risk.</p>
<h2 id="ui-constraints">UI constraints</h2>
<p>The UI has to be obvious under pressure. A player should not be reading instructions while the timer is running.</p>
<p>The controls are intentionally few:</p>
<ul>
<li>choose duration</li>
<li>start</li>
<li>tap</li>
<li>see result</li>
<li>review history</li>
<li>export evidence if needed</li>
</ul>
<p>The harder work is preventing the browser from interfering: double-tap zoom, scroll gestures, safe-area issues, and fullscreen behavior all matter more on a real iPad than in a desktop browser.</p>
<h2 id="what-i-learned">What I learned</h2>
<p>Small event tools benefit from being boring. The best version is not the most animated one; it is the one that produces trustworthy results with minimal setup.</p>
<p>TapSurge is a good example of a private tool that could be public, but does not need a large roadmap. Its scope is the feature.</p>
]]></content:encoded>
    </item>
    <item>
      <title>qwen-local: Running an OpenAI-Compatible Model Service on Apple Silicon</title>
      <link>https://blog.minifish.org/posts/qwen-local-on-apple-silicon/</link>
      <pubDate>Sun, 24 May 2026 08:15:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/qwen-local-on-apple-silicon/</guid>
      <description>A project note on qwen-local, a local OpenAI-compatible AI service for Apple Silicon using MLX, Qwen, Qwen3 TTS, and Whisper.</description>
      <content:encoded><![CDATA[<p><code>qwen-local</code> is an OpenAI-compatible local model service for a 16 GB Apple Silicon Mac. It wraps local MLX models behind a FastAPI service and exposes chat, embeddings, text-to-speech, and speech-to-text through one local endpoint.</p>
<p>The idea is simple: keep local inference usable by normal OpenAI SDK clients.</p>
<h2 id="why-this-exists">Why this exists</h2>
<p>Local models are most useful when they can plug into existing tools. A model that only works through a special command is interesting, but a model that looks like an OpenAI-compatible service can be used by editors, agents, scripts, and gateways.</p>
<p>That is the purpose of <code>qwen-local</code>. It is not a model research project. It is an adapter and runtime boundary.</p>
<p>The default shape is:</p>
<ul>
<li>MLX for Apple Silicon inference</li>
<li>Qwen for chat</li>
<li>Qwen embeddings</li>
<li>Qwen3 TTS through <code>mlx-audio</code></li>
<li>MLX Whisper for speech-to-text</li>
<li>one <code>/v1</code> API surface</li>
</ul>
<p>Once models are cached, inference should not require external API calls.</p>
<h2 id="the-useful-constraint-16-gb">The useful constraint: 16 GB</h2>
<p>The project targets a realistic personal machine rather than a workstation with huge memory. That constraint forces decisions:</p>
<ul>
<li>prefer quantized models</li>
<li>keep concurrency conservative</li>
<li>avoid loading every capability eagerly if it hurts responsiveness</li>
<li>make stuck inference and runtime locks visible</li>
</ul>
<p>Local AI services fail in different ways from hosted APIs. A hosted provider returns rate-limit errors or provider errors. A local process can get memory pressure, model load stalls, file cache problems, or long single-user queues.</p>
<p>That makes operational behavior part of the product.</p>
<h2 id="relationship-to-tailgate">Relationship to tailgate</h2>
<p><code>qwen-local</code> is the local model service. Tailgate is the gateway that decides when to use it.</p>
<p>Keeping those roles separate matters. The local service should focus on model loading, request compatibility, and media endpoints. The gateway can handle policy, provider selection, fallback, and external clients.</p>
<p>That split keeps <code>qwen-local</code> from becoming a general AI router.</p>
<h2 id="what-i-learned">What I learned</h2>
<p>OpenAI-compatible does not mean full OpenAI clone. The useful target is compatibility for the clients I actually use:</p>
<ul>
<li>chat completions</li>
<li>embeddings</li>
<li>speech generation</li>
<li>transcription</li>
<li>predictable model IDs</li>
<li>normal error shapes where possible</li>
</ul>
<p>The second lesson is that local inference needs a health model. It is not enough to expose an endpoint. I need to know whether the service is loaded, busy, stuck, or unavailable, especially when another tool is routing requests into it.</p>
<h2 id="june-2026-update">June 2026 update</h2>
<p>The pushed version has moved the local speech stack from Kokoro to Qwen3 TTS. The default TTS model is now <code>mlx-community/Qwen3-TTS-12Hz-0.6B-CustomVoice-4bit</code>, served through <code>mlx-audio</code> with <code>local-tts</code> as the API model alias. The default voice is <code>vivian</code>, and <code>&quot;default&quot;</code> maps to that voice.</p>
<p>This keeps the project aligned around Qwen for chat, embeddings, and speech while still keeping Whisper as the ASR backend. It also reinforces the single-worker assumption: chat, embeddings, TTS, and ASR all share one serialized local inference worker so the Mac does not get overloaded by parallel requests.</p>
<h2 id="open-source-status">Open source status</h2>
<p>This project is private because it includes local operational assumptions and is tuned for my own machine. The general pattern is public enough to discuss: make local models boring by putting them behind familiar API contracts.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Tailgate: A Private AI Gateway for Local and Remote Models</title>
      <link>https://blog.minifish.org/posts/tailgate-private-ai-gateway/</link>
      <pubDate>Sun, 24 May 2026 08:10:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/tailgate-private-ai-gateway/</guid>
      <description>A project note on tailgate, a private AI gateway that centralizes model routing, secrets, provider selection, and local model integration.</description>
      <content:encoded><![CDATA[<p>Tailgate is a personal OpenAI-compatible AI gateway. It gives tools like Codex, Cursor, SDK clients, and local agents one private <code>base_url</code>, while provider keys and routing rules stay on a server I control.</p>
<p>It is not meant to be a public model marketplace. The point is not to replace OpenRouter or any other provider. The point is to make my own AI workflow less scattered.</p>
<h2 id="the-problem">The problem</h2>
<p>Once you use multiple model providers, the configuration spreads quickly:</p>
<ul>
<li>local model endpoint</li>
<li>hosted model provider keys</li>
<li>fallback behavior</li>
<li>model names</li>
<li>pricing assumptions</li>
<li>tool-specific environment variables</li>
<li>different capabilities for chat, embeddings, speech, and transcription</li>
</ul>
<p>Every client wants a slightly different setup. That is annoying for normal use and worse for agents, because agent configuration should be boring and repeatable.</p>
<p>Tailgate puts that complexity behind one OpenAI-compatible surface.</p>
<h2 id="design-shape">Design shape</h2>
<p>The core API follows familiar endpoints:</p>
<ul>
<li><code>GET /v1/models</code></li>
<li><code>POST /v1/chat/completions</code></li>
<li><code>POST /v1/embeddings</code></li>
<li><code>POST /v1/audio/speech</code></li>
<li><code>POST /v1/audio/transcriptions</code></li>
</ul>
<p>Behind that surface, the gateway can route requests to local <code>qwen-local</code>, DeepSeek, OpenRouter, or future compatible providers. It tracks provider health, supports streaming passthrough, and can apply simple route selection rules.</p>
<p>The most useful rule is not fancy AI logic. It is policy:</p>
<ul>
<li>prefer local when the task fits</li>
<li>keep secrets off client machines</li>
<li>avoid sending private work to external providers accidentally</li>
<li>fall back only when the route explicitly allows it</li>
</ul>
<h2 id="why-private">Why private</h2>
<p>Tailgate contains too many assumptions about my own environment to be a clean open source project. It is shaped around private networking, provider credentials, model preferences, and operational defaults.</p>
<p>The public lesson is still useful: an AI gateway does not need to start as a large platform. For one person, it can simply be a policy boundary.</p>
<h2 id="what-i-learned">What I learned</h2>
<p>The biggest value of a gateway is not only key management. It is reducing mental overhead.</p>
<p>Before the gateway, every tool needed to know too much. After the gateway, tools only need:</p>
<ul>
<li>one base URL</li>
<li>one API key or private network policy</li>
<li>normal OpenAI-compatible request shapes</li>
</ul>
<p>That makes experiments cheaper. I can change the provider map without editing every client.</p>
<p>The second lesson is that local models need protection. A small local model service may only handle one heavy inference at a time. A gateway can enforce concurrency and fallback rules so clients do not accidentally overload the local runtime.</p>
<h2 id="june-2026-update">June 2026 update</h2>
<p>The pushed version has a clearer premium route. <code>premium/chat</code> now prefers the configured DeepSeek premium model before falling back to the configured OpenRouter premium model. Config order is also used as a tiebreaker when candidates have the same price ranking, which keeps routing behavior predictable instead of surprising.</p>
<p>This is a good example of the gateway&rsquo;s real job: not to be clever, but to make model policy explicit. A client can ask for <code>premium/chat</code>; Tailgate decides which concrete upstream model should satisfy that tier.</p>
<h2 id="current-status">Current status</h2>
<p>Tailgate is active and private. I expect it to stay private unless the configuration model becomes generic enough to be useful outside my own setup.</p>
]]></content:encoded>
    </item>
    <item>
      <title>PocketBabel: Browser Translation Without a Backend</title>
      <link>https://blog.minifish.org/posts/pocketbabel-browser-translation/</link>
      <pubDate>Sun, 24 May 2026 08:05:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/pocketbabel-browser-translation/</guid>
      <description>A project note on PocketBabel, a frontend-only English-Chinese translation app powered by transformers.js and designed for offline reuse.</description>
      <content:encoded><![CDATA[<p>PocketBabel is a frontend-only English-Chinese translation app. It uses <code>@huggingface/transformers</code> in the browser, targets Cloudflare Pages, and is designed to keep working after the model has been downloaded and cached.</p>
<p>The important constraint is that there is no backend inference service. The browser is not just the UI; it is also the runtime.</p>
<h2 id="why-build-it">Why build it</h2>
<p>Most translation tools are service-shaped: text goes to a server, the server runs a model, and the result comes back. That is fine for many cases, but it is also more infrastructure than I wanted for a narrow personal tool.</p>
<p>PocketBabel asks a smaller question: if the scope is only English to Chinese and Chinese to English, can the whole product be a static site?</p>
<p>That decision makes the system easier to reason about:</p>
<ul>
<li>no API key</li>
<li>no account system</li>
<li>no backend deployment</li>
<li>no request logging</li>
<li>no server-side scaling problem</li>
</ul>
<p>The cost is that the first model download matters and browser performance becomes part of the product.</p>
<h2 id="product-boundary">Product boundary</h2>
<p>The project intentionally avoids becoming a general translation platform. The current shape is narrow:</p>
<ul>
<li>English to Chinese</li>
<li>Chinese to English</li>
<li>text input and output</li>
<li>desktop and mobile browser support</li>
<li>PWA shell and offline reuse</li>
</ul>
<p>It does not try to support OCR, speech input, synced history, arbitrary language pairs, or collaborative workflows. Those features are tempting, but they would change the project from a useful small app into a maintenance surface.</p>
<h2 id="technical-shape">Technical shape</h2>
<p>The implementation is a React and Vite app. Translation is handled by transformers.js with browser-side model loading and caching. Deployment is static, which makes Cloudflare Pages a natural fit.</p>
<p>The UI challenge is not only making a textarea and a button. The app needs to explain model state without becoming noisy:</p>
<ul>
<li>model not downloaded yet</li>
<li>model loading</li>
<li>translation running</li>
<li>offline reuse available</li>
<li>failure state when the browser cannot support the runtime well</li>
</ul>
<p>For an app like this, good status text is part of the architecture. If users do not know whether the model is downloading, warming up, or stuck, the app feels broken even when the code is doing the right thing.</p>
<h2 id="what-i-learned">What I learned</h2>
<p>Browser AI is practical when the product scope is narrow. It is much less practical when you pretend the browser is a free replacement for a server.</p>
<p>The useful pattern is:</p>
<ol>
<li>choose a task with a clear input and output</li>
<li>constrain the model set</li>
<li>make loading and caching behavior visible</li>
<li>avoid account and sync features unless they are essential</li>
</ol>
<p>PocketBabel works because it does not try to be Google Translate. It is a small translation surface for a small set of language directions.</p>
<h2 id="june-2026-update">June 2026 update</h2>
<p>The pushed version has grown in two small but useful directions without changing the core product boundary.</p>
<p>First, short word or phrase inputs can now use dictionary-style prompts. That makes PocketBabel more useful for vocabulary lookup: instead of treating every short input as a sentence, the app can ask for meanings, part-of-speech style structure, and usage notes.</p>
<p>Second, translated output can be read aloud through the browser&rsquo;s <code>speechSynthesis</code> API when the browser supports it. This is deliberately output speech, not speech input or speech translation. It keeps the app frontend-only while making the translated result easier to check.</p>
<h2 id="open-source-status">Open source status</h2>
<p>PocketBabel is public because the architecture is self-contained and does not rely on private infrastructure. It is also a good example of the kind of project that benefits from being inspectable: the privacy story is stronger when the code path is visible.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Minifish Lab Projects</title>
      <link>https://blog.minifish.org/posts/minifish-lab-projects/</link>
      <pubDate>Sun, 24 May 2026 08:00:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/minifish-lab-projects/</guid>
      <description>A living index of Minifish Lab projects, with short notes on what each project does, its current status, and the project notes in this series.</description>
      <content:encoded><![CDATA[<p>This is a living index of the projects I keep under Minifish Lab. Some of them are public. Some are private because they contain personal operations, deployment assumptions, trading workflow details, or code that is useful to me but not ready to be maintained for other people.</p>
<p>The goal of this series is not to pretend every small repository should become a product. The goal is to leave a useful engineering record: why each project exists, what design choices mattered, what I learned from it, and where the boundary is between a good private tool and a project worth publishing.</p>
<p>I will keep this page updated as projects change.</p>
<h2 id="project-map">Project map</h2>
<table>
  <thead>
      <tr>
          <th>Project</th>
          <th>Area</th>
          <th>Source</th>
          <th>Status</th>
          <th>Project note</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>PocketBabel</code></td>
          <td>AI / Browser app</td>
          <td>Public</td>
          <td>Active</td>
          <td><a href="/posts/pocketbabel-browser-translation/">PocketBabel: Browser Translation Without a Backend</a></td>
      </tr>
      <tr>
          <td><code>tailgate</code></td>
          <td>AI infrastructure</td>
          <td>Private</td>
          <td>Active</td>
          <td><a href="/posts/tailgate-private-ai-gateway/">Tailgate: A Private AI Gateway for Local and Remote Models</a></td>
      </tr>
      <tr>
          <td><code>qwen-local</code></td>
          <td>Local AI</td>
          <td>Private</td>
          <td>Active</td>
          <td><a href="/posts/qwen-local-on-apple-silicon/">qwen-local: Running an OpenAI-Compatible Model Service on Apple Silicon</a></td>
      </tr>
      <tr>
          <td><code>tapsurge</code></td>
          <td>Offline frontend tool</td>
          <td>Private</td>
          <td>Active</td>
          <td><a href="/posts/tapsurge-ipad-tap-test/">TapSurge: An iPad Tap-Speed Tool Built for Competition Use</a></td>
      </tr>
      <tr>
          <td><code>ob-sizer</code></td>
          <td>Database tooling</td>
          <td>Private</td>
          <td>Active</td>
          <td><a href="/posts/ob-sizer-capacity-estimator/">OB Sizer: Turning Migration Sizing into a Browser Tool</a></td>
      </tr>
      <tr>
          <td><code>agentd</code></td>
          <td>Agent runtime</td>
          <td>Private</td>
          <td>Experimental</td>
          <td><a href="/posts/agentd-wasm-agent-runtime/">agentd: A Transport-Neutral Runtime for Personal Agents</a></td>
      </tr>
      <tr>
          <td><code>canopy</code></td>
          <td>Maps / MCP</td>
          <td>Private</td>
          <td>Active</td>
          <td>Scheduled: 2026-06-09</td>
      </tr>
      <tr>
          <td><code>nightfall</code></td>
          <td>AI game</td>
          <td>Private</td>
          <td>Prototype</td>
          <td>Scheduled: 2026-06-10</td>
      </tr>
      <tr>
          <td><code>driftloop</code></td>
          <td>Generative music</td>
          <td>Private</td>
          <td>Active prototype</td>
          <td>Scheduled: 2026-06-11</td>
      </tr>
      <tr>
          <td><code>mood-midi-mlx</code></td>
          <td>Local AI / Music</td>
          <td>Private</td>
          <td>Research prototype</td>
          <td>Scheduled: 2026-06-12</td>
      </tr>
      <tr>
          <td><code>tegdb</code></td>
          <td>Database</td>
          <td>Public</td>
          <td>Experimental</td>
          <td><a href="/posts/tegdb-embedded-database/">TegDB: Notes from Building a Small Embedded Database</a></td>
      </tr>
      <tr>
          <td><code>ob-skills</code></td>
          <td>AI workflow / Database</td>
          <td>Public</td>
          <td>Active</td>
          <td><a href="/posts/ob-skills-oceanbase-agent-skills/">ob-skills: Packaging OceanBase Knowledge as Agent Skills</a></td>
      </tr>
      <tr>
          <td><code>quacklake</code></td>
          <td>Analytics</td>
          <td>Private</td>
          <td>Prototype</td>
          <td><a href="/posts/quacklake-wasm-analytics-prototype/">QuackLake: A DuckDB-WASM Analytics Prototype</a></td>
      </tr>
      <tr>
          <td><code>scale-kv</code></td>
          <td>Storage systems</td>
          <td>Private</td>
          <td>Experimental</td>
          <td><a href="/posts/scale-kv-storage-experiment/">scale-kv: A Storage Experiment About Complexity</a></td>
      </tr>
      <tr>
          <td><code>cta-guard</code></td>
          <td>Trading systems</td>
          <td>Private</td>
          <td>Sensitive</td>
          <td><a href="/posts/cta-guard-trading-risk-system/">cta-guard: Risk Controls Before Trading Logic</a></td>
      </tr>
      <tr>
          <td><code>tegdb-server</code></td>
          <td>Database server</td>
          <td>Public</td>
          <td>Experimental</td>
          <td><a href="/posts/tegdb-server-postgres-compatible/">tegdb-server: Putting a PostgreSQL-Compatible Protocol in Front of TegDB</a></td>
      </tr>
      <tr>
          <td><code>bedrock-lab</code></td>
          <td>Game modding</td>
          <td>Private</td>
          <td>Small tool</td>
          <td><a href="/posts/bedrock-lab-behavior-pack/">Bedrock Lab: A Behavior Pack as a Small Rules Sandbox</a></td>
      </tr>
      <tr>
          <td><code>aeroplay</code></td>
          <td>Offline games</td>
          <td>Public</td>
          <td>Template-ready</td>
          <td><a href="/posts/aeroplay-offline-game-hub/">AeroPlay: A Flight-Mode Game Hub</a></td>
      </tr>
      <tr>
          <td><code>map-ask</code></td>
          <td>Maps / AI</td>
          <td>Private</td>
          <td>Prototype</td>
          <td><a href="/posts/map-ask-natural-language-geosearch/">map-ask: Natural-Language Geospatial Search</a></td>
      </tr>
      <tr>
          <td><code>good-fishing-day</code></td>
          <td>Personal dashboard</td>
          <td>Private</td>
          <td>Personal utility</td>
          <td><a href="/posts/good-fishing-day-dashboard/">good-fishing-day: A Fishing Weather Dashboard</a></td>
      </tr>
      <tr>
          <td><code>distilmuse</code></td>
          <td>Music AI</td>
          <td>Private</td>
          <td>Remote placeholder</td>
          <td>No project note yet</td>
      </tr>
      <tr>
          <td><code>symbolic-math-mlx</code></td>
          <td>Local AI / Math</td>
          <td>Private</td>
          <td>Remote placeholder</td>
          <td>No project note yet</td>
      </tr>
  </tbody>
</table>
<h2 id="why-write-about-private-projects">Why write about private projects?</h2>
<p>Open source is not the only useful form of sharing. A private project can still contain lessons worth publishing:</p>
<ul>
<li>how the problem was framed</li>
<li>which constraints shaped the architecture</li>
<li>what was deliberately left out</li>
<li>where a prototype became too complex</li>
<li>what I would do differently next time</li>
</ul>
<p>For private repositories, these notes avoid secrets, private network details, exact production configuration, personal data, and sensitive business or trading parameters. The public artifact is the engineering story, not a dump of implementation details.</p>
<h2 id="how-i-classify-these-projects">How I classify these projects</h2>
<p>Some projects are small tools that should stay small. <code>tapsurge</code>, <code>good-fishing-day</code>, and <code>bedrock-lab</code> are in that category. Their value comes from being built for one specific situation.</p>
<p>Some are experiments in system shape. <code>agentd</code>, <code>scale-kv</code>, <code>quacklake</code>, <code>tegdb</code>, and <code>tegdb-server</code> are more about understanding boundaries than shipping a polished product.</p>
<p>Some are workbench infrastructure. <code>qwen-local</code>, <code>tailgate</code>, <code>PocketBabel</code>, <code>canopy</code>, and <code>ob-skills</code> exist because I want AI tools to fit into my own environment instead of depending entirely on remote services.</p>
<p>Some are creative-system prototypes. <code>driftloop</code>, <code>mood-midi-mlx</code>, and <code>nightfall</code> are less about CRUD-product shape and more about how rules, local models, and browser runtimes can create a coherent experience.</p>
<p>Some projects sit close to professional or sensitive domains. <code>ob-sizer</code> is safe to discuss at the methodology level. <code>cta-guard</code> needs more caution: the interesting public topic is risk control and operational design, not trading edge.</p>
<h2 id="maintenance-rule">Maintenance rule</h2>
<p>When a project changes meaningfully, I will update this index first. If the change is only implementation detail, the individual post can stay as a snapshot. If the project changes direction, graduates into a product, or gets retired, the index should say so plainly.</p>
<p>That makes this page the map, and each project post a field note.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Running OpenAI Symphony as a Solo Developer Across Two Repos</title>
      <link>https://blog.minifish.org/posts/symphony-solo-dev-blog/</link>
      <pubDate>Fri, 20 Mar 2026 21:00:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/symphony-solo-dev-blog/</guid>
      <description>A hands-on report on running OpenAI Symphony with Linear, GitHub, and Codex across two personal repositories.</description>
      <content:encoded><![CDATA[<p>I recently spent a session getting <a href="https://github.com/openai/symphony">OpenAI Symphony</a> working on two personal repositories in a solo-developer setup. The goal was simple: use <a href="https://linear.app/">Linear</a> as the task queue, let Symphony pick up issues automatically, and have Codex make code changes with as little manual coordination as possible.</p>
<p>This post is intentionally sanitized. I am not including tokens, local machine details, private paths, secrets, or any internal repository configuration that should not be published.</p>
<h2 id="official-symphony-resources">Official Symphony resources</h2>
<p>If you are trying to reproduce or extend this setup, start from the upstream project rather than this blog post alone:</p>
<ul>
<li><strong>Repository:</strong> <a href="https://github.com/openai/symphony">github.com/openai/symphony</a> — source, issues, and release notes.</li>
<li><strong>Specification:</strong> <a href="https://github.com/openai/symphony/blob/main/SPEC.md"><code>SPEC.md</code></a> — describes the intended behavior and interfaces.</li>
<li><strong>Reference implementation:</strong> <a href="https://github.com/openai/symphony/tree/main/elixir"><code>elixir/</code></a> — Elixir-based reference; follow the README there for build and run details.</li>
</ul>
<p>Symphony is positioned as experimental or preview-quality software; run it only in environments and repositories you trust, and read the repo README for current limitations and safety expectations.</p>
<h2 id="hands-on-from-clone-to-first-run">Hands-on: from clone to first run</h2>
<p>Everything below follows the <a href="https://github.com/openai/symphony/blob/main/elixir/README.md">Elixir reference README</a>. If a step fails, fix that step before tweaking your narrative expectations—the runtime is strict about valid <code>WORKFLOW.md</code> YAML at startup.</p>
<h3 id="0-prerequisites">0. Prerequisites</h3>
<ul>
<li>
<p><strong>Linear:</strong> a workspace where you can create a <strong>project</strong> and issues inside that project.</p>
</li>
<li>
<p><strong>Linear API key:</strong> Settings → Security &amp; access → Personal API keys. Export it in your shell (do not commit it):</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-bash" data-lang="bash"><span style="display:flex;"><span>export LINEAR_API_KEY<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;your_linear_personal_api_key&#34;</span>
</span></span></code></pre></div></li>
<li>
<p><strong>Codex CLI</strong> with <code>codex app-server</code> available (Symphony launches Codex in <a href="https://developers.openai.com/codex/app-server/">App Server mode</a>). Ensure <code>codex</code> is on <code>PATH</code> when the <code>codex.command</code> in <code>WORKFLOW.md</code> runs.</p>
</li>
<li>
<p><strong>Runtime for the reference implementation:</strong> the upstream docs recommend <a href="https://mise.jdx.dev/">mise</a> for Erlang/Elixir versions; use <code>mise install</code> in <code>symphony/elixir</code> as documented.</p>
</li>
</ul>
<h3 id="1-linear-setup-workspace-team-project-workflow-and-api-key">1. Linear setup: workspace, team, project, workflow, and API key</h3>
<p>Symphony only sees what Linear exposes through the API. If the board is wrong, every later step looks like a Symphony bug. Configure Linear <strong>before</strong> you wire <code>project_slug</code> into <code>WORKFLOW.md</code>.</p>
<h4 id="11-workspace-and-team">1.1 Workspace and team</h4>
<ul>
<li>Use a Linear <strong>workspace</strong> you control (personal or org). You need permission to create <strong>projects</strong> and <strong>issues</strong> on a <strong>team</strong>.</li>
<li>Pick the <strong>team</strong> that will own the automated work. Issues are always tied to a team; your project will live under that team’s context. For a solo setup, one dedicated team per “product line” or per repo is enough.</li>
</ul>
<h4 id="12-create-a-project-this-is-the-symphony-queue-boundary">1.2 Create a project (this is the Symphony queue boundary)</h4>
<ol>
<li>In Linear, open <strong>Projects</strong> (or the team’s project list) and <strong>create a new project</strong> for the repository you are automating (for example one GitHub repo ↔ one Linear project).</li>
<li>Give it a clear name so you do not file issues into the wrong queue later.</li>
<li>Open the <strong>project</strong> itself—not only the team backlog. Symphony’s <code>tracker.project_slug</code> refers to <strong>this</strong> project.</li>
</ol>
<h4 id="13-read-the-project_slug-correctly">1.3 Read the <code>project_slug</code> correctly</h4>
<ol>
<li>With the project open, copy the <strong>page URL</strong> from the browser address bar (or use “Copy link” if Linear offers it for the project).</li>
<li>The <strong>slug</strong> is the identifier in that URL that points at this project. Paste it into <code>WORKFLOW.md</code> as <code>project_slug</code> <strong>exactly</strong>—same spelling, same segment the URL uses.</li>
<li>If you rename the project or move it, re-check the URL and update <code>WORKFLOW.md</code>; a stale slug is an instant “nothing happens” failure.</li>
</ol>
<h4 id="14-align-workflow-states-with-workflowmd">1.4 Align workflow <strong>states</strong> with <code>WORKFLOW.md</code></h4>
<p>Your simplified <code>WORKFLOW.md</code> lists <code>active_states</code> and <code>terminal_states</code> (for example <code>Todo</code>, <code>In Progress</code>, <code>Rework</code>, <code>Merging</code>, and terminals like <code>Done</code>, <code>Canceled</code>, <code>Cancelled</code>, <code>Duplicate</code>).</p>
<ol>
<li>In Linear, open <strong>Team settings</strong> → <strong>Workflow</strong> (wording may vary slightly by plan and UI version).</li>
<li>Ensure the <strong>team</strong> that owns this project actually has <strong>status</strong> names that match what you put in YAML—<strong>including spelling</strong> (<code>Canceled</code> vs <code>Cancelled</code> are different strings).</li>
<li>If a state is missing, <strong>add</strong> it to the team workflow. If Linear ships a default you do not use (for example an extra backlog column), you can leave it unused; what matters is that every state your agent and YAML mention <strong>exists</strong> on the board.</li>
<li>Decide how issues <strong>enter</strong> the pipeline: many setups use <code>Todo</code> or <code>In Progress</code> as the first “Symphony should care” state. Put that state in <code>active_states</code> so polling can pick the issue up.</li>
</ol>
<h4 id="15-personal-api-key-symphony-uses-linear_api_key">1.5 Personal API key (Symphony uses <code>LINEAR_API_KEY</code>)</h4>
<ol>
<li>Open your <strong>user Settings</strong> → <strong>Security &amp; access</strong> (or <strong>API</strong> / <strong>Personal API keys</strong>, depending on Linear’s UI).</li>
<li>Create a <strong>new personal API key</strong>, give it a label you will recognize (for example <code>symphony-local</code>).</li>
<li>Copy the key once, set <code>export LINEAR_API_KEY=&quot;...&quot;</code> on the machine that runs Symphony, and <strong>never</strong> commit it to git or paste it into <code>WORKFLOW.md</code> unless you intentionally use env indirection like <code>tracker.api_key: $LINEAR_API_KEY</code> (still keep secrets out of the repo).</li>
</ol>
<h4 id="16-creating-issues-the-way-symphony-expects">1.6 Creating issues the way Symphony expects</h4>
<ol>
<li><strong>Create the issue inside the project:</strong> from the <strong>project</strong> view, use <strong>New issue</strong> (or equivalent) so the issue is <strong>associated with that project</strong>. Creating an issue only on the team backlog without attaching the project is the classic “Symphony is idle” mistake.</li>
<li>Set <strong>title</strong> and <strong>description</strong> to something actionable; the Markdown body of <code>WORKFLOW.md</code> passes <code>issue.title</code> and <code>issue.description</code> into Codex.</li>
<li>Move the issue to a state listed under <code>active_states</code> (for example <code>Todo</code> or <code>In Progress</code>) so it is not sitting in a column Symphony does not poll.</li>
</ol>
<h4 id="17-optional-but-useful">1.7 Optional but useful</h4>
<ul>
<li><strong>Templates:</strong> a small issue template (context, acceptance criteria, “how to validate”) makes agent runs less ambiguous.</li>
<li><strong>Labels:</strong> optional; Symphony does not require them unless you add logic elsewhere.</li>
<li><strong>Permissions:</strong> if the API key belongs to a restricted user, confirm that user can read and update issues in the target project.</li>
</ul>
<p>After this, you can copy <code>project_slug</code> into <code>WORKFLOW.md</code> with confidence. If anything in this section is skipped, revisit <strong>1.3</strong> (slug) and <strong>1.6</strong> (issue in project) first when debugging.</p>
<h3 id="2-build-the-symphony-binary-reference-implementation">2. Build the Symphony binary (reference implementation)</h3>
<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-bash" data-lang="bash"><span style="display:flex;"><span>git clone https://github.com/openai/symphony
</span></span><span style="display:flex;"><span>cd symphony/elixir
</span></span><span style="display:flex;"><span>mise trust
</span></span><span style="display:flex;"><span>mise install
</span></span><span style="display:flex;"><span>mise exec -- mix setup
</span></span><span style="display:flex;"><span>mise exec -- mix build
</span></span></code></pre></div><p>After this, the launcher is <code>./bin/symphony</code> inside <code>symphony/elixir</code> (see the same README). You can start it with an <strong>absolute path</strong> to any <code>WORKFLOW.md</code> you maintain:</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-bash" data-lang="bash"><span style="display:flex;"><span>mise exec -- ./bin/symphony /absolute/path/to/your/repo/WORKFLOW.md
</span></span></code></pre></div><p>If you omit the path, it defaults to <code>./WORKFLOW.md</code> in the current directory—useful when you are iterating inside a single checkout.</p>
<p>Optional flags from upstream:</p>
<ul>
<li><code>--logs-root</code> — log directory (default: <code>./log</code> relative to how you invoke the binary).</li>
<li><code>--port</code> — also starts the optional Phoenix observability UI (dashboard/API as described in the Elixir README).</li>
</ul>
<h3 id="3-add-workflowmd-to-the-repository-you-want-automated">3. Add <code>WORKFLOW.md</code> to the repository you want automated</h3>
<ol>
<li>
<p>Copy the template from the Symphony repo: <a href="https://github.com/openai/symphony/blob/main/elixir/WORKFLOW.md"><code>elixir/WORKFLOW.md</code></a> → your target repo (often repo root).</p>
</li>
<li>
<p><strong>Edit the YAML front matter</strong> for your world:</p>
<ul>
<li><strong><code>tracker.project_slug</code>:</strong> in Linear, open your project, copy its URL from the browser, and take the <strong>slug</strong> segment (the README describes this explicitly).</li>
<li><strong><code>workspace.root</code>:</strong> a directory on disk where Symphony may create <strong>one workspace per issue</strong> (large disk is fine; this is not your git clone root—it is a parent for per-issue workspaces).</li>
<li><strong><code>hooks.after_create</code>:</strong> typically <code>git clone ... .</code> into that workspace so Codex works on a fresh copy of your code. Use the clone URL and branch you actually use (HTTPS or SSH is your choice; private repos need credentials on the machine running Symphony).</li>
<li><strong><code>codex.command</code>:</strong> must match how you invoke App Server locally (model flags, config, etc.). If this command is wrong, the agent never comes up cleanly.</li>
</ul>
</li>
<li>
<p>Align <strong>Linear workflow states</strong> with what <code>WORKFLOW.md</code> expects. The stock template references states such as <code>Todo</code>, <code>In Progress</code>, <code>Rework</code>, <code>Human Review</code>, and <code>Merging</code>. If your team uses different names, either rename states in Linear (Team Settings → Workflow) or edit <code>active_states</code> / <code>terminal_states</code> and the Markdown “status map” in <code>WORKFLOW.md</code> so they match reality.</p>
</li>
<li>
<p>Optionally copy the <strong>skills</strong> from the Symphony repo (<code>commit</code>, <code>push</code>, <code>pull</code>, <code>land</code>, <code>linear</code>, etc.) into your repo if your workflow prompt expects them—the Elixir README calls this out.</p>
</li>
</ol>
<p>Symphony <strong>does not boot</strong> if <code>WORKFLOW.md</code> is missing or the YAML front matter is invalid; fix the file and restart.</p>
<h3 id="4-run-and-sanity-check-before-opening-a-ticket">4. Run and sanity-check before opening a ticket</h3>
<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-bash" data-lang="bash"><span style="display:flex;"><span>export LINEAR_API_KEY<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;...&#34;</span>   <span style="color:#75715e"># if not already in your shell profile</span>
</span></span><span style="display:flex;"><span>cd /path/to/openai/symphony/elixir
</span></span><span style="display:flex;"><span>mise exec -- ./bin/symphony /path/to/your/automated/repo/WORKFLOW.md
</span></span></code></pre></div><p>Then verify:</p>
<ul>
<li>The process stays running and polls on the interval you set (<code>polling.interval_ms</code> in the template).</li>
<li>If you passed <code>--port</code>, you can hit the dashboard/API URLs documented in the Elixir README for live state.</li>
</ul>
<h3 id="5-first-linear-issue-the-mistake-that-looks-like-symphony-is-broken">5. First Linear issue (the mistake that looks like “Symphony is broken”)</h3>
<p>Do this in order or you will get silent no-ops:</p>
<ol>
<li>Create or pick a <strong>Linear project</strong> whose slug matches <strong><code>tracker.project_slug</code></strong> exactly.</li>
<li>Create the issue <strong>inside that project</strong>, not as a free-floating team issue.</li>
<li>Put the issue in an <strong>active</strong> state listed under <code>active_states</code> in <code>WORKFLOW.md</code> (for the default template, something like <code>Todo</code> or <code>In Progress</code>—not <code>Backlog</code> if your prompt tells the agent to ignore <code>Backlog</code>).</li>
</ol>
<p>If Symphony polls successfully but your issue never enters the watched project, you will see healthy logs and zero useful work—this is the <code>project_slug</code> lesson from later in this post.</p>
<h3 id="6-two-repos-repeat-the-pattern">6. Two repos (repeat the pattern)</h3>
<p>For each codebase, maintain <strong>its own</strong> <code>WORKFLOW.md</code>, <strong>its own</strong> Linear project (and slug), <strong>its own</strong> <code>workspace.root</code>, and run <strong>its own</strong> <code>./bin/symphony .../WORKFLOW.md</code> process. Trying to multiplex multiple repositories through one workflow file is how you get accidental coupling and confusing failures.</p>
<hr>
<p>If you want the upstream one-liner to bootstrap with Codex inside your repo, the FAQ in the Elixir README suggests pointing Codex at <a href="https://github.com/openai/symphony/blob/main/elixir/README.md"><code>elixir/README.md</code></a> and asking it to wire files for your codebase—still verify <code>project_slug</code>, workspace paths, and git remotes yourself.</p>
<h2 id="why-i-tried-this">Why I tried this</h2>
<p>What interested me most about Symphony was not “AI that writes code” in isolation. I already have coding tools for that. The interesting part was orchestration:</p>
<ul>
<li>a task source</li>
<li>a state machine</li>
<li>an isolated workspace per task</li>
<li>an agent runtime</li>
<li>a repeatable loop from issue to code change</li>
</ul>
<p>That is a different shape of workflow from normal editor-assisted coding.</p>
<h2 id="why-i-used-linear-instead-of-github-issues">Why I used Linear instead of GitHub Issues</h2>
<p>One thing became clear very quickly: Symphony is designed around Linear as the source of truth for work. It does not naturally start from GitHub Issues. Instead, the workflow looks more like this:</p>
<ol>
<li>Create a Linear issue</li>
<li>Symphony polls the configured Linear project</li>
<li>Symphony creates a dedicated workspace for that issue</li>
<li>Codex works inside that workspace</li>
<li>The workflow advances by issue state</li>
</ol>
<p>At first this felt a little strange, because I am used to GitHub Issues being the center of project work. But after testing it, I could see the logic. Linear is the task system. GitHub is the code system.</p>
<h2 id="the-first-practical-lesson-project-scoping-matters">The first practical lesson: project scoping matters</h2>
<p>A surprisingly easy mistake was creating an issue in the wrong place.</p>
<p>I had a Linear workspace and a correctly configured project, but the first issue I created was not actually attached to the project that Symphony was watching. From the outside it looked like “nothing is happening,” but the real problem was much simpler: Symphony was correctly polling the configured project and my issue was outside that scope.</p>
<p>That was a good reminder that in this setup, <code>project_slug</code> is not a decorative field. It is the queue boundary.</p>
<h2 id="making-workflowmd-actually-usable">Making <code>WORKFLOW.md</code> actually usable</h2>
<p>The <a href="https://github.com/openai/symphony/blob/main/elixir/WORKFLOW.md">stock <code>elixir/WORKFLOW.md</code></a> in the Symphony repository is intentionally large: long status maps, PR sweeps, workpad templates, and guardrails meant for serious team-style orchestration. For solo maintenance on a small repo, that is often more surface area than you want to own on day one.</p>
<p>What I actually wanted was a <strong>small YAML front matter</strong> plus a <strong>short agent brief</strong> that still respects Linear state and runs a tight validate loop.</p>
<p>The elements I kept in practice:</p>
<ul>
<li>one Linear project per repository</li>
<li>one Symphony process per repository</li>
<li>one workspace root per repository</li>
<li>explicit active and terminal states (only the ones I really use)</li>
<li>explicit install/setup commands in <code>after_create</code></li>
<li>explicit validation before completion (<code>npm</code> in my case)</li>
<li><code>codex app-server</code> with sandbox left at workspace write, approval policy set explicitly so the run does not stall on prompts</li>
</ul>
<h3 id="a-simplified-workflowmd-sanitized">A simplified <code>WORKFLOW.md</code> (sanitized)</h3>
<p>Below is the <strong>shape</strong> of the workflow file I run. Values such as the Linear project slug, workspace directory, and git remote are <strong>placeholders</strong>—replace them with your own. Do not copy real identifiers from this post into production without checking them in Linear and Git.</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-md" data-lang="md"><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span>tracker:
</span></span><span style="display:flex;"><span>  kind: linear
</span></span><span style="display:flex;"><span>  project_slug: &#34;your-linear-project-slug&#34;
</span></span><span style="display:flex;"><span>  active_states:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">-</span> Todo
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">-</span> In Progress
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">-</span> Rework
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">-</span> Merging
</span></span><span style="display:flex;"><span>  terminal_states:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">-</span> Done
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">-</span> Canceled
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">-</span> Cancelled
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">-</span> Duplicate
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>polling:
</span></span><span style="display:flex;"><span>  interval_ms: 5000
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>workspace:
</span></span><span style="display:flex;"><span>  root: ~/symphony-workspaces/your-repo-short-name
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>hooks:
</span></span><span style="display:flex;"><span>  after_create: |
</span></span><span style="display:flex;"><span>    git clone --depth 1 https://github.com/your-org/your-repo.git .
</span></span><span style="display:flex;"><span>    npm install
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>agent:
</span></span><span style="display:flex;"><span>  max_concurrent_agents: 1
</span></span><span style="display:flex;"><span>  max_turns: 20
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>codex:
</span></span><span style="display:flex;"><span>  command: codex app-server
</span></span><span style="display:flex;"><span>  approval_policy: never
</span></span><span style="display:flex;"><span>  thread_sandbox: workspace-write
</span></span><span style="display:flex;"><span>  turn_sandbox_policy:
</span></span><span style="display:flex;"><span>    type: workspaceWrite
</span></span><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>You are working on a Linear issue {{ issue.identifier }}.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Title: {{ issue.title }}
</span></span><span style="display:flex;"><span>Body: {{ issue.description }}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Rules:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">-</span> Always start by understanding the current state of the issue.
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">-</span> If state is Todo, move it to In Progress.
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">-</span> If state is Rework, review existing changes and fix issues.
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">-</span> If state is Merging, finalize merge (do not keep coding).
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Execution:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">1.</span> Understand the task
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">2.</span> Reproduce or reason about current behavior
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">3.</span> Make minimal safe changes
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">4.</span> Run:
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">-</span> npm run build OR npm test
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">5.</span> If success:
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">-</span> commit changes
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">-</span> push branch or merge directly according to repository flow
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Do not:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">-</span> Ask humans for help
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">-</span> Modify files outside workspace
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">-</span> Skip validation
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Goal:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Deliver working code change with valid validation and keep the Linear issue state accurate.
</span></span></code></pre></div><h3 id="how-to-fill-this-in-safely">How to fill this in safely</h3>
<ul>
<li><strong><code>project_slug</code>:</strong> in Linear, open the project and copy its URL; the slug is the path segment that identifies the project (see the Elixir README). It must match the project where you create issues.</li>
<li><strong><code>workspace.root</code>:</strong> any empty-friendly parent directory on the machine that runs Symphony; Symphony creates a subdirectory per issue under this root.</li>
<li><strong><code>after_create</code>:</strong> use your real <code>git clone</code> URL and package install command (<code>npm install</code>, <code>pnpm install</code>, <code>make</code>, and so on).</li>
<li><strong>Linear states:</strong> your team must actually define or use states compatible with <code>active_states</code> / <code>terminal_states</code>. If Linear uses different names, edit the lists to match.</li>
</ul>
<p>This was a much better balance for me than copying the entire official workflow verbatim, while still staying inside Symphony’s YAML + Markdown contract.</p>
<h2 id="the-first-real-run">The first real run</h2>
<p>Once the workflow was wired correctly, the first successful run was a great moment. The basic flow worked:</p>
<ol>
<li>Create a Linear issue</li>
<li>Symphony picks it up</li>
<li>A workspace is created</li>
<li>The repository is cloned</li>
<li>Dependencies are installed</li>
<li>Codex makes a change</li>
<li>Validation runs</li>
<li>The issue moves forward in the workflow</li>
</ol>
<p>That first time matters because it changes the whole thing from “interesting repo I am reading” into “real tool I can use.”</p>
<h2 id="a-workflow-surprise-no-pr-direct-merge">A workflow surprise: no PR, direct merge</h2>
<p>One unexpected result was that the run did not produce a pull request. Instead, it created a branch and then merged directly into <code>main</code>.</p>
<p>For a team workflow, that would be a problem. For my personal setup, I actually found it acceptable.</p>
<p>Because I am the only person using this flow right now, direct merge is not automatically bad. It is fast, and it fits a solo maintenance loop. The tradeoff is obvious: less review structure, more need for good validation and discipline.</p>
<p>If I later want a stricter process, the right fix is probably branch protection plus a stronger PR gate in the workflow.</p>
<h2 id="why-i-accepted-a-solo-mode">Why I accepted a “solo mode”</h2>
<p>After thinking about it, I realized there are really two different modes here:</p>
<h3 id="team-mode">Team mode</h3>
<ul>
<li>branch protection</li>
<li>pull requests</li>
<li>human review gates</li>
<li>merge discipline</li>
</ul>
<h3 id="solo-mode">Solo mode</h3>
<ul>
<li>fast issue pickup</li>
<li>direct code change</li>
<li>direct landing when validation passes</li>
</ul>
<p>For now I am explicitly leaning toward solo mode. That is not because it is universally better. It is just a better fit for a single developer trying to reduce friction on personal repos.</p>
<h2 id="scaling-from-one-repo-to-two">Scaling from one repo to two</h2>
<p>After getting the first repository working, I wanted to know whether I could use Symphony across more than one repo.</p>
<p>The answer was yes, but not by forcing one workflow to manage everything. The cleaner model was:</p>
<ul>
<li>one Linear project per repository</li>
<li>one <code>WORKFLOW</code> file per repository</li>
<li>one Symphony process per repository</li>
<li>one workspace root per repository</li>
</ul>
<p>That means each repo gets its own queue, workspace, and execution loop. The result is much easier to reason about than trying to multiplex multiple repos through a single workflow.</p>
<p>Conceptually, the setup became:</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>Repo A -&gt; Linear Project A -&gt; Symphony Process A
</span></span><span style="display:flex;"><span>Repo B -&gt; Linear Project B -&gt; Symphony Process B
</span></span></code></pre></div><p>That separation made the system feel much more stable.</p>
<h2 id="things-that-felt-weird">Things that felt weird</h2>
<p>A few things still feel unusual in this setup:</p>
<h3 id="1-linear-is-the-real-driver">1. Linear is the real driver</h3>
<p>If you are used to GitHub-centric project flow, it takes a minute to reset your intuition.</p>
<h3 id="2-the-workflow-file-is-closer-to-an-operating-manual-than-a-config-file">2. The workflow file is closer to an operating manual than a config file</h3>
<p>It is not just about parameters. It strongly shapes agent behavior.</p>
<h3 id="3-small-scope-mistakes-create-silent-failures">3. Small scope mistakes create “silent failures”</h3>
<p>If the wrong project is watched, or the issue is created in the wrong place, everything can look healthy while nothing useful happens.</p>
<h3 id="4-defaults-are-often-too-implicit">4. Defaults are often too implicit</h3>
<p>Model choice, reasoning depth, safety behavior, and merge style all become much clearer once they are explicitly set instead of left to defaults.</p>
<h2 id="what-i-would-improve-next">What I would improve next</h2>
<p>There are a few upgrades that would make this setup stronger without making it too heavyweight:</p>
<ul>
<li>make validation stricter before landing changes</li>
<li>make commit messages more informative</li>
<li>optionally require PRs for selected repos</li>
<li>capture a better audit trail of what the agent actually did</li>
<li>design a lightweight rollback path for bad automated changes</li>
</ul>
<p>That would preserve the speed of solo mode while reducing the risk of bad direct merges.</p>
<h2 id="final-take">Final take</h2>
<p>My main takeaway is that Symphony becomes much more interesting once it is treated as a workflow runtime, not just a coding demo.</p>
<p>The useful mental model is not “an AI that edits files.” It is closer to this:</p>
<ul>
<li>work arrives through a queue</li>
<li>each task gets an isolated environment</li>
<li>the agent runs inside a bounded workflow</li>
<li>the repo is just one part of the system</li>
</ul>
<p>For a solo developer, that can actually be a very comfortable way to work, as long as the workflow is shaped carefully enough.</p>
<p>It is still early, still a little rough, and definitely not something I would blindly trust everywhere. But for small personal projects, it already feels surprisingly real.</p>
<h2 id="future-potential-kill-the-relay">Future potential: kill the relay</h2>
<p>Symphony is not competing with tab completion. It is a probe for a nastier question: <strong>if work is just queue + policy + execution, why would you keep a permanent class of people whose main job is to sit between a customer sentence and a git merge?</strong></p>
<p>Here is the version I actually believe.</p>
<p><strong>B2B should look like a pipe, not a committee.</strong> Whoever hears the customer—sales engineer, CS, onboarding, whoever—<strong>opens the issue</strong>. That issue is the contract. Behind it, <strong>Symphony-grade orchestration</strong> does the rest: clone, implement, test, merge, release. Not “faster Jira.” Not “AI assists your sprint.” <strong>The default path is machine throughput; humans are for edge cases, politics, and blame.</strong></p>
<p>Does that erase humans? No—it erases <strong>the middle</strong>. The classic career ladder where “product” rewrites reality for “engineering” so engineering can rewrite it again for Git is not destiny. It is <strong>coordination rent</strong>. Orchestration is a wrecking ball aimed at that rent. If your value is mostly translating between tools and meetings, the stack is not coming to help you—it is coming to <strong>delete the slot</strong>.</p>
<p>You can list risks forever—compliance, security, hallucinations, bad merges—and you should. But risk is not a moral argument for headcount. It is an argument for <strong>thinner, sharper ownership</strong>: a tiny number of people who set policy and own catastrophes, plus a machine that does the boring middle at machine speed.</p>
<p>Yes, today’s tools are still a preview: flaky, embarrassing, unsafe if you are lazy about validation. <strong>Irrelevant to the direction.</strong> The direction is <strong>first-hand demand in, shipped software out</strong>, with as few interpreters as the market will tolerate. In ten years, “we need more PMs and more engineers because that is how software is made” will read like “we need more telephone switchboard operators because calls exist.”</p>
<p>My two-repo setup is a toy. The logic is not.</p>
<h2 id="appendix-sanitized-lessons-learned">Appendix: sanitized lessons learned</h2>
<ul>
<li>Configure Linear (project, slug, workflow states, API key, issues inside the project) before blaming Symphony</li>
<li>Start with one repo, not many</li>
<li>Keep one workflow per repo</li>
<li>Use one Linear project per repo</li>
<li>Make state transitions explicit</li>
<li>Do not rely too much on defaults</li>
<li>Validate aggressively before allowing automated landing</li>
<li>Expect the first “nothing happened” failure to be a scoping mistake</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>From AI Conversations to Published Blog: The MCP-Powered Publishing Revolution</title>
      <link>https://blog.minifish.org/posts/ai-mcp-blog-publishing-workflow/</link>
      <pubDate>Mon, 19 Jan 2026 10:00:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/ai-mcp-blog-publishing-workflow/</guid>
      <description>A practical publishing workflow that turns AI conversations into Hugo posts through MCP, GitHub Actions, and a structured blog repository.</description>
      <content:encoded><![CDATA[<h2 id="the-problem-lost-context-lost-thoughts">The Problem: Lost Context, Lost Thoughts</h2>
<p>We&rsquo;ve all been there. You&rsquo;re deep in a technical discussion with an AI assistant—analyzing code, exploring architecture, or debugging a complex issue. The conversation is rich with insights, and you think: &ldquo;This would make a great blog post.&rdquo;</p>
<p>But then reality hits: you need to switch to your blog repository, format the content, commit it, push it, and wait for the build. By the time you&rsquo;re back, the original context is gone, and the momentum is lost.</p>
<p><strong>What if you could publish directly from where you are?</strong></p>
<h2 id="building-on-existing-automation">Building on Existing Automation</h2>
<p>In <a href="/posts/how-to-automatically-publish-a-blog-using-github-actions/">my previous post about automatically publishing a blog using GitHub Actions</a>, I set up a workflow where pushing to the blog repository triggers an automatic build and deployment to GitHub Pages. This solved the build and deployment automation, but there was still one manual step remaining: creating the post file itself.</p>
<p>The workflow I described there handles:</p>
<ol>
<li>Checking out the blog repository</li>
<li>Building the Hugo site with <code>make</code></li>
<li>Deploying to <code>jackysp.github.io</code></li>
</ol>
<p>But you still needed to be in the blog repository to create the post. That&rsquo;s where MCP changes everything.</p>
<h2 id="enter-mcp-the-missing-link">Enter MCP: The Missing Link</h2>
<p>The <a href="https://modelcontextprotocol.io/">Model Context Protocol (MCP)</a> is revolutionizing how AI agents interact with external systems. Instead of treating AI as a passive tool, MCP enables agents to act as autonomous agents with direct access to your tools and workflows.</p>
<p>In my setup, I&rsquo;ve connected MCP-enabled agents (like Cursor) directly to my blog repository via GitHub MCP. This means:</p>
<ul>
<li><strong>No context switching</strong>: Stay in your current working directory, whether it&rsquo;s a random project folder or a deep codebase exploration</li>
<li><strong>Preserve conversation flow</strong>: The AI maintains the full context of your discussion</li>
<li><strong>Direct publishing</strong>: Create and publish posts without leaving your IDE</li>
</ul>
<h2 id="the-architecture-seamless-integration">The Architecture: Seamless Integration</h2>
<p>Here&rsquo;s how the complete workflow operates:</p>
<pre tabindex="0"><code>┌─────────────────────────────────────────────────────────┐
│  AI Agent (Cursor/Claude) with MCP enabled              │
│  - Context: Any code repository or discussion            │
│  - Tool: GitHub MCP Server                               │
└──────────────────┬──────────────────────────────────────┘
                   │
                   │ Creates post via GitHub MCP
                   │
                   ▼
┌─────────────────────────────────────────────────────────┐
│  Blog Repository (jackysp/blog)                         │
│  - content/posts/&lt;slug&gt;/index.md                        │
│  - Commit: &#34;Publish: [title]&#34;                           │
└──────────────────┬──────────────────────────────────────┘
                   │
                   │ Push to master branch
                   │
                   ▼
┌─────────────────────────────────────────────────────────┐
│  GitHub Actions (from previous post)                     │
│  - Build: Hugo static site generation                   │
│  - Deploy: Push to jackysp.github.io                    │
└──────────────────┬──────────────────────────────────────┘
                   │
                   │ Published
                   │
                   ▼
┌─────────────────────────────────────────────────────────┐
│  Live Site (jackysp.github.io)                          │
│  - Post is live and accessible                           │
└─────────────────────────────────────────────────────────┘
</code></pre><p>The GitHub Actions part remains exactly as described in the previous post—no changes needed there. The MCP layer adds the ability to trigger it from anywhere.</p>
<h2 id="the-workflow-in-action">The Workflow in Action</h2>
<h3 id="1-ai-powered-content-creation">1. AI-Powered Content Creation</h3>
<p>When you&rsquo;re discussing a technical topic with an AI agent, you can simply ask:</p>
<blockquote>
<p>&ldquo;Turn this discussion into a blog post and publish it.&rdquo;</p>
</blockquote>
<p>The AI agent, with access to GitHub via MCP, can:</p>
<ul>
<li>Extract key insights from your conversation</li>
<li>Format content according to Hugo front matter requirements</li>
<li>Create properly structured markdown files</li>
<li>Handle images and assets</li>
<li>Commit and push to the repository</li>
</ul>
<h3 id="2-automated-build--deploy">2. Automated Build &amp; Deploy</h3>
<p>The moment a post is pushed to the <code>master</code> branch, the same GitHub Actions workflow from the previous post kicks in:</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-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">on</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">push</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">branches</span>: [ <span style="color:#ae81ff">master ]</span>
</span></span></code></pre></div><p>The workflow (as detailed in <a href="/posts/how-to-automatically-publish-a-blog-using-github-actions/">the previous post</a>):</p>
<ol>
<li>Checks out the blog repository with submodules</li>
<li>Builds the Hugo site using <code>make</code></li>
<li>Deploys the built artifacts to <code>jackysp.github.io</code></li>
</ol>
<p>All without manual intervention.</p>
<h3 id="3-governance-through-contracts">3. Governance Through Contracts</h3>
<p>To ensure quality and prevent accidents, I&rsquo;ve implemented an <strong>AI Publishing Contract</strong> (<code>PUBLISHING.md</code>) that defines:</p>
<ul>
<li><strong>Allowed paths</strong>: Only <code>content/**</code> and <code>static/**</code> can be modified</li>
<li><strong>Post format</strong>: Required front matter fields (title, date, tags, slug, summary)</li>
<li><strong>Image handling</strong>: Standardized location and reference format</li>
<li><strong>Commit conventions</strong>: Single commit per post with descriptive messages</li>
</ul>
<p>This contract ensures that AI agents can publish content while respecting the repository structure and quality standards.</p>
<h2 id="why-this-matters-the-developer-experience-revolution">Why This Matters: The Developer Experience Revolution</h2>
<h3 id="zero-context-switching">Zero Context Switching</h3>
<p>Traditional workflow:</p>
<ol>
<li>Copy conversation → Switch to blog repo → Format → Commit → Push → Wait</li>
<li><strong>Context lost</strong>, momentum broken</li>
</ol>
<p>New workflow:</p>
<ol>
<li>Ask AI to publish → Done</li>
<li><strong>Context preserved</strong>, workflow continuous</li>
</ol>
<h3 id="capturing-technical-insights">Capturing Technical Insights</h3>
<p>The best technical insights often emerge during active problem-solving. With this workflow, you can:</p>
<ul>
<li>Document discoveries in real-time</li>
<li>Turn debugging sessions into tutorials</li>
<li>Transform architecture discussions into deep-dives</li>
<li>Share codebase explorations as learning resources</li>
</ul>
<h3 id="scaling-knowledge-sharing">Scaling Knowledge Sharing</h3>
<p>Previously, the friction of publishing meant many valuable insights were never written down. Now, the barrier to publishing is minimal, making it easier to:</p>
<ul>
<li>Share learnings with your team</li>
<li>Build a personal knowledge base</li>
<li>Contribute to the developer community</li>
<li>Document your problem-solving journey</li>
</ul>
<h2 id="technical-implementation-details">Technical Implementation Details</h2>
<h3 id="mcp-server-configuration">MCP Server Configuration</h3>
<p>The GitHub MCP server provides the AI agent with:</p>
<ul>
<li>Repository read/write access</li>
<li>File creation and modification</li>
<li>Commit and push capabilities</li>
<li>Branch management</li>
</ul>
<h3 id="github-actions-workflow">GitHub Actions Workflow</h3>
<p>The CI/CD pipeline (as described in <a href="/posts/how-to-automatically-publish-a-blog-using-github-actions/">the previous post</a>) handles:</p>
<ul>
<li>Go environment setup (for Hugo builds)</li>
<li>Repository checkout with submodules</li>
<li>Site generation via <code>make</code></li>
<li>Deployment to GitHub Pages repository</li>
</ul>
<p>No changes needed to the existing workflow—it just gets triggered from a new entry point.</p>
<h3 id="hugo-site-configuration">Hugo Site Configuration</h3>
<p>Posts follow Hugo&rsquo;s standard structure:</p>
<ul>
<li><strong>Location</strong>: <code>content/posts/&lt;slug&gt;/index.md</code></li>
<li><strong>Format</strong>: YAML front matter + Markdown content</li>
<li><strong>Images</strong>: Stored in each post bundle and referenced with relative filenames</li>
<li><strong>Draft control</strong>: <code>draft: true/false</code> for preview/publish</li>
</ul>
<h2 id="the-future-ai-augmented-documentation">The Future: AI-Augmented Documentation</h2>
<p>This workflow represents a shift toward <strong>AI-augmented documentation</strong>. Instead of treating AI as a writing assistant, we&rsquo;re treating it as a publishing agent that can:</p>
<ul>
<li>Understand context from code discussions</li>
<li>Extract technical insights automatically</li>
<li>Format and structure content appropriately</li>
<li>Publish without breaking workflow</li>
</ul>
<p>As MCP and similar protocols mature, we&rsquo;ll see more sophisticated capabilities:</p>
<ul>
<li>Automatic code analysis and explanation</li>
<li>Multi-post series generation from extended discussions</li>
<li>Cross-referencing with existing content</li>
<li>SEO and metadata optimization</li>
</ul>
<h2 id="getting-started">Getting Started</h2>
<p>If you want to set up a similar workflow:</p>
<ol>
<li><strong>Set up automated publishing</strong> (see <a href="/posts/how-to-automatically-publish-a-blog-using-github-actions/">my previous post</a>)</li>
<li><strong>Enable MCP in your AI agent</strong> (Cursor, Claude Desktop, etc.)</li>
<li><strong>Configure GitHub MCP server</strong> with repository access</li>
<li><strong>Define publishing contracts</strong> for governance</li>
<li><strong>Start publishing</strong> from your conversations</li>
</ol>
<p>The technical details are straightforward, but the impact on productivity and knowledge capture is profound.</p>
<h2 id="conclusion">Conclusion</h2>
<p>The intersection of AI agents, MCP protocols, and automated CI/CD creates a new paradigm for technical publishing. By building on the existing GitHub Actions automation and adding MCP as the entry point, we eliminate context switching and reduce friction.</p>
<p>This isn&rsquo;t just about automating blog posts—it&rsquo;s about <strong>preserving the flow state of technical discovery</strong> and making knowledge sharing as natural as having a conversation.</p>
<p>The future of technical documentation is here, and it&rsquo;s conversational.</p>
<hr>
<p><em>This post was created and published using the exact workflow described above—from a discussion about workflow automation to a live blog post, all without leaving the conversation context.</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>Brikka on Induction: An Engineering View of a Triggered Extraction System</title>
      <link>https://blog.minifish.org/posts/brikka-induction-triggered-extraction/</link>
      <pubDate>Sun, 18 Jan 2026 10:00:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/brikka-induction-triggered-extraction/</guid>
      <description>A systems view of brewing with a Brikka on induction, treating extraction as a triggered thermal and pressure process.</description>
      <content:encoded><![CDATA[<h2 id="motivation-why-this-is-an-engineering-problem">Motivation: Why This Is an Engineering Problem</h2>
<p>Most Moka pot guides treat brewing as a <em>recipe problem</em>:
grind size, water temperature, and heat level.</p>
<p>That framing breaks down completely when you introduce:</p>
<ul>
<li>A <strong>Brikka</strong> (pressure valve + burst extraction)</li>
<li>An <strong>induction hob</strong></li>
<li>A <strong>steel induction adapter plate</strong></li>
</ul>
<p>At this point, you are no longer “brewing coffee”.
You are operating a <strong>multi-stage thermal + pressure system with delayed feedback</strong>.</p>
<p>This article reframes Brikka-on-induction as a <strong>control problem</strong>, not a recipe.</p>
<h2 id="brikka-vs-classic-moka-a-structural-difference">Brikka vs Classic Moka: A Structural Difference</h2>
<p>Classic Moka pots operate as <strong>continuous-flow systems</strong>.</p>
<p>Brikka is fundamentally different.</p>
<h3 id="brikka-is-a-triggered-system">Brikka is a Triggered System</h3>
<ul>
<li>No flow occurs until a <strong>pressure threshold</strong> is reached</li>
<li>Once triggered, the valve opens abruptly</li>
<li>Extraction happens in a <strong>very short, high-energy window</strong></li>
</ul>
<p>From a systems perspective:</p>
<blockquote>
<p><strong>Classic Moka = streaming pipeline</strong><br>
<strong>Brikka = edge-triggered event</strong></p>
</blockquote>
<h2 id="induction--adapter-plate-where-the-model-changes">Induction + Adapter Plate: Where the Model Changes</h2>
<p>With an induction hob and the official Bialetti adapter plate, the heat path becomes:</p>
<p>Induction coil<br>
→ Steel adapter plate<br>
→ Aluminum boiler<br>
→ Water</p>
<p>The adapter plate introduces <strong>significant thermal inertia</strong>.</p>
<p>Low or medium power levels often never leave the heat accumulation phase.</p>
<h2 id="engineering-goal-reach-the-trigger-then-stop">Engineering Goal: Reach the Trigger, Then Stop</h2>
<p>Because Brikka extracts only after the valve opens, the primary goal is:</p>
<blockquote>
<p>Drive the system to the trigger point as efficiently as possible — then stop.</p>
</blockquote>
<p>Any energy added after triggering only increases bitterness.</p>
<h2 id="parameter-design">Parameter Design</h2>
<h3 id="water">Water</h3>
<ul>
<li>Cold water to the fill line</li>
</ul>
<p>Cold water ensures a linear pressure ramp.</p>
<h3 id="beans">Beans</h3>
<ul>
<li>Medium to medium-light roast</li>
</ul>
<p>Brikka amplifies front-loaded flavors.</p>
<h3 id="grind">Grind</h3>
<ul>
<li>Timemore C5 ESP: baseline <strong>1.2.0</strong></li>
<li>Adjust only ±0.0.5</li>
</ul>
<p>Brikka has a narrow operating window.</p>
<h3 id="heat-strategy">Heat Strategy</h3>
<p><strong>Preheating Phase (Before Grinding):</strong></p>
<ul>
<li>Start heating the adapter plate at <strong>Level 9</strong> while measuring beans and grinding</li>
<li>The official stainless steel adapter plate is thick and conducts heat slowly</li>
<li>Preheating ensures the plate reaches operating temperature before brewing begins</li>
</ul>
<p><strong>Brewing Phase:</strong></p>
<ul>
<li>Phase 1: <strong>Level 9</strong> until trigger</li>
<li>Phase 2: <strong>Cut power immediately at first continuous output</strong></li>
<li>Rinse boiler bottom with cold water</li>
</ul>
<p>Residual heat is sufficient.</p>
<h2 id="timing-as-validation">Timing as Validation</h2>
<ul>
<li>Trigger time: <strong>3–5 minutes</strong></li>
<li>Much longer indicates system inefficiency</li>
</ul>
<p>Time validates the system, not flavor.</p>
<h2 id="design-trade-offs">Design Trade-offs</h2>
<p>High initial power stresses equipment but exits the thermal dead zone.</p>
<p>Immediate cutoff sacrifices volume but preserves flavor boundaries.</p>
<h2 id="final-mental-model">Final Mental Model</h2>
<blockquote>
<p><strong>Brikka is not a brewer.<br>
It is a pressure-triggered extraction event.</strong></p>
</blockquote>
<p>Once the event fires, the system should coast.</p>
<p>Respecting that boundary makes the system predictable and repeatable.</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>Introduction to WebVM</title>
      <link>https://blog.minifish.org/posts/introduction-to-webvm/</link>
      <pubDate>Mon, 13 Jan 2025 19:54:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/introduction-to-webvm/</guid>
      <description>WebVM is a virtual machine (VM) that executes entirely within a web browser. It&amp;#39;s an innovative project that brings the power of a Linux environment straight to your browser,...</description>
      <content:encoded><![CDATA[<h2 id="what-is-webvm">What is WebVM?</h2>
<p><a href="https://github.com/leaningtech/webvm">WebVM</a> is a virtual machine (VM) that executes entirely within a web browser. It&rsquo;s an innovative project that brings the power of a Linux environment straight to your browser, eliminating the need for traditional virtual machine setups. WebVM operates within a sandboxed environment, ensuring secure execution of applications without affecting the host system.</p>
<h2 id="understanding-webvm">Understanding WebVM</h2>
<p>The source repository provides a frontend for the WebVM demo. By forking the repository and following the instructions outlined in the GitHub Actions, you can build an image using the Dockerfile located at:</p>
<ul>
<li><a href="https://github.com/leaningtech/webvm/blob/main/dockerfiles/debian_large">Debian Large Dockerfile</a></li>
</ul>
<p>This image can then be hosted on GitHub Pages, as demonstrated in my <a href="https://blog.minifish.org/webvm/">demo</a>.</p>
<p><img alt="demo" loading="lazy" src="/posts/introduction-to-webvm/20250113_195726_image.webp"></p>
<p>WebVM&rsquo;s primary functionality is to stream resources to the browser, minimizing client-side resource consumption. It enables the execution of various applications that are typically restricted to virtual machines, all within a browser. Additionally, WebVM allows for the embedding of these applications through custom front-ends.</p>
<h2 id="default-image-and-capabilities">Default Image and Capabilities</h2>
<p>The default WebVM image is <strong>Debian-mini</strong>, which may have limited capabilities. To enhance its functionality, I have opted for the <strong>Debian-large</strong> image, which has been extended to a 2GB disk capacity. This provides a more robust environment with additional tools and packages.</p>
<h2 id="usage-and-benefits">Usage and Benefits</h2>
<p>WebVM offers a range of applications and capabilities:</p>
<ol>
<li>
<p><strong>Custom Image Creation:</strong> Create custom images tailored to your specific requirements, allowing for a personalized virtual environment.</p>
</li>
<li>
<p><strong>Web-Based Linux Terminal:</strong> Access a web-based Linux terminal to execute Linux commands directly within the browser. This includes:</p>
<ul>
<li><strong>SSH/SCP File Transfers:</strong> Securely transfer files using SSH and SCP protocols.</li>
<li><strong>HTTP Server Initiation:</strong> Start an HTTP server using <code>python3 -m http.server</code>.</li>
</ul>
</li>
<li>
<p><strong>Sandboxed Security:</strong> Operates within a sandbox environment, ensuring secure execution of applications without affecting the host system.</p>
</li>
<li>
<p><strong>Serverless Architecture:</strong> Embraces a serverless architecture by executing entirely on the client side. Running a Linux server within a browser presents a unique and innovative approach to virtualization.</p>
</li>
</ol>
<h2 id="alternative-options">Alternative Options</h2>
<p>Yes, there are alternative options. <a href="https://bellard.org/jslinux/">JSLinux</a> is a preferred and faster option. However, it does not allow modifications to the image, which can be a limitation if you require a customized environment.</p>
<h2 id="additional-tips">Additional Tips</h2>
<ul>
<li>
<p><strong>Internet Connectivity via Tailscale:</strong></p>
<p>WebVM can connect to the internet via <a href="https://tailscale.com/">Tailscale</a>. It utilizes the first available node as an exit node. If you execute:</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-bash" data-lang="bash"><span style="display:flex;"><span>curl https://ifconfig.me
</span></span></code></pre></div><p>You will obtain your current node&rsquo;s IP address.</p>
</li>
<li>
<p><strong>DNS Functionality:</strong></p>
<p>The DNS functionality of Tailscale is currently experiencing issues. It&rsquo;s recommended to use IP addresses to connect to other nodes within your Tailscale network instead of domain names.</p>
</li>
<li>
<p><strong>Default Credentials:</strong></p>
<p>You can obtain the default <code>user:password</code> and <code>root:password</code> credentials by checking the Dockerfile:</p>
<ul>
<li><a href="https://github.com/leaningtech/webvm/blob/main/dockerfiles/debian_large#L15-L18">Default Credentials in Dockerfile</a></li>
</ul>
</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>WebVM is a powerful tool that brings the versatility of a Linux environment to your browser. Whether you&rsquo;re looking to experiment with Linux commands, develop applications, or require a portable and sandboxed environment, WebVM offers a serverless and secure solution. Its ability to create custom images and operate entirely on the client side sets it apart from other web-based virtual machines.</p>
<p>Feel free to explore WebVM and customize it to suit your needs. Happy coding!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Notes on Modifying a Brompton Clone Bike</title>
      <link>https://blog.minifish.org/posts/brompton-clone-bike-modification/</link>
      <pubDate>Fri, 10 Jan 2025 18:07:06 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/brompton-clone-bike-modification/</guid>
      <description>Notes on modifying an Aceoffix Brompton clone bike, including accessories, tradeoffs, cost, and practical buying tips.</description>
      <content:encoded><![CDATA[<p>I&rsquo;m excited to share the final version of my <strong>Aceoffix 01</strong>, a Brompton clone bike I&rsquo;ve been customizing:</p>
<p><img alt="20250110_181217_IMG_6530" loading="lazy" src="/posts/brompton-clone-bike-modification/20250110_181217_IMG_6530.webp"></p>
<h2 id="what-modifications-did-i-make">What Modifications Did I Make?</h2>
<ol>
<li>
<p><strong>Handlebar Grips</strong></p>
<p>The original grips were glued on and became loose over time, so I replaced them with new, more secure ones.</p>
</li>
<li>
<p><strong>Bell</strong></p>
<p>Safety first! I added a bell to alert pedestrians and other cyclists.</p>
</li>
<li>
<p><strong>CNC Mount Base for Action Cameras</strong></p>
<p>Installed at the center of the handlebar, it&rsquo;s perfect for mounting my DJI OSMO Pocket 1 or a phone holder.</p>
</li>
<li>
<p><strong>Front Bottle Bag from Decathlon Riverside</strong></p>
<p>A convenient spot for a water bottle and small items.</p>
</li>
<li>
<p><strong>Front Light</strong></p>
<p>Mounted just above the front wheel to illuminate the path without blinding anyone.</p>
</li>
<li>
<p><strong>Soft Saddle from Decathlon</strong></p>
<p>For a more comfortable ride.</p>
</li>
<li>
<p><strong>Four Universal Easy Wheels</strong></p>
<p>These make the bike easier to push when folded. The set of four cost me 100 SGD.</p>
</li>
<li>
<p><strong>Taillight</strong></p>
<p>For visibility and safety during night rides.</p>
</li>
<li>
<p><strong>Front Bag</strong></p>
<p>Provides additional storage space for essentials.</p>
</li>
<li>
<p><strong>Anti-Loose Accessory</strong></p>
<p>Helps keep components tight and secure.</p>
<p><img alt="IMG_6529" loading="lazy" src="/posts/brompton-clone-bike-modification/IMG_6529.webp"></p>
</li>
<li>
<p><strong>Tool Set</strong></p>
<p>Essential for on-the-go repairs and adjustments.</p>
<p><img alt="IMG_6531" loading="lazy" src="/posts/brompton-clone-bike-modification/IMG_6531.webp"></p>
</li>
</ol>
<h2 id="why-did-i-choose-these-modifications">Why Did I Choose These Modifications?</h2>
<p>I experimented with mounting accessories on the handlebar, like the bottle holder and front light, but it made the handlebar too crowded and unstable. For instance, the bottle holder wasn&rsquo;t secure when attached to the handlebar. I decided to utilize existing mounts, such as the taillight bracket, to streamline the setup.</p>
<p>The additional accessories are intended to make the bike even easier to push when folded. It&rsquo;s already convenient, but I thought, <em>why not enhance it further?</em></p>
<h2 id="areas-for-improvement">Areas for Improvement</h2>
<ul>
<li>
<p><strong>Anti-Loose Accessory</strong>: It&rsquo;s not working as well as I&rsquo;d hoped and might need to be replaced.</p>
</li>
<li>
<p><strong>Front Bag Straps</strong>: They&rsquo;re a bit long, and I&rsquo;m concerned they could get caught in the front wheel. I might need to shorten them.</p>
</li>
</ul>
<h2 id="cost-breakdown">Cost Breakdown</h2>
<p>Most accessories cost between <strong>5–50 SGD</strong>, except for the universal wheels, which were <strong>100 SGD</strong> for all four. Initially, I wasn&rsquo;t sure if I needed all four wheels—maybe two would have sufficed—but I ultimately decided to get all four for better stability.</p>
<h2 id="anything-else">Anything Else?</h2>
<p>I can&rsquo;t think of anything else at the moment. If you have any suggestions or feedback, I&rsquo;d love to hear them!</p>
<h2 id="tips-for-buying-accessories-in-singapore">Tips for Buying Accessories in Singapore</h2>
<ul>
<li>
<p><strong>Decathlon</strong>: A great place to find affordable accessories, and they have a 365-day return policy!</p>
</li>
<li>
<p><strong>Shopee</strong>: Good for finding unique or hard-to-find items.</p>
</li>
<li>
<p><strong>Taobao</strong>: Offers a wide range of accessories, but shipping can be expensive. You can ask a friend in China to help you buy and ship the items to Singapore.</p>
</li>
</ul>
<p>I don&rsquo;t recommend buying from bike shops in Singapore, as they tend to be more expensive—about twice the price.</p>
<hr>
<p>Let me know if there&rsquo;s anything else you&rsquo;d like to know or discuss!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Harnessing AI to Create High-Quality Podcasts Quickly and for Free</title>
      <link>https://blog.minifish.org/posts/harnessing-ai-to-create-high-quality-podcasts-quickly-and-for-free/</link>
      <pubDate>Wed, 11 Dec 2024 17:11:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/harnessing-ai-to-create-high-quality-podcasts-quickly-and-for-free/</guid>
      <description>As a long-time podcaster, I&amp;#39;ve always enjoyed sharing my thoughts and ideas through audio. While the world of video content—and the role of a YouTuber—has its allure, the...</description>
      <content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>As a long-time podcaster, I&rsquo;ve always enjoyed sharing my thoughts and ideas through audio. While the world of video content—and the role of a YouTuber—has its allure, the complexities of video editing have kept me anchored in the realm of podcasting. My journey has involved leveraging platforms like <a href="https://creators.spotify.com/"><strong>Spotify Creator</strong></a> (formerly <strong>Anchor</strong>) for hosting and distributing my recordings. This platform offers a wide array of features for free, including audio recording, editing capabilities, and automatic promotion to Spotify.</p>
<p>However, I sought a more comprehensive solution, one that would allow me to listen to my own podcast while driving, using the Podcast app on my CarPlay device. To achieve this, I ventured into publishing on <strong>Apple Podcasts</strong> (<a href="https://podcastsconnect.apple.com/">podcastsconnect.apple.com</a>), which also offers free hosting. With a self-designed cover and episodes uploaded, I was set—or so I thought.</p>
<h2 id="the-challenges-of-traditional-podcasting">The Challenges of Traditional Podcasting</h2>
<p>Despite having the technical setup, I faced significant challenges:</p>
<ul>
<li><strong>Consistency</strong>: Maintaining a regular publishing schedule proved difficult.</li>
<li><strong>Voice Quality</strong>: My voice quality was inconsistent, affecting listener engagement.</li>
<li><strong>Content Preparation</strong>: Crafting well-structured episodes without improvisation was challenging.</li>
<li><strong>Enhancements</strong>: Incorporating background music and other audio elements to enrich the listening experience required additional effort.</li>
</ul>
<p>These hurdles led to my podcast being suspended for approximately two years. I found myself in need of a solution that could simplify the process and revitalize my passion for podcasting.</p>
<h2 id="discovering-notebooklm-an-ai-powered-podcasting-tool">Discovering NotebookLM: An AI-Powered Podcasting Tool</h2>
<p>Recently, I stumbled upon <strong>NotebookLM</strong> (<a href="https://notebooklm.google.com/">notebooklm.google.com</a>), an innovative application developed by Google. NotebookLM harnesses the power of artificial intelligence to generate podcast content. Users can provide a topic and related documents, and the AI takes over, creating engaging podcast episodes.</p>
<h3 id="my-experience-with-notebooklm">My Experience with NotebookLM</h3>
<p>Intrigued, I decided to give NotebookLM a try. The results were nothing short of astounding:</p>
<ul>
<li><strong>Effortless Production</strong>: The AI effortlessly generated a half-hour episode featuring two speakers discussing the topics in English.</li>
<li><strong>Enhanced Content</strong>: It went beyond the provided information, utilizing search engines to gather additional relevant data from the internet.</li>
<li><strong>Quality Output</strong>: The quality of the generated content was exceptionally high, surpassing what I could produce on my own.</li>
<li><strong>Incorporated Music</strong>: Appropriate background music was added, enhancing the overall listening experience.</li>
<li><strong>Cost-Free</strong>: All these features were available entirely for free.</li>
</ul>
<h2 id="a-case-study-deep-dive-into-tidb">A Case Study: Deep Dive into TiDB</h2>
<p>To put NotebookLM to the test, I created an episode about <strong>TiDB</strong>, a product developed by my current employer. The process was seamless, and the final product was impressive. You can listen to the episode here: <a href="https://podcasts.apple.com/us/podcast/deep-dive-into-tidb/id1609444337?i=1000679181770">Deep Dive into TiDB</a>.</p>
<h2 id="conclusion">Conclusion</h2>
<p>The integration of AI into podcast creation through tools like NotebookLM has the potential to revolutionize the way we produce content. It removes many of the barriers that podcasters face, such as time constraints, technical challenges, and the need for consistent quality.</p>
<p>For anyone looking to start or rejuvenate their podcast without the traditional hassles, I highly recommend giving NotebookLM a try. It&rsquo;s remarkable to see how AI can not only match but enhance human capabilities in creative endeavors.</p>
<hr>
<p>I hope this helps! Let me know if there&rsquo;s anything you&rsquo;d like to add or modify in your blog post.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Brompton-like Bikes: A Guide to Trifoldable Bicycles</title>
      <link>https://blog.minifish.org/posts/brompton-like-bikes-a-guide-to-trifoldable-bicycles/</link>
      <pubDate>Mon, 09 Dec 2024 19:27:06 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/brompton-like-bikes-a-guide-to-trifoldable-bicycles/</guid>
      <description>A practical guide to Brompton-like trifold bikes, clone brands, accessories, and tradeoffs for urban use.</description>
      <content:encoded><![CDATA[<p>Brompton-like bikes are renowned for their compactness and portability. Unlike the typical two-fold bikes, these are trifold bikes, making them exceptionally convenient for urban commuting and travel.</p>
<h2 id="key-features">Key Features</h2>
<ul>
<li><strong>Compact Size</strong>: The trifold design results in a very small folded size.</li>
<li><strong>Lightweight</strong>: Weighing between <strong>10 to 11 kg</strong>, making them easy to carry.</li>
<li><strong>Easy Transportation</strong>: Suitable for air travel, and can be taken on public transport like MRT and buses.</li>
<li><strong>Thin Tires</strong>: Facilitates easy movement and efficient riding.</li>
<li><strong>Front Bracket</strong>: Allows for quick changes of front bags.</li>
<li><strong>Popularity</strong>: Highly popular in Singapore.</li>
<li><strong>Non-standard Accessories</strong>: May require specific accessories due to unique design.</li>
</ul>
<p>My bike is a Brompton clone, and I&rsquo;ll share some insights on the various brands and models available.</p>
<p><img alt="bike" loading="lazy" src="/posts/brompton-like-bikes-a-guide-to-trifoldable-bicycles/20241210_093935_BAD44C59-7EF9-428A-8AA3-C81C46B01A9A.webp"></p>
<h2 id="brompton-bikes">Brompton Bikes</h2>
<p>Brompton is the original brand offering high-quality trifold bikes.</p>
<ul>
<li><strong>Price Range</strong>: Approximately <strong>10,000 RMB to 40,000 RMB</strong>, with common models around <strong>15,000 RMB</strong>.</li>
<li><strong>Models</strong>:
<ul>
<li><strong>C Line</strong>: Features <strong>3-speed internal hub gears</strong>.</li>
<li><strong>P Line</strong>: Equipped with <strong>4-speed external shift gears</strong>.</li>
</ul>
</li>
</ul>
<h2 id="brompton-clone-brands">Brompton Clone Brands</h2>
<p>For those seeking more affordable options, several brands offer Brompton-like bikes ranging from <strong>3,000 to 7,000 RMB</strong>.</p>
<h3 id="aceoffix">Aceoffix</h3>
<ul>
<li><strong>Gears</strong>: Available in <strong>3 or 5-speed external shift gears</strong>.</li>
<li><strong>Frame Material</strong>: Options of steel or aluminum frames.</li>
<li><strong>Compatibility</strong>: Standard size compatible with Brompton accessories.</li>
</ul>
<h3 id="cran">Cran</h3>
<ul>
<li><strong>Gears</strong>: Offers <strong>7 or 9-speed external shift gears</strong>.</li>
<li><strong>Sizes</strong>:
<ul>
<li><strong>7-Speed</strong>: Standard size.</li>
<li><strong>9-Speed</strong>: Non-standard size.</li>
</ul>
</li>
<li><strong>Frame Material</strong>: Primarily steel; aluminum frames may have stability issues.</li>
</ul>
<h3 id="3sixty">3Sixty</h3>
<ul>
<li>One of the earliest Brompton clones.</li>
<li>Features <strong>internal shift gears</strong>.</li>
</ul>
<h3 id="yubu-鱼布">Yubu (鱼布)</h3>
<ul>
<li>Custom assembled by a Brompton enthusiast in Hangzhou.</li>
<li><strong>Gears</strong>: <strong>3-speed external shift gears</strong>.</li>
<li><strong>Reputation</strong>: Known for high quality and reliability.</li>
</ul>
<h3 id="dagger">Dagger</h3>
<ul>
<li>A new entrant in the market.</li>
<li><strong>Price</strong>: Offers bikes at <strong>3,700 RMB</strong> with features comparable to higher-priced models (&gt;5,000 RMB).</li>
<li><strong>Value</strong>: High cost-performance ratio.</li>
</ul>
<h2 id="choosing-the-right-bike">Choosing the Right Bike</h2>
<ul>
<li><strong>Avoid Internal Shift Gears</strong>: While reliable, they add weight to the bike.</li>
<li><strong>Optimal Gears</strong>: <strong>3 or 5-speed external gears</strong> are stable and sufficient for most needs.</li>
<li><strong>Portability Concerns</strong>: <strong>9-speed models</strong> might be less convenient for transportation due to size.</li>
<li><strong>Standard Size Preference</strong>: Ensures compatibility with standard transfer cases and accessories.</li>
<li><strong>Pedals</strong>: Consider non-foldable pedals with quick-release options for convenience.</li>
</ul>
<h2 id="essential-accessories">Essential Accessories</h2>
<ul>
<li><strong>Lighting</strong>: Front and rear lights are mandatory by law in Singapore.</li>
<li><strong>Additional Gear</strong>:
<ul>
<li><strong>Bottle Bracket</strong></li>
<li><strong>Bell</strong></li>
<li><strong>Phone Bracket</strong></li>
<li><strong>Helmet</strong></li>
<li><strong>Front and Rear Lights</strong></li>
</ul>
</li>
<li><strong>Bag</strong>: A Brompton two-strap bag for carrying essentials.</li>
</ul>
<h2 id="riding-tips">Riding Tips</h2>
<ul>
<li><strong>UV Protection</strong>: Use sun cream or wear sun-protective clothing.</li>
<li><strong>Saddle Upgrade</strong>:
<ul>
<li>
<p><strong>Wide Saddle</strong>: For rides longer than 1 hour. 90 degrees is ideal for the saddle angle.</p>
<p><img alt="wide" loading="lazy" src="/posts/brompton-like-bikes-a-guide-to-trifoldable-bicycles/20241209_213208_IMG_6425.webp"></p>
</li>
<li>
<p><strong>Thin Saddle</strong>: Suitable for shorter rides. 60 degrees is ideal for the saddle angle.</p>
<p><img alt="thin" loading="lazy" src="/posts/brompton-like-bikes-a-guide-to-trifoldable-bicycles/20241209_213223_IMG_6424.webp"></p>
</li>
</ul>
</li>
<li><strong>Accessory Shopping</strong>: Most accessories can be found at stores like Decathlon, though some items may require sourcing elsewhere due to sizing.</li>
</ul>
<hr>
<p><em>Note: While many accessories are readily available at retailers like Decathlon, certain items like helmets may have limited sizing options.</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>My Recent Purchases: A Review of Three Sanrenmu Folding Knives</title>
      <link>https://blog.minifish.org/posts/my-recent-purchases-a-review-of-three-sanrenmu-folding-knives/</link>
      <pubDate>Thu, 28 Nov 2024 16:10:43 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/my-recent-purchases-a-review-of-three-sanrenmu-folding-knives/</guid>
      <description>A hands-on review of three Sanrenmu folding knives, with notes on everyday use, build quality, and practical differences.</description>
      <content:encoded><![CDATA[<p>Recently, I decided to expand my collection of folding knives and purchased three models from <strong><a href="#">Sanrenmu</a></strong>. For anyone considering a new knife or looking for suggestions, I wanted to share my experiences with these three models: the 9201, 9008, and 820. Each has its own strengths and weaknesses, and hopefully, my insights can help you make an informed decision.</p>
<hr>
<h2 id="sanrenmu-9201">Sanrenmu 9201</h2>
<ul>
<li><strong>Price:</strong> 198 Yuan</li>
<li><strong>Lock Type:</strong> Axis Lock</li>
<li><strong>Handle Material:</strong> Plastic</li>
<li><strong>Blade Material:</strong> D2 Steel</li>
</ul>
<h3 id="first-impressions">First Impressions</h3>
<p>The 9201 features an axis lock mechanism, which I found to be <strong>very smooth and easy to open and close</strong>. The D2 steel blade offers good edge retention, and the plastic handle keeps the knife lightweight.</p>
<h3 id="pros">Pros</h3>
<ul>
<li><strong>Ease of Use:</strong> The axis lock allows for ambidextrous operation, making it convenient for both left and right-handed users.</li>
<li><strong>Lightweight:</strong> The plastic handle reduces overall weight, making it comfortable to carry.</li>
</ul>
<h3 id="cons">Cons</h3>
<ul>
<li><strong>Poor Detailing:</strong> Some edges and surfaces were rough, causing minor cuts to my hand during use.</li>
<li><strong>Finish Quality:</strong> The attention to detail was lacking, which detracted from the overall experience.</li>
</ul>
<h3 id="personal-modifications">Personal Modifications</h3>
<p>I used sandpaper and a rotary tool to smooth out the rough edges. While this improved the handling, I accidentally scratched the blade slightly during the process.</p>
<hr>
<h2 id="sanrenmu-9008">Sanrenmu 9008</h2>
<ul>
<li><strong>Price:</strong> 168 Yuan</li>
<li><strong>Lock Type:</strong> Frame Lock with Safety Lock</li>
<li><strong>Handle Material:</strong> Steel</li>
<li><strong>Blade Material:</strong> Unknown</li>
<li><strong>Features:</strong> Quick-opening flipper</li>
</ul>
<h3 id="first-impressions-1">First Impressions</h3>
<p>Out of the box, the 9008 showcased <strong>excellent workmanship</strong>. The quick-opening flipper worked flawlessly, and the safety lock adds an extra layer of security when the knife is not in use.</p>
<h3 id="pros-1">Pros</h3>
<ul>
<li><strong>Build Quality:</strong> The knife feels solid and well-constructed.</li>
<li><strong>Quick-opening Flipper:</strong> Provides rapid deployment of the blade.</li>
<li><strong>Safety Lock:</strong> Prevents accidental opening.</li>
</ul>
<h3 id="cons-1">Cons</h3>
<ul>
<li><strong>Weight:</strong> The steel handle makes it quite heavy, which isn&rsquo;t ideal for everyday carry.</li>
<li><strong>Button Quality:</strong> The button mechanism isn&rsquo;t as smooth as I would like.</li>
<li><strong>Blade Material:</strong> Unspecified steel that doesn&rsquo;t hold an edge as well as higher-end materials.</li>
</ul>
<h3 id="usage">Usage</h3>
<p>Due to its weight, I decided to <strong>keep the 9008 in my car</strong>. It features a window-breaking hammer, making it a practical tool for emergencies.</p>
<hr>
<h2 id="sanrenmu-820">Sanrenmu 820</h2>
<ul>
<li><strong>Price:</strong> 298 Yuan</li>
<li><strong>Lock Type:</strong> Frame Lock</li>
<li><strong>Handle Material:</strong> Titanium</li>
<li><strong>Blade Material:</strong> VG10 Steel</li>
<li><strong>Features:</strong> Ball bearings (no safety lock or flipper)</li>
</ul>
<h3 id="first-impressions-2">First Impressions</h3>
<p>The 820 stands out with its <strong>titanium handle and VG10 blade</strong>, offering a premium feel. It operates on ball bearings, ensuring a smooth action when opening and closing.</p>
<h3 id="pros-2">Pros</h3>
<ul>
<li><strong>Material Quality:</strong> The VG10 blade and titanium handle are high-quality materials that enhance durability and aesthetics.</li>
<li><strong>Workmanship:</strong> Excellent fit and finish.</li>
</ul>
<h3 id="cons-2">Cons</h3>
<ol>
<li>
<p><strong>Opening Mechanism Issues:</strong></p>
<ul>
<li>The stop pin is too small.</li>
<li>The lock interface is large and deep, making one-handed opening difficult.</li>
</ul>
</li>
<li>
<p><strong>Pocket Clip:</strong></p>
<ul>
<li>The clip is excessively tight, making it hard to attach to pockets easily.</li>
</ul>
</li>
</ol>
<h3 id="personal-modifications-1">Personal Modifications</h3>
<p>I disassembled the 820 and <strong>polished the lock interface</strong>, significantly improving the ease of one-handed opening. Post-modification, the knife opens smoothly, though there&rsquo;s a slight looseness when closed.</p>
<hr>
<h2 id="reflections-and-insights">Reflections and Insights</h2>
<ul>
<li>
<p><strong>Axis Lock vs. Frame Lock:</strong></p>
<ul>
<li><em>Axis Lock (9201):</em> Offers ambidextrous use and works well with a plastic handle but has a complex structure that&rsquo;s challenging to disassemble.</li>
<li><em>Frame Lock (820 &amp; 9008):</em> Simpler design and easier to disassemble (especially the 820 after some practice). However, it&rsquo;s more suited to one-handed use and relies heavily on precise craftsmanship for smooth operation.</li>
</ul>
</li>
<li>
<p><strong>Handle Materials:</strong></p>
<ul>
<li><em>Titanium Handle (820):</em> Initially underestimated, the titanium handle provides a superior feel compared to plastic or steel. Once accustomed to it, switching back to other materials feels like a downgrade.</li>
<li><em>Steel Handle (9008):</em> Durable but adds unnecessary weight, making it less ideal for carrying around.</li>
<li><em>Plastic Handle (9201):</em> Lightweight but doesn&rsquo;t offer the same premium feel as metal handles.</li>
</ul>
</li>
<li>
<p><strong>Practicality vs. Aesthetics:</strong></p>
<ul>
<li>The 9008, while heavy, serves a practical purpose in the car for emergencies.</li>
<li>The 820, after modifications, has become my preferred <strong>everyday carry</strong> despite the tight pocket clip.</li>
</ul>
</li>
<li>
<p><strong>DIY Modifications:</strong></p>
<ul>
<li>Personalizing and improving the knives was a rewarding experience, even if there were minor mishaps like scratching the blade on the 9201.</li>
<li>Disassembling and reassembling the 820 has turned it into not just a tool but a hobbyist&rsquo;s toy.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="conclusion">Conclusion</h2>
<p>Each knife has its place:</p>
<ul>
<li><strong>Sanrenmu 9008:</strong> Stays in the car for emergency situations, thanks to its sturdy build and window-breaking feature.</li>
<li><strong>Sanrenmu 820:</strong> Becomes my go-to carry knife after modifications. Its high-quality materials and improved functionality make it stand out.</li>
<li><strong>Sanrenmu 9201:</strong> Although I&rsquo;m unsure of its future use, it might serve as an additional car knife or a backup.</li>
</ul>
<p>If you&rsquo;re considering a Sanrenmu folding knife, think about what features matter most to you—be it the locking mechanism, handle material, or ease of carry. Also, don&rsquo;t shy away from making personal adjustments to tailor the knife to your preferences.</p>
<hr>
<p><em>Note: This review is based on personal experiences and modifications. Results may vary depending on individual usage and skills.</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>The Correct Way to Use `go build`</title>
      <link>https://blog.minifish.org/posts/the-correct-way-to-use-go-build/</link>
      <pubDate>Thu, 28 Nov 2024 15:41:19 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/the-correct-way-to-use-go-build/</guid>
      <description>When working with Go, it&amp;#39;s important to know the proper way to compile your programs to avoid common errors. Here are some tips on using the go build command effectively.</description>
      <content:encoded><![CDATA[<p>When working with Go, it&rsquo;s important to know the proper way to compile your programs to avoid common errors. Here are some tips on using the <code>go build</code> command effectively.</p>
<h2 id="recommended-usage">Recommended Usage</h2>
<ul>
<li>
<p><strong>Compile all Go files in the current directory:</strong></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-bash" data-lang="bash"><span style="display:flex;"><span>go build
</span></span></code></pre></div></li>
<li>
<p><strong>Compile all Go files explicitly:</strong></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-bash" data-lang="bash"><span style="display:flex;"><span>go build *.go
</span></span></code></pre></div></li>
</ul>
<h2 id="common-pitfalls">Common Pitfalls</h2>
<h3 id="compiling-a-single-file">Compiling a Single File</h3>
<p>Running:</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-bash" data-lang="bash"><span style="display:flex;"><span>go build main.go
</span></span></code></pre></div><p>will only build <code>main.go</code>. This can lead to errors if <code>main.go</code> depends on other Go files in the same package, as those files won&rsquo;t be included in the build process.</p>
<h3 id="including-non-go-files">Including Non-Go Files</h3>
<p>Using:</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-bash" data-lang="bash"><span style="display:flex;"><span>go build *
</span></span></code></pre></div><p>will cause an error if the <code>*</code> wildcard includes non-Go files. The compiler will output an error message stating that only Go files can be compiled. This serves as a precise reminder to exclude non-Go files from the build command.</p>
<hr>
<p>By using <code>go build</code> correctly, you can ensure that all necessary files in your package are compiled together, avoiding missing dependencies and other common compilation issues.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Exploring Local LLMs with Ollama: My Journey and Practices</title>
      <link>https://blog.minifish.org/posts/exploring-local-llms-with-ollama-my-journey-and-practices/</link>
      <pubDate>Wed, 27 Nov 2024 18:26:14 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/exploring-local-llms-with-ollama-my-journey-and-practices/</guid>
      <description>Local Large Language Models (LLMs) have been gaining traction as developers and enthusiasts seek more control over their AI tools without relying solely on cloud-based solutions....</description>
      <content:encoded><![CDATA[<p>Local Large Language Models (LLMs) have been gaining traction as developers and enthusiasts seek more control over their AI tools without relying solely on cloud-based solutions. In this blog post, I&rsquo;ll share my experiences with <strong>Ollama</strong>, a remarkable tool for running local LLMs, along with other tools like <strong>llamaindex</strong> and <strong>Candle</strong>. I&rsquo;ll also discuss various user interfaces (UI) that enhance the local LLM experience.</p>
<hr>
<h2 id="table-of-contents">Table of Contents</h2>
<ul>
<li><a href="#introduction-to-ollama">Introduction to Ollama</a>
<ul>
<li><a href="#a-popular-choice">A Popular Choice</a></li>
<li><a href="#ease-of-use">Ease of Use</a></li>
<li><a href="#built-with-golang">Built with Golang</a></li>
</ul>
</li>
<li><a href="#my-practices-with-ollama">My Practices with Ollama</a>
<ul>
<li><a href="#preferred-models">Preferred Models</a>
<ul>
<li><a href="#llama-31">Llama 3.1</a></li>
<li><a href="#mistral">Mistral</a></li>
<li><a href="#phi-3">Phi-3</a></li>
<li><a href="#qwen-2">Qwen-2</a></li>
</ul>
</li>
<li><a href="#hardware-constraints">Hardware Constraints</a></li>
</ul>
</li>
<li><a href="#exploring-uis-for-ollama">Exploring UIs for Ollama</a>
<ul>
<li><a href="#openwebui">OpenWebUI</a></li>
<li><a href="#page-assist">Page Assist</a></li>
<li><a href="#enchanted">Enchanted</a></li>
<li><a href="#anythingllm">AnythingLLM</a></li>
<li><a href="#dify">Dify</a></li>
</ul>
</li>
<li><a href="#diving-into-llamaindex">Diving into llamaindex</a></li>
<li><a href="#experimenting-with-candle">Experimenting with Candle</a></li>
<li><a href="#conclusion">Conclusion</a></li>
</ul>
<hr>
<h2 id="introduction-to-ollama">Introduction to Ollama</h2>
<h3 id="a-popular-choice">A Popular Choice</h3>
<p><a href="https://github.com/jmorganca/ollama">Ollama</a> has rapidly become a favorite among developers interested in local LLMs. Within a year, it has garnered significant attention on GitHub, reflecting its growing user base and community support.</p>
<h3 id="ease-of-use">Ease of Use</h3>
<p>One of Ollama&rsquo;s standout features is its simplicity. It&rsquo;s as easy to use as Docker, making it accessible even to those who may not be deeply familiar with machine learning frameworks. The straightforward command-line interface allows users to download and run models with minimal setup.</p>
<h3 id="built-with-golang">Built with Golang</h3>
<p>Ollama is written in <strong>Golang</strong>, ensuring performance and efficiency. Golang&rsquo;s concurrency features contribute to Ollama&rsquo;s ability to handle tasks effectively, which is crucial when working with resource-intensive LLMs.</p>
<h2 id="my-practices-with-ollama">My Practices with Ollama</h2>
<h3 id="preferred-models">Preferred Models</h3>
<h4 id="llama-31">Llama 3.1</h4>
<p>I&rsquo;ve found that <strong>Llama 3.1</strong> works exceptionally well with Ollama. It&rsquo;s my go-to choice due to its performance and compatibility.</p>
<h4 id="mistral">Mistral</h4>
<p>While <strong>Mistral</strong> also performs well, it hasn&rsquo;t gained as much popularity as Llama. Nevertheless, it&rsquo;s a solid option worth exploring.</p>
<h4 id="phi-3">Phi-3</h4>
<p>Developed by Microsoft, <strong>Phi-3</strong> is both fast and efficient. The 2B parameter model strikes a balance between size and performance, making it one of the best small-sized LLMs available.</p>
<h4 id="qwen-2">Qwen-2</h4>
<p>Despite its impressive benchmarks, <strong>Qwen-2</strong> didn&rsquo;t meet my expectations in practice. It might work well in certain contexts, but it didn&rsquo;t suit my specific needs.</p>
<h3 id="hardware-constraints">Hardware Constraints</h3>
<p>Running large models on hardware with limited resources can be challenging. On my 16GB MacBook, models around <strong>7B to 8B parameters</strong> are the upper limit. Attempting to run larger models results in performance issues.</p>
<h2 id="exploring-uis-for-ollama">Exploring UIs for Ollama</h2>
<p>Enhancing the user experience with UIs can make interacting with local LLMs more intuitive. Here&rsquo;s a look at some UIs I&rsquo;ve tried:</p>
<h3 id="openwebui">OpenWebUI</h3>
<p><a href="https://github.com/OpenWebUI/OpenWebUI">OpenWebUI</a> offers a smooth and user-friendly interface similar to Ollama&rsquo;s default UI. It requires Docker to run efficiently, which might be a barrier for some users.</p>
<ul>
<li><strong>Features</strong>:
<ul>
<li>Basic Retrieval-Augmented Generation (RAG) capabilities.</li>
<li>Connection to OpenAI APIs.</li>
</ul>
</li>
</ul>
<h3 id="page-assist">Page Assist</h3>
<p><a href="https://chrome.google.com/webstore/detail/page-assist/">Page Assist</a> is a Chrome extension that I&rsquo;ve chosen for its simplicity and convenience.</p>
<ul>
<li><strong>Advantages</strong>:
<ul>
<li>No requirement for Docker.</li>
<li>Accesses the current browser page as input, enabling context-aware interactions.</li>
</ul>
</li>
</ul>
<h3 id="enchanted">Enchanted</h3>
<p><a href="https://apps.apple.com/app/enchanted-ai-assistant/id">Enchanted</a> is unique as it provides an iOS UI for local LLMs with support for Ollama.</p>
<ul>
<li><strong>Usage</strong>:
<ul>
<li>By using <strong>Tailscale</strong>, I can connect it to Ollama running on my MacBook.</li>
<li>Serves as an alternative to Apple’s native intelligence features.</li>
</ul>
</li>
</ul>
<h3 id="anythingllm">AnythingLLM</h3>
<p><a href="https://github.com/Mintplex-Labs/anything-llm">AnythingLLM</a> offers enhanced RAG capabilities. However, in my experience, it hasn&rsquo;t performed consistently well enough for regular use.</p>
<h3 id="dify">Dify</h3>
<p><a href="https://github.com/langgenius/dify">Dify</a> is a powerful and feature-rich option.</p>
<ul>
<li><strong>Pros</strong>:
<ul>
<li>Easy to set up with an extensive feature set.</li>
</ul>
</li>
<li><strong>Cons</strong>:
<ul>
<li>Resource-intensive, requiring Docker and running multiple containers like Redis and PostgreSQL.</li>
</ul>
</li>
</ul>
<h2 id="diving-into-llamaindex">Diving into llamaindex</h2>
<p><a href="https://github.com/jerryjliu/llama_index">llamaindex</a> is geared towards developers who are comfortable writing code. While it offers robust functionalities, it does have a learning curve.</p>
<ul>
<li><strong>Observations</strong>:
<ul>
<li>Documentation is somewhat limited, often necessitating diving into the source code.</li>
<li>The <code>llamaindex-cli</code> tool aims to simplify getting started but isn&rsquo;t entirely stable.
<ul>
<li>Works seamlessly with OpenAI.</li>
<li>Requires code modifications to function with Ollama.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="experimenting-with-candle">Experimenting with Candle</h2>
<p><strong>Candle</strong> is an intriguing project written in <strong>Rust</strong>.</p>
<ul>
<li>
<p><strong>Features</strong>:</p>
<ul>
<li>Uses <a href="https://huggingface.co/">Hugging Face</a> to download models.</li>
<li>Simple to run but exhibits slower performance compared to Ollama.</li>
</ul>
</li>
<li>
<p><strong>Additional Tools</strong>:</p>
<ul>
<li><strong>Cake</strong>: A distributed solution based on Candle, <strong>Cake</strong> opens up possibilities for scaling and extending use cases.</li>
</ul>
</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>Exploring local LLMs has been an exciting journey filled with learning and experimentation. Tools like Ollama, llamaindex, and Candle offer various pathways to harnessing the power of LLMs on personal hardware. While there are challenges, especially with hardware limitations and setup complexities, the control and privacy afforded by local models make the effort worthwhile.</p>
<hr>
<p><em>Feel free to share your experiences or ask questions in the comments below!</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>Exploring Tailscale: Building Your Own Network Easily</title>
      <link>https://blog.minifish.org/posts/exploring-tailscale-building-your-own-network-easily/</link>
      <pubDate>Wed, 27 Nov 2024 18:18:38 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/exploring-tailscale-building-your-own-network-easily/</guid>
      <description>I recently started experimenting with Tailscale, a tool that has significantly simplified the way I manage my personal network across devices. In this blog post, I&amp;#39;ll share how I...</description>
      <content:encoded><![CDATA[<p>I recently started experimenting with <strong>Tailscale</strong>, a tool that has significantly simplified the way I manage my personal network across devices. In this blog post, I&rsquo;ll share how I discovered Tailscale, its core features, and my personal setup that leverages this powerful tool.</p>
<h2 id="discovering-tailscale-through-webvm">Discovering Tailscale Through WebVM</h2>
<p>My journey with Tailscale began when I came across <a href="https://github.com/leaningtech/webvm">WebVM</a>, an impressive project that allows you to run a virtual machine directly in your browser. Intrigued by the possibilities, I delved deeper and discovered that Tailscale could help me create a seamless, private network across all my devices.</p>
<h2 id="what-is-tailscale">What is Tailscale?</h2>
<p>Tailscale is a mesh VPN network built on top of <strong>WireGuard</strong>, specifically using the <a href="https://github.com/WireGuard/wireguard-go">WireGuard-go</a> implementation. It allows you to create a secure, encrypted network between your devices, no matter where they are located.</p>
<h3 id="key-features">Key Features</h3>
<ul>
<li><strong>Free Plan Available</strong>: Tailscale offers a free plan that is sufficient for personal use, allowing up to 20 devices.</li>
<li><strong>Ease of Use</strong>: Setting up Tailscale is straightforward. With minimal configuration, you can have your own network up and running quickly.</li>
<li><strong>Cross-Platform Support</strong>: Tailscale works exceptionally well across the Apple ecosystem, including <strong>iOS</strong>, <strong>tvOS</strong>, and <strong>macOS</strong>.</li>
<li><strong>Magic DNS Service</strong>: It provides a built-in DNS service that makes it easy to address your devices by name.</li>
</ul>
<h2 id="performance-on-different-platforms">Performance on Different Platforms</h2>
<p>While Tailscale shines on Apple devices, in my experience, it hasn&rsquo;t performed as well on Windows. I encountered some connectivity and stability issues on Windows machines, which may vary based on individual setups.</p>
<h2 id="my-tailscale-setup">My Tailscale Setup</h2>
<p>Here&rsquo;s how I leveraged Tailscale to connect my devices and access my home network seamlessly.</p>
<h3 id="running-tailscale-on-apple-tv">Running Tailscale on Apple TV</h3>
<p>I installed Tailscale on my <strong>Apple TV</strong>, which stays online <strong>24/7</strong>. This makes it an excellent candidate for a consistently available node in my network.</p>
<ul>
<li><strong>Enabling Subnet Routing</strong>: By enabling subnet routing on the Apple TV, I can access other devices on the same local network, such as my <strong>NAS</strong> and <strong>router</strong>, as if I were connected locally.</li>
<li><strong>Setting Up an Exit Node</strong>: I configured the Apple TV as an <strong>exit node</strong>, allowing me to route internet traffic through my home network. This is useful when I need to access geo-restricted content or ensure a secure connection.</li>
</ul>
<h3 id="connecting-other-devices">Connecting Other Devices</h3>
<p>I also installed Tailscale on my <strong>MacBook</strong> and <strong>iPhone</strong>, which allows all my personal devices to communicate over the secure network, no matter where I am.</p>
<h2 id="benefits-ive-enjoyed">Benefits I&rsquo;ve Enjoyed</h2>
<ul>
<li><strong>Secure Remote Access</strong>: I can securely access my home network devices from anywhere.</li>
<li><strong>Consistent Environment</strong>: All my devices appear on the same network, simplifying file sharing and remote management.</li>
<li><strong>No Need for Complex VPN Setups</strong>: Tailscale eliminates the need for traditional VPN configurations, port forwarding, or dynamic DNS services.</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>Tailscale has transformed the way I interact with my devices across different locations. Its ease of use and robust feature set make it an excellent choice for anyone looking to create a personal, secure network.</p>
<p>If you&rsquo;re interested in simplifying your network setup and want a hassle-free way to connect your devices, I highly recommend giving Tailscale a try.</p>
<p><strong>Links:</strong></p>
<ul>
<li><a href="https://tailscale.com/">Tailscale Official Website</a></li>
<li><a href="https://github.com/leaningtech/webvm">WebVM Project on GitHub</a></li>
<li><a href="https://github.com/WireGuard/wireguard-go">WireGuard-go on GitHub</a></li>
</ul>
<p><em>Note: This post reflects my personal experiences with Tailscale. Performance may vary based on individual configurations and devices.</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>Tips for Traveling in Vietnam: My Personal Experiences</title>
      <link>https://blog.minifish.org/posts/tips-for-traveling-in-vietnam-my-personal-experiences/</link>
      <pubDate>Wed, 27 Nov 2024 18:13:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/tips-for-traveling-in-vietnam-my-personal-experiences/</guid>
      <description>Vietnam is a vibrant country with a rich culture, friendly people, and stunning landscapes. Recently, I spent some time exploring Vietnam, and I wanted to share some of my...</description>
      <content:encoded><![CDATA[<p>Vietnam is a vibrant country with a rich culture, friendly people, and stunning landscapes. Recently, I spent some time exploring Vietnam, and I wanted to share some of my experiences and tips to help fellow travelers make the most of their visit.</p>
<h2 id="internet-access-and-blocked-websites">Internet Access and Blocked Websites</h2>
<ul>
<li><strong>Slow Access to Twitter</strong>: If you&rsquo;re an avid Twitter user, be prepared for slower connection speeds. The platform doesn&rsquo;t perform as well in Vietnam.</li>
<li><strong>Inaccessible Websites</strong>: Certain websites are blocked in Vietnam, including:
<ul>
<li><strong>Medium</strong></li>
<li>Some porn websites</li>
</ul>
</li>
<li><strong>Tip</strong>: If you rely on any of these platforms, consider downloading content beforehand or using a VPN to access them during your stay.</li>
</ul>
<h2 id="visa-process-for-chinese-citizens">Visa Process for Chinese Citizens</h2>
<ul>
<li><strong>E-Visa Conversion</strong>: Travelers from China holding an E-Visa need to convert it into a paper visa upon arrival.
<ul>
<li><strong>Bring Two Photos</strong>: Ensure you have two passport-sized photos ready for the visa conversion process.</li>
<li><strong>Carry Cash for Tips</strong>: It&rsquo;s customary to offer tips during the visa processing, so have some cash (preferably small denominations) on hand.</li>
</ul>
</li>
</ul>
<h2 id="exploring-saigon-ho-chi-minh-city">Exploring Saigon (Ho Chi Minh City)</h2>
<ul>
<li><strong>City Vibes</strong>: Saigon reminded me of a blend between <strong>Shanghai</strong> and <strong>Qingdao</strong>.
<ul>
<li><strong>Cleanliness</strong>: While the city is bustling with energy, it&rsquo;s not as clean as Shanghai or Qingdao.</li>
</ul>
</li>
<li><strong>Abundance of Coffee Shops</strong>: Vietnam is famous for its coffee culture. You&rsquo;ll find numerous coffee shops on almost every street corner.
<ul>
<li><strong>Must-Try</strong>: Don&rsquo;t miss out on traditional Vietnamese iced coffee (<strong>Cà Phê Sữa Đá</strong>).</li>
</ul>
</li>
</ul>
<h2 id="affordability">Affordability</h2>
<ul>
<li><strong>Cheaper Than Bangkok</strong>: Compared to cities like Bangkok, Saigon is significantly more affordable.
<ul>
<li><strong>Hotels</strong>: Accommodation can be about <strong>half the price</strong> of equivalent hotels in Bangkok.</li>
<li><strong>General Expenses</strong>: Food, transportation, and entertainment are also reasonably priced.</li>
</ul>
</li>
</ul>
<h2 id="currency-and-payments">Currency and Payments</h2>
<ul>
<li><strong>Accepted Currencies</strong>:
<ul>
<li><strong>Vietnamese Dong (VND)</strong> is the official currency.</li>
<li><strong>US Dollars (USD)</strong> are widely accepted and sometimes preferred.</li>
<li><strong>Chinese RMB</strong> can be used in some places, but USD is more commonly accepted.</li>
</ul>
</li>
<li><strong>Tip</strong>: Carrying USD can be convenient, but always have some local currency for small purchases.</li>
</ul>
<h2 id="local-customs-and-observations">Local Customs and Observations</h2>
<ul>
<li><strong>Clothing Choices</strong>: You&rsquo;ll notice many locals wearing long-sleeved clothing and pants, even in hot weather.
<ul>
<li><strong>Reason</strong>: This is to <strong>prevent sunburn</strong> and protect their skin from the sun.</li>
</ul>
</li>
<li><strong>Hardworking People</strong>: The Vietnamese are known for their strong work ethic. Streets are lively with activity from early morning until late at night.</li>
<li><strong>Affordable Goods</strong>: Many items, from street food to souvenirs, are priced affordably.</li>
<li><strong>Non-Spicy Cuisine</strong>:
<ul>
<li><strong>Similar to Cantonese Food</strong>: The flavors are mild and focus on the freshness of ingredients.</li>
<li><strong>Must-Try Dishes</strong>: Pho (noodle soup), Banh Mi (baguette sandwich), and fresh spring rolls.</li>
</ul>
</li>
</ul>
<h2 id="final-thoughts">Final Thoughts</h2>
<p>Vietnam offers a captivating blend of cultural experiences, historical sites, and modern attractions. Whether you&rsquo;re sipping coffee in a cozy café, exploring bustling markets, or simply taking in the sights and sounds of the city, there&rsquo;s something for every traveler.</p>
<p><strong>Travel Tips</strong>:</p>
<ul>
<li><strong>Plan Ahead</strong>: Be aware of the internet limitations and plan accordingly if you need access to certain websites.</li>
<li><strong>Stay Hydrated</strong>: The climate can be hot and humid, so drink plenty of water.</li>
<li><strong>Respect Local Customs</strong>: Dress modestly when visiting temples or rural areas.</li>
</ul>
<p>I hope these insights help you in planning your trip to Vietnam. It&rsquo;s a destination filled with unforgettable experiences!</p>
<p>Safe travels!</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to Choose the Right Fishing Gear: Tips from My Fishing Experiences</title>
      <link>https://blog.minifish.org/posts/how-to-choose-the-right-fishing-gear-tips-from-my-fishing-experiences/</link>
      <pubDate>Wed, 27 Nov 2024 17:46:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-choose-the-right-fishing-gear-tips-from-my-fishing-experiences/</guid>
      <description>Over the years, I&amp;#39;ve gathered a lot of experience in fishing, especially in selecting the right gear. Choosing the appropriate rod, reel, line, and lure can significantly impact...</description>
      <content:encoded><![CDATA[<p><em>Note: All prices mentioned are in RMB.</em></p>
<h2 id="introduction">Introduction</h2>
<p>Over the years, I&rsquo;ve gathered a lot of experience in fishing, especially in selecting the right gear. Choosing the appropriate rod, reel, line, and lure can significantly impact your fishing success. In this blog, I want to share my insights to help you make informed decisions when selecting fishing equipment.</p>
<h2 id="rod-selection">Rod Selection</h2>
<h3 id="understanding-rod-actions">Understanding Rod Actions</h3>
<ul>
<li><strong>L (Light) Action Rods</strong>:
<ul>
<li>Best for casting lures weighing <strong>4-5g</strong>.</li>
<li>Ideal for using lures within half of the rod&rsquo;s lure weight range (e.g., for a rod rated <strong>2-7g</strong>, the optimal lure weight is around <strong>4-5g</strong>).</li>
</ul>
</li>
<li><strong>UL (Ultra-Light) Action Rods</strong>:
<ul>
<li>Suitable for casting lures weighing <strong>3g</strong> or less.</li>
</ul>
</li>
</ul>
<h3 id="one-piece-vs-multi-piece-rods">One-Piece vs. Multi-Piece Rods</h3>
<ul>
<li><strong>One-Piece Rods</strong>:
<ul>
<li>Offer better sensitivity and casting performance.</li>
<li>However, they are less portable and harder to transport.</li>
<li>Not as easy to sell if you decide to upgrade.</li>
</ul>
</li>
<li><strong>Multi-Piece Rods</strong>:
<ul>
<li>More convenient for travel.</li>
<li>Slight trade-off in sensitivity compared to one-piece rods.</li>
</ul>
</li>
</ul>
<h3 id="recommended-action">Recommended Action</h3>
<ul>
<li>The <strong>best action</strong> for spinning cast rods is <strong>Light (L)</strong> action.</li>
</ul>
<h2 id="reel-selection">Reel Selection</h2>
<h3 id="spinning-reels-vs-baitcasting-reels">Spinning Reels vs. Baitcasting Reels</h3>
<ul>
<li><strong>Spinning Reels</strong>:
<ul>
<li>Easier for casting, especially for beginners.</li>
<li>Better suited for saltwater fishing than baitcasting reels.</li>
<li>Can use heavier action rods to cast small lures effectively.</li>
</ul>
</li>
<li><strong>Baitcasting Reels</strong>:
<ul>
<li>Look cool and offer precision, but have a steeper learning curve.</li>
<li>Not as user-friendly for casting light lures.</li>
</ul>
</li>
</ul>
<h3 id="reel-size-and-line-compatibility">Reel Size and Line Compatibility</h3>
<ul>
<li>For a <strong>2500S</strong> reel:
<ul>
<li><strong>0.8 PE</strong> line is sufficient.</li>
<li>Using <strong>0.6 PE</strong> line on a 2500S reel may require too much line to fill the spool.</li>
</ul>
</li>
<li>For a <strong>2000S</strong> reel:
<ul>
<li><strong>0.6 PE</strong> line is appropriate.</li>
</ul>
</li>
<li>It&rsquo;s not common to use PE lines smaller than <strong>0.6</strong>, as they are less widely available.</li>
</ul>
<h3 id="light-weight-reels">Light Weight Reels</h3>
<ul>
<li>Always choose <strong>lightweight reels</strong> for better balance and less fatigue during fishing.</li>
</ul>
<h2 id="line-selection">Line Selection</h2>
<h3 id="pe-line-vs-fluorocarbon">PE Line vs. Fluorocarbon</h3>
<ul>
<li><strong>Casting Difficulty</strong>:
<ul>
<li><strong>1.5 PE</strong> and fluorocarbon lines offer similar casting challenges.</li>
</ul>
</li>
<li><strong>PE Line Advantages</strong>:
<ul>
<li>Thinner diameter for the same strength compared to fluorocarbon.</li>
<li>Essential for longer casting distances.</li>
</ul>
</li>
<li><strong>Avoiding Line Connections</strong>:
<ul>
<li>Use a <strong>Carolina rig</strong> to eliminate the need to connect PE line and fluorocarbon leader.</li>
</ul>
</li>
</ul>
<h3 id="recommended-lines">Recommended Lines</h3>
<ul>
<li><strong>YGK PE Line</strong>:
<ul>
<li>Highly recommended for its quality.</li>
<li>Performs much better than Sufix lines.</li>
<li>Consider using <strong>0.6 or 0.8 PE</strong> from YGK.</li>
</ul>
</li>
<li><strong>Fluorocarbon Line</strong>:
<ul>
<li><strong>1.5 Fluorocarbon</strong> can be used as a leader if necessary.</li>
</ul>
</li>
</ul>
<h2 id="rigs-and-lures">Rigs and Lures</h2>
<ul>
<li><strong>Carolina Rig</strong>:
<ul>
<li>Allows you to use PE line without needing to connect it to a fluorocarbon leader.</li>
<li>Effective for various fishing conditions.</li>
</ul>
</li>
</ul>
<h2 id="recommendations">Recommendations</h2>
<h3 id="final-gear-choices">Final Gear Choices</h3>
<ul>
<li><strong>Rod</strong>:
<ul>
<li><strong>L Action Spinning Rod</strong> with multiple pieces for portability.</li>
</ul>
</li>
<li><strong>Reel</strong>:
<ul>
<li><strong>2000S or 2500S</strong> spinning reel, prioritize lightweight models.</li>
</ul>
</li>
<li><strong>Line</strong>:
<ul>
<li><strong>0.6 or 0.8 YGK PE line</strong> (lean towards 0.6 for more capacity).</li>
<li>Optional <strong>1.5 Fluorocarbon</strong> leader.</li>
</ul>
</li>
<li><strong>Rig</strong>:
<ul>
<li>Use a <strong>Carolina rig</strong> setup.</li>
</ul>
</li>
</ul>
<h2 id="brands-and-models">Brands and Models</h2>
<h3 id="chinese-domestic-market-cdm-rods">Chinese Domestic Market (CDM) Rods</h3>
<ul>
<li>Few high-quality CDM spinning rods are available.</li>
<li>The best CDM spinning rod is the <strong>翠鸟 (Kingfisher)</strong> from <strong>钓之屋 (Fishing House)</strong>.</li>
<li>The <strong>游侠</strong> (the larger version of 翠鸟) is not as good.
<ul>
<li>Example: A 2500S reel on a 游侠 lacks proper rod skewness, affecting casting performance.</li>
</ul>
</li>
</ul>
<h3 id="shimano-rods-and-reels">Shimano Rods and Reels</h3>
<ul>
<li><strong>Shimano</strong> spinning rods and reels are superior in performance.</li>
<li>The reels have features that prevent accidental rolling after opening the bail, allowing you to stop the line with your finger for better control.</li>
</ul>
<h4 id="reels-ive-used">Reels I&rsquo;ve Used</h4>
<ul>
<li><strong>Shimano Vanquish 2500S (2023 model)</strong>:
<ul>
<li>Extremely lightweight and smooth.</li>
<li>Feels almost too slim, raising concerns about durability.</li>
<li>The infinite loop system is excellent.</li>
</ul>
</li>
<li><strong>Shimano Stradic 2500S (2023 model)</strong>:
<ul>
<li>Heavier compared to Vanquish.</li>
<li>Handle design is not as comfortable.</li>
<li>Appearance gives a sense of reliability.</li>
</ul>
</li>
<li><strong>Shimano Vanford C2000SHG (2020 model)</strong>:
<ul>
<li>Great performance but similar concerns about durability due to plastic components.</li>
<li>Feels less robust than the Stradic but is lighter.</li>
</ul>
</li>
</ul>
<h3 id="price-comparison-in-rmb">Price Comparison (in RMB)</h3>
<ul>
<li><strong>钓之屋 翠鸟 (Kingfisher)</strong>: <strong>360</strong></li>
<li><strong>Shimano Stradic</strong>: <strong>900</strong></li>
<li><strong>Shimano Vanford</strong>: <strong>1,100</strong></li>
<li><strong>Shimano Vanquish</strong>: <strong>2,600</strong></li>
<li><strong>Shimano Stella</strong>: <strong>3,800</strong></li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>Choosing the right fishing gear is crucial for an enjoyable and successful fishing experience. By considering the rod action, reel type and size, line selection, and the right rigs, you can optimize your setup for the best performance. While high-end brands like Shimano offer top-quality equipment, there are also cost-effective options available. Remember to prioritize what suits your fishing style and comfort.</p>
<p>Happy fishing!</p>
]]></content:encoded>
    </item>
    <item>
      <title>A Cloudflare WARP Failure and VPS Recovery Notes</title>
      <link>https://blog.minifish.org/posts/encounter-with-a-major-issue-with-cloudflare-warp-a-life-and-death-rescue-for-vps/</link>
      <pubDate>Wed, 15 May 2024 20:31:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/encounter-with-a-major-issue-with-cloudflare-warp-a-life-and-death-rescue-for-vps/</guid>
      <description>A recovery note from a Cloudflare WARP failure that broke VPS connectivity, with the failure mode and rescue path documented.</description>
      <content:encoded><![CDATA[<p>Today, I planned to use Warp to select an IP exit for my VPS, following the <a href="https://developers.cloudflare.com/warp-client/get-started/linux/">Cloudflare official documentation</a>. When executing the <code>warp-cli connect</code> step, the server immediately lost connection, and the problem persisted even after rebooting.</p>
<p>After researching, I found that this problem is not unique. For instance, in a <a href="https://www.v2ex.com/t/933725">discussion on V2EX</a>, many users encountered similar issues. The solution is to run <code>warp-cli set-mode proxy</code> before executing <code>warp-cli connect</code> to bypass the local address. Surprisingly, Cloudflare&rsquo;s official documentation does not mention this crucial step, undoubtedly increasing the complexity and risk of configuration.</p>
<p>In the process of exploring solutions, I found that some users suggested repairing by rebuilding the instance or using VNC connection. However, since I am using AWS Lightsail, VNC is not applicable. Ultimately, I decided to try the method mentioned in <a href="https://www.4os.org/2022/02/14/aws-lightsail-ssh-%E6%8C%82%E6%8E%89%E5%A6%82%E4%BD%95%E7%99%BB%E5%BD%95/">this article</a>: creating a snapshot backup of the current VPS, then creating a new instance from the snapshot, and loading a script to execute <code>warp-cli set-mode proxy</code> when the new instance starts.</p>
<p>After checking the existing instance, I found that no snapshot had been created. This discovery reminded me of the importance of regular backups. Without other options, I could only attempt a snapshot backup as guided by the aforementioned article. However, no matter what startup script command I tried, it failed to execute successfully. The execution result of AWS Lightsail&rsquo;s startup script is not visible, making problem-solving more difficult.</p>
<p>In near desperation, I found an old snapshot dated 2022 on the snapshot page. Although this snapshot was created using old technology, and many important updates might be lost after recovery, it was my last hope. After starting the snapshot recovery process, I unexpectedly discovered through the <code>history</code> command that this snapshot contained all the important updates. This discovery allowed the entire recovery process to be completed smoothly.</p>
<p>This experience re-emphasized the importance of backups. Careful backups from the past ultimately avoided severe data loss. Furthermore, AWS&rsquo;s static IP retention feature also played a crucial role. The new instance could immediately bind to the IP once the old instance released the static IP, achieving a seamless switch.</p>
<h2 id="conclusion">Conclusion</h2>
<ol>
<li><strong>Backups are essential</strong>: Regular backups are key to ensuring stable system operations.</li>
<li><strong>Operate with caution</strong>: Before executing critical commands, thoroughly review and understand relevant documentation and user feedback to avoid potential risks.</li>
<li><strong>Trust your past self</strong>: Meticulous work done in the past can often prove invaluable at critical moments.</li>
</ol>
<p>I hope this experience can serve as a reference and help for others, preventing similar issues from occurring.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to Use the HTTP API in TiDB with TLS Enabled</title>
      <link>https://blog.minifish.org/posts/how-to-use-the-http-api-in-tidb-with-tls-enabled/</link>
      <pubDate>Mon, 22 May 2023 21:45:00 -0700</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-use-the-http-api-in-tidb-with-tls-enabled/</guid>
      <description>Many customers have TLS enabled, which is different from the lab environment.</description>
      <content:encoded><![CDATA[<h2 id="background">Background</h2>
<p>Many customers have TLS enabled, which is different from the lab environment.</p>
<h2 id="curl">Curl</h2>
<p>Curl requires a specified CA certificate, otherwise it will report an error.</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-bash" data-lang="bash"><span style="display:flex;"><span>curl --cacert ca.crt https://127.0.0.1:10080/status
</span></span></code></pre></div><h2 id="wget">Wget</h2>
<p>Many containers do not have curl, so wget is used instead. Wget is better as it does not require a CA certificate.</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-bash" data-lang="bash"><span style="display:flex;"><span>wget --no-check-certificate http://127.0.0.1:10080/status
</span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>How to Deploy a Secure Transparent Gateway</title>
      <link>https://blog.minifish.org/posts/how-to-deploy-a-secure-transparent-gateway/</link>
      <pubDate>Wed, 12 Oct 2022 21:07:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-deploy-a-secure-transparent-gateway/</guid>
      <description>After moving house, there are many more devices at home that need internet access. However, I don&amp;#39;t want to configure a proxy on each device, so I thought of using a transparent...</description>
      <content:encoded><![CDATA[<h2 id="background">Background</h2>
<p>After moving house, there are many more devices at home that need internet access. However, I don&rsquo;t want to configure a proxy on each device, so I thought of using a transparent gateway.</p>
<h2 id="transparent-gateway">Transparent Gateway</h2>
<p>After some research, I found that the easiest way is to use the premium version of Clash, although I didn&rsquo;t know when Clash released a premium version. I mainly referred to <a href="https://www.cfmem.com/2022/05/clash.html">this article</a>. It&rsquo;s much simpler than setting up iptables.</p>
<h3 id="network-topology">Network Topology</h3>
<p>I have a 10-year-old Thinkpad x230 at home, which is perfect for this purpose. Here is a simple topology diagram.</p>
<p>Router1 is a fiber-optic modem with routing capabilities, Router2 is a regular router, with the gateway and DNS pointing to the Thinkpad, where Linux is running to act as a transparent gateway with Clash on top.</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-txt" data-lang="txt"><span style="display:flex;"><span>                                 +------------+
</span></span><span style="display:flex;"><span>                                 |            |
</span></span><span style="display:flex;"><span>                                 |  Internet  |
</span></span><span style="display:flex;"><span>                                 |            |
</span></span><span style="display:flex;"><span>                                 +-----+------+
</span></span><span style="display:flex;"><span>                                       |
</span></span><span style="display:flex;"><span>                                 +-----+------+
</span></span><span style="display:flex;"><span>                                 |            |
</span></span><span style="display:flex;"><span>                      +----------+  Router1   +-----------+
</span></span><span style="display:flex;"><span>                      |          |            |           |
</span></span><span style="display:flex;"><span>                      |          +------------+           |
</span></span><span style="display:flex;"><span>                      |                                   |
</span></span><span style="display:flex;"><span>                      |                                   |
</span></span><span style="display:flex;"><span>                +-----+-----+                       +-----+------+
</span></span><span style="display:flex;"><span>                |           |                       |            |
</span></span><span style="display:flex;"><span>     +----------+  Router2  +----------+            |  Thinkpad  |
</span></span><span style="display:flex;"><span>     |          |           |          |            |            |
</span></span><span style="display:flex;"><span>     |          +-----+-----+          |            +------------+
</span></span><span style="display:flex;"><span>     |                |                |
</span></span><span style="display:flex;"><span>     |                |                |
</span></span><span style="display:flex;"><span>     |                |                |
</span></span><span style="display:flex;"><span>+----+-----+     +----+-----+    +-----+-----+
</span></span><span style="display:flex;"><span>|          |     |          |    |           |
</span></span><span style="display:flex;"><span>|   Mac    |     |  iPad    |    |  iPhone   |
</span></span><span style="display:flex;"><span>|          |     |          |    |           |
</span></span><span style="display:flex;"><span>+----------+     +----------+    +-----------+
</span></span></code></pre></div><h3 id="add-dns-section-in-clash-configuration">Add DNS Section in Clash Configuration</h3>
<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-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">dns</span>:
</span></span><span style="display:flex;"><span><span style="color:#f92672">enable</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">listen</span>: <span style="color:#ae81ff">0.0.0.0</span>:<span style="color:#ae81ff">53</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">enhanced-mode</span>: <span style="color:#ae81ff">fake-ip</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">nameserver</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">114.114.114.114</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">fallback</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">8.8.8.8</span>
</span></span></code></pre></div><h3 id="clash-tun-feature-section">Clash tun Feature Section</h3>
<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-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">tun</span>:
</span></span><span style="display:flex;"><span><span style="color:#f92672">enable</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">stack</span>: <span style="color:#ae81ff">system</span> <span style="color:#75715e"># or gvisor</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">dns-hijack</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">any:53</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">tcp://any:53</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">auto-route</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">auto-detect-interface</span>: <span style="color:#66d9ef">true</span>
</span></span></code></pre></div><p>For traffic forwarding, simply edit <code>/etc/sysctl.conf</code> on the Thinkpad and add <code>net.ipv4.ip_forward=1</code>, then execute <code>sysctl -p</code> to apply it. After that, point the gateway and DNS of Router2 to the Thinkpad, and you&rsquo;re done.</p>
<h2 id="network-protocols">Network Protocols</h2>
<p>Initially, I used native HTTP2 for unblocking, but it cannot proxy UDP. When only a few devices need unblocking, it doesn&rsquo;t matter whether UDP is used, but with many devices at home, some of them can only use UDP. I considered socks + tls, but it didn&rsquo;t feel secure and required opening odd ports like UDP 443. It felt like giving away my intentions. Eventually, I chose Trojan, which essentially mimics native HTTPS. Trojan has two versions; I used Trojan-go simply because I didn&rsquo;t want to manage dependencies. Also, I&rsquo;m more familiar with Go.</p>
<p>Trojan-go has a requirement for a genuinely accessible HTTP server, so I used the simplest Python <code>http.server</code>. Back in Python 2, it was called <code>simplehttp</code>. You can simply use <code>python3 -m http.server 80</code> and optionally add <code>--directory</code> to specify a directory.</p>
<p>Additionally, Trojan-go requires the client to fill in the SNI, which means using the domain used during key application. Therefore, prerequisites like applying for the <a href="https://github.com/haoel/haoel.github.io">domain</a>, applying for Let&rsquo;s Encrypt certificates, and configuring crontab must all be completed. There&rsquo;s a learning curve, but I had done it before, so I just skipped that part.</p>
<p>For the client part, you can use Clash directly, and refer to <a href="https://github.com/Dreamacro/clash/wiki/configuration">here</a> for guidance.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to Set Up a Minecraft Bedrock Server Using Multipass</title>
      <link>https://blog.minifish.org/posts/how-to-set-up-a-minecraft-bedrock-server-using-multipass/</link>
      <pubDate>Mon, 02 May 2022 19:05:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-set-up-a-minecraft-bedrock-server-using-multipass/</guid>
      <description>Recently, both of my kids have become interested in Minecraft, and some of their peers are also playing it. We previously bought the Switch version, but its online capabilities...</description>
      <content:encoded><![CDATA[<h2 id="background">Background</h2>
<p>Recently, both of my kids have become interested in Minecraft, and some of their peers are also playing it. We previously bought the Switch version, but its online capabilities are quite poor, and the device performance is subpar, resulting in a less-than-ideal experience. Thus, I began considering the idea of setting up our own server. Of course, you can play in multiplayer with friends, but the game ends as soon as the host goes offline, which is not as good as having a server that is always online.</p>
<p>For version selection, there is the NetEase version, the official Java version, and the Bedrock version. Based on my previous understanding, the NetEase version has all sorts of anti-addiction regulations, so that&rsquo;s a no-go. The Java version server setup seems to rely on third-party launchers, resembling piracy to some extent. Therefore, I decided on the Bedrock version. Another reason for choosing the Bedrock version is its origins as the mobile version of Minecraft, and Microsoft later <a href="https://minecraft.fandom.com/zh/wiki/%E5%9F%BA%E5%B2%A9%E7%89%88?variant=zh">expanded support to more platforms</a>, making it the most versatile. Additionally, its core is written in C++, which should offer better performance and resource efficiency.</p>
<h2 id="downloading-the-server-software">Downloading the Server Software</h2>
<p>I downloaded the <a href="https://www.minecraft.net/en-us/download/server/bedrock">Ubuntu version</a>. While I also downloaded the Windows version and managed to run it, I’m not experienced in managing Windows Server, and the Windows version has some quirks. I won’t delve into those here.</p>
<h2 id="local-network-server">Local Network Server</h2>
<p>Initially, I intended to use an old Thinkpad, but couldn&rsquo;t find it, and ended up using an old Macbook. After updating everything, I found that the old Macbook was still quite robust, outperforming the Thinkpad. So, I set up a virtual machine. I discovered that Ubuntu Server no longer supports direct ISO downloads, requiring Multipass to launch instead.</p>
<p>What is Multipass?</p>
<p>Developed by the Ubuntu team, it is a lightweight virtualization platform. Multipass consists of <code>multipass</code> and <code>multipassd</code>. The former provides both GUI and CLI, while the latter requires root permissions and runs in the background. It can be downloaded directly or installed via brew cask.</p>
<p>Multipass seems to only install Ubuntu Server, with some customization, such as automatically creating a user named &ldquo;ubuntu&rdquo; and key pairing. Once installed, you get a 1C 1GB virtual machine called &ldquo;primary&rdquo; that automatically mounts the user’s home directory.</p>
<p><code>multipassd</code> is more of a pluggable virtual hypervisor supporting hyperkit and qemu by default, with hyperkit intended for Intel macOS and qemu for M1.</p>
<p><code>multipassd</code> can also use Virtualbox as a backend hypervisor, which is more recommended because it offers bridging capabilities, whereas relying solely on port mapping would be cumbersome. It&rsquo;s not that qemu doesn&rsquo;t support bridging, but there’s an easy-to-follow <a href="https://multipass.run/docs/set-up-the-driver">Virtualbox bridging tutorial</a> on Multipass’s official documentation.</p>
<p>I set up Virtualbox first. I must say, Oracle is generous here as it&rsquo;s still free to use. Then, I followed the steps from the link above. It&rsquo;s essential to reboot the machine after running the first step <code>sudo multipass set local.driver=virtualbox</code>, as the variable might not take effect immediately. Otherwise, the primary created will still use the old backend. Since there&rsquo;s already a default virtual machine, I didn&rsquo;t want to create another one to avoid extra resource usage. Additionally, note a few things:</p>
<ol>
<li>For modifying primary configuration, I didn’t find a way to do it using Multipass, so I used vbox’s command <code>sudo VBoxManage controlvm &quot;primary&quot; --cpus 2 --memory 4096</code> (where memory is in MB).</li>
<li>The primary mount point in Multipass can automatically unmount in some situations, resulting in errors. Therefore, Minecraft data shouldn’t be stored in the mount point permanently to avoid core dumps.</li>
</ol>
<p>After extracting the server files, just start it in tmux/screen.</p>
<p>Lastly, I used <a href="https://apps.apple.com/us/app/amphetamine/id937984704?mt=12">Amphetamine</a> from the App Store to ensure that the laptop continues working when closed by disabling the default sleep setting after the screen is closed. You’ll see some warnings when doing this, but just be aware of them.</p>
<p><strong>Update:</strong></p>
<p>For systemd startup, you can refer to <a href="https://gist.github.com/gatopeich/36ed7fab3850367bbcd8e6f40becd4e5">this gist</a>. The server&rsquo;s console has some commands, like &ldquo;stop&rdquo;, that perform graceful shutdowns, so it&rsquo;s necessary to rely on a screen session to create it. Additionally, the server startup has some environmental dependencies, so it’s best to write a small script for starting it, like:</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-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>cd /home/user/bedrock-server
</span></span><span style="display:flex;"><span>LD_LIBRARY_PATH<span style="color:#f92672">=</span>. ./bedrock_server
</span></span></code></pre></div><p>Using absolute paths can lead to core dumps.</p>
<h2 id="public-network-server">Public Network Server</h2>
<p>For public network servers, the choices are to map the internal network to the public network or purchase a cloud server. Due to security concerns, I opted to buy a cloud server. Although the official site mentioned Ubuntu support, I found that Debian servers also run without issues. The only thing to note is that the Bedrock version uses UDP, and China Unicom’s 5G network disables UDP, so without WiFi, you cannot connect.</p>
<h2 id="server-monitoring">Server Monitoring</h2>
<p>To monitor the server’s status, monitoring is necessary, especially for cloud servers, as provider information might be insufficient. I used <a href="https://grafana.com/products/cloud/">Grafana Cloud</a>.</p>
<p>Using the free version is fine, just follow the guide to select a Linux server integration, then run the Grafana Agent installation and verification on the server to be monitored. Note to change the hostname in the Grafana Agent configuration file to differentiate between different servers, which acts as a label.</p>
<p>I understand that Grafana Agent is a lightweight node exporter with some Prometheus functionalities, but it can also remote write, so there&rsquo;s no need to worry about the disk filling up.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to Create GitHub Verified Commits on a MacBook M1</title>
      <link>https://blog.minifish.org/posts/how-to-create-github-verified-commits-on-a-macbook-m1/</link>
      <pubDate>Sat, 12 Feb 2022 11:54:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-create-github-verified-commits-on-a-macbook-m1/</guid>
      <description>One day, I impulsively turned on GitHub&amp;#39;s Vigilant mode.</description>
      <content:encoded><![CDATA[<h2 id="background">Background</h2>
<p>One day, I impulsively turned on GitHub&rsquo;s Vigilant mode.</p>
<p><img alt="test" loading="lazy" src="/posts/how-to-create-github-verified-commits-on-a-macbook-m1/2022-02-12-12.02.07.webp"></p>
<p>As a result, all my commits started looking like this.</p>
<p><img alt="test" loading="lazy" src="/posts/how-to-create-github-verified-commits-on-a-macbook-m1/2022-02-12-12.11.01.webp"></p>
<p>To figure out how to make them Verified, I found the following method.</p>
<h2 id="method">Method</h2>
<p>I actually referred to this <a href="https://zhuanlan.zhihu.com/p/76861431">link</a>. However, it wasn&rsquo;t quite enough, as there might be authentication-related issues on MacBooks that lead to commit errors. So, I found this <a href="https://stackoverflow.com/a/40066889">solution</a>.</p>
<p>In summary, to verify, you need to enter a password. The issue on a Mac is the prompt for entering the password, which needs to be replaced with pinentry-mac, which most people install via homebrew.</p>
<p>Moreover, this solution thoughtfully provides a way to verify:</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-shell" data-lang="shell"><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;test&#34;</span> | gpg --clearsign
</span></span></code></pre></div><h2 id="gpg-experience">GPG Experience</h2>
<ol>
<li>It doesn&rsquo;t replace the ssh key. After successfully setting it up, I deleted my GitHub ssh key and discovered that I couldn&rsquo;t log in. Actually, it only verifies the legitimacy of commits.</li>
<li>On the local machine, in any repo, you only need to enter the password once, and that makes it a verified commit. It doesn&rsquo;t affect daily use; it just adds a green check mark for verification.</li>
<li>Using the https protocol + token seems more reliable than this method, but I&rsquo;m not sure if it provides a verified mark.</li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>How to Play Minecraft (Bedrock Edition) Cross-Platform</title>
      <link>https://blog.minifish.org/posts/how-to-play-minecraft-bedrock-edition-cross-platform/</link>
      <pubDate>Thu, 03 Feb 2022 00:00:00 +0000</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-play-minecraft-bedrock-edition-cross-platform/</guid>
      <description>During the Chinese New Year holiday, I spent some time at home playing on the Switch with my child, and I ended up purchasing Minecraft (Bedrock Edition). In order to play...</description>
      <content:encoded><![CDATA[<h3 id="background">Background</h3>
<p>During the Chinese New Year holiday, I spent some time at home playing on the Switch with my child, and I ended up purchasing Minecraft (Bedrock Edition). In order to play together, I also bought the Windows version. Additionally, I wanted to test if the GitHub Actions for this blog are still working properly.</p>
<h3 id="preparation">Preparation</h3>
<p>You need two Microsoft accounts, so I dug out my wife&rsquo;s long-unused Hotmail account.</p>
<h3 id="multiplayer">Multiplayer</h3>
<ol>
<li>Restart the game on the Switch and log into the Microsoft account. It has to be restarted each time, and I&rsquo;m not sure why.</li>
<li>Add each other as friends using the short ID.</li>
<li>Create a world on the Switch; Windows should join (it doesn&rsquo;t work the other way around, and I&rsquo;m not sure why).</li>
<li>Enjoy playing together!</li>
</ol>
<p>This guide is brief and straightforward, with nothing much more to add.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to Compare Data Consistency between MySQL and PostgreSQL</title>
      <link>https://blog.minifish.org/posts/how-to-compare-data-consistency-between-mysql-and-postgresql/</link>
      <pubDate>Sun, 09 May 2021 18:13:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-compare-data-consistency-between-mysql-and-postgresql/</guid>
      <description>Recently, I encountered a problem where a user wanted to synchronize data from PostgreSQL to TiDB (which uses the same protocol as MySQL) and wanted to know whether the data after...</description>
      <content:encoded><![CDATA[<h2 id="background">Background</h2>
<p>Recently, I encountered a problem where a user wanted to synchronize data from PostgreSQL to TiDB (which uses the same protocol as MySQL) and wanted to know whether the data after synchronization is consistent. I hadn&rsquo;t dealt with this kind of issue before, so I did a bit of research.</p>
<p>Typically, to verify data consistency, you compute a checksum on both sides and compare them.</p>
<h2 id="tidb-mysql-side">TiDB (MySQL) Side</h2>
<p>For the verification of a specific table, the following SQL is used:</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-SQL" data-lang="SQL"><span style="display:flex;"><span><span style="color:#66d9ef">SELECT</span> bit_xor(
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">CAST</span>(crc32(
</span></span><span style="display:flex;"><span>        concat_ws(<span style="color:#e6db74">&#39;,&#39;</span>,
</span></span><span style="display:flex;"><span>            col1, col2, col3, <span style="color:#960050;background-color:#1e0010">…</span>, colN,
</span></span><span style="display:flex;"><span>            concat(<span style="color:#66d9ef">isnull</span>(col1), <span style="color:#66d9ef">isnull</span>(col2), <span style="color:#960050;background-color:#1e0010">…</span>, <span style="color:#66d9ef">isnull</span>(colN))
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>    ) <span style="color:#66d9ef">AS</span> UNSIGNED)
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">FROM</span> t;
</span></span></code></pre></div><p>Let&rsquo;s look at a specific example:</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-SQL" data-lang="SQL"><span style="display:flex;"><span><span style="color:#66d9ef">DROP</span> <span style="color:#66d9ef">TABLE</span> <span style="color:#66d9ef">IF</span> <span style="color:#66d9ef">EXISTS</span> t;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">CREATE</span> <span style="color:#66d9ef">TABLE</span> t (i INT, j INT);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">INSERT</span> <span style="color:#66d9ef">INTO</span> t <span style="color:#66d9ef">VALUES</span> (<span style="color:#ae81ff">2</span>, <span style="color:#ae81ff">3</span>), (<span style="color:#66d9ef">NULL</span>, <span style="color:#66d9ef">NULL</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">SELECT</span> bit_xor(
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">CAST</span>(crc32(
</span></span><span style="display:flex;"><span>        concat_ws(<span style="color:#e6db74">&#39;,&#39;</span>,
</span></span><span style="display:flex;"><span>            i, j,
</span></span><span style="display:flex;"><span>            concat(<span style="color:#66d9ef">isnull</span>(i), <span style="color:#66d9ef">isnull</span>(j))
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>    ) <span style="color:#66d9ef">AS</span> UNSIGNED)
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">FROM</span> t;
</span></span></code></pre></div><p>The result is:</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>+-------------------------------------------------------------------------------------------------------------------------------------------+
</span></span><span style="display:flex;"><span>| bit_xor(
</span></span><span style="display:flex;"><span>    CAST(crc32(
</span></span><span style="display:flex;"><span>        concat_ws(&#39;,&#39;,
</span></span><span style="display:flex;"><span>            i, j,
</span></span><span style="display:flex;"><span>            concat(isnull(i), isnull(j))
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>    ) AS UNSIGNED)
</span></span><span style="display:flex;"><span>) |
</span></span><span style="display:flex;"><span>+-------------------------------------------------------------------------------------------------------------------------------------------+
</span></span><span style="display:flex;"><span>|                                                           5062371 |
</span></span><span style="display:flex;"><span>+-------------------------------------------------------------------------------------------------------------------------------------------+
</span></span><span style="display:flex;"><span>1 row in set (0.00 sec)
</span></span></code></pre></div><h2 id="postgresql-side">PostgreSQL Side</h2>
<p>The goal is simply to write the same SQL as above, but PostgreSQL does not support <code>bit_xor</code>, <code>crc32</code>, <code>isnull</code>, nor does it have unsigned types. Therefore, the solution is relatively straightforward—relying on UDFs (User-Defined Functions).</p>
<p>After some research, the main missing functions can be addressed with a few custom implementations.</p>
<p><code>bit_xor</code>:</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-SQL" data-lang="SQL"><span style="display:flex;"><span><span style="color:#66d9ef">CREATE</span> <span style="color:#66d9ef">OR</span> <span style="color:#66d9ef">REPLACE</span> <span style="color:#66d9ef">AGGREGATE</span> bit_xor(<span style="color:#66d9ef">IN</span> v bigint) (SFUNC <span style="color:#f92672">=</span> int8xor, <span style="color:#66d9ef">STYPE</span> <span style="color:#f92672">=</span> bigint);
</span></span></code></pre></div><p><code>crc32</code>:</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-SQL" data-lang="SQL"><span style="display:flex;"><span><span style="color:#66d9ef">CREATE</span> <span style="color:#66d9ef">OR</span> <span style="color:#66d9ef">REPLACE</span> <span style="color:#66d9ef">FUNCTION</span> crc32(text_string text) <span style="color:#66d9ef">RETURNS</span> bigint <span style="color:#66d9ef">AS</span> <span style="color:#960050;background-color:#1e0010">$$</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">DECLARE</span>
</span></span><span style="display:flex;"><span>    tmp bigint;
</span></span><span style="display:flex;"><span>    i int;
</span></span><span style="display:flex;"><span>    j int;
</span></span><span style="display:flex;"><span>    byte_length int;
</span></span><span style="display:flex;"><span>    binary_string bytea;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">BEGIN</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">IF</span> text_string <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;&#39;</span> <span style="color:#66d9ef">THEN</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">RETURN</span> <span style="color:#ae81ff">0</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">END</span> <span style="color:#66d9ef">IF</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    i <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>;
</span></span><span style="display:flex;"><span>    tmp <span style="color:#f92672">=</span> <span style="color:#ae81ff">4294967295</span>;
</span></span><span style="display:flex;"><span>    byte_length <span style="color:#f92672">=</span> <span style="color:#66d9ef">bit_length</span>(text_string) <span style="color:#f92672">/</span> <span style="color:#ae81ff">8</span>;
</span></span><span style="display:flex;"><span>    binary_string <span style="color:#f92672">=</span> decode(<span style="color:#66d9ef">replace</span>(text_string, E<span style="color:#e6db74">&#39;\\&#39;</span>, E<span style="color:#e6db74">&#39;\\\\&#39;</span>), <span style="color:#e6db74">&#39;escape&#39;</span>);
</span></span><span style="display:flex;"><span>    LOOP
</span></span><span style="display:flex;"><span>        tmp <span style="color:#f92672">=</span> (tmp <span style="color:#f92672">#</span> get_byte(binary_string, i))::bigint;
</span></span><span style="display:flex;"><span>        i <span style="color:#f92672">=</span> i <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>;
</span></span><span style="display:flex;"><span>        j <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>;
</span></span><span style="display:flex;"><span>        LOOP
</span></span><span style="display:flex;"><span>            tmp <span style="color:#f92672">=</span> ((tmp <span style="color:#f92672">&gt;&gt;</span> <span style="color:#ae81ff">1</span>) <span style="color:#f92672">#</span> (<span style="color:#ae81ff">3988292384</span> <span style="color:#f92672">*</span> (tmp <span style="color:#f92672">&amp;</span> <span style="color:#ae81ff">1</span>)))::bigint;
</span></span><span style="display:flex;"><span>            j <span style="color:#f92672">=</span> j <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>;
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">IF</span> j <span style="color:#f92672">&gt;=</span> <span style="color:#ae81ff">8</span> <span style="color:#66d9ef">THEN</span>
</span></span><span style="display:flex;"><span>                EXIT;
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">END</span> <span style="color:#66d9ef">IF</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">END</span> LOOP;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">IF</span> i <span style="color:#f92672">&gt;=</span> byte_length <span style="color:#66d9ef">THEN</span>
</span></span><span style="display:flex;"><span>            EXIT;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">END</span> <span style="color:#66d9ef">IF</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">END</span> LOOP;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">RETURN</span> (tmp <span style="color:#f92672">#</span> <span style="color:#ae81ff">4294967295</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">END</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">$$</span> <span style="color:#66d9ef">IMMUTABLE</span> <span style="color:#66d9ef">LANGUAGE</span> plpgsql;
</span></span></code></pre></div><p><code>isnull</code>:</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-SQL" data-lang="SQL"><span style="display:flex;"><span><span style="color:#66d9ef">CREATE</span> <span style="color:#66d9ef">OR</span> <span style="color:#66d9ef">REPLACE</span> <span style="color:#66d9ef">FUNCTION</span> <span style="color:#66d9ef">isnull</span>(anyelement) <span style="color:#66d9ef">RETURNS</span> int <span style="color:#66d9ef">AS</span> <span style="color:#960050;background-color:#1e0010">$$</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">BEGIN</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">RETURN</span> <span style="color:#66d9ef">CAST</span>((<span style="color:#960050;background-color:#1e0010">$</span><span style="color:#ae81ff">1</span> <span style="color:#66d9ef">IS</span> <span style="color:#66d9ef">NULL</span>) <span style="color:#66d9ef">AS</span> INT);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">END</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">$$</span> <span style="color:#66d9ef">LANGUAGE</span> plpgsql;
</span></span></code></pre></div><p>After creating the three UDFs above, let&rsquo;s test the previous example. Note that <code>UNSIGNED</code> should be changed to <code>BIGINT</code>.</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-SQL" data-lang="SQL"><span style="display:flex;"><span><span style="color:#66d9ef">DROP</span> <span style="color:#66d9ef">TABLE</span> <span style="color:#66d9ef">IF</span> <span style="color:#66d9ef">EXISTS</span> t;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">CREATE</span> <span style="color:#66d9ef">TABLE</span> t (i INT, j INT);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">INSERT</span> <span style="color:#66d9ef">INTO</span> t <span style="color:#66d9ef">VALUES</span> (<span style="color:#ae81ff">2</span>, <span style="color:#ae81ff">3</span>), (<span style="color:#66d9ef">NULL</span>, <span style="color:#66d9ef">NULL</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">SELECT</span> bit_xor(
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">CAST</span>(crc32(
</span></span><span style="display:flex;"><span>        concat_ws(<span style="color:#e6db74">&#39;,&#39;</span>,
</span></span><span style="display:flex;"><span>            i, j,
</span></span><span style="display:flex;"><span>            concat(<span style="color:#66d9ef">isnull</span>(i), <span style="color:#66d9ef">isnull</span>(j))
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>    ) <span style="color:#66d9ef">AS</span> BIGINT)
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">FROM</span> t;
</span></span></code></pre></div><p>The result:</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> bit_xor
</span></span><span style="display:flex;"><span>---------
</span></span><span style="display:flex;"><span> 5062371
</span></span><span style="display:flex;"><span>(1 row)
</span></span></code></pre></div><p>It&rsquo;s exactly the same as on the TiDB (MySQL) side.</p>
<h2 id="postscript">Postscript</h2>
<ol>
<li>I haven&rsquo;t tested more extensively; this is just a simple test.</li>
<li>UDFs are indeed a great feature that greatly enhance flexibility.</li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>How to Configure a Chinese LaTeX Environment on Windows</title>
      <link>https://blog.minifish.org/posts/how-to-configure-a-chinese-latex-environment-on-windows/</link>
      <pubDate>Wed, 31 Mar 2021 00:00:00 +0000</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-configure-a-chinese-latex-environment-on-windows/</guid>
      <description>Initially, I didn&amp;#39;t think this was something worth writing about, because CTex was previously working smoothly for everyone. However, it turns out that CTex hasn&amp;#39;t been updated...</description>
      <content:encoded><![CDATA[<h2 id="background">Background</h2>
<p>Initially, I didn&rsquo;t think this was something worth writing about, because CTex was previously working smoothly for everyone. However, it turns out that CTex hasn&rsquo;t been updated since 2016. So, I wanted to find a replacement for Chinese LaTeX on Windows in 2021.</p>
<h2 id="configuration-method">Configuration Method</h2>
<ol>
<li>Install MiKTeX (TexLive should work as well). MiKTeX can automatically download dependency packages and you can also proactively install the ctex package.</li>
<li>Install the VSCode LaTeX extension.</li>
<li>Configure the LaTeX extension. I found a powerful configuration on Zhihu, but it’s quite complex, so I simplified it a bit.</li>
</ol>
<p>A brief explanation: the core components here are the recipes and tools. Tools are the compilation toolchain, specifying which tools to use for compilation and the options to use, without regard to order. Recipes define how to combine the above tools to generate the final document, where the order does matter. I&rsquo;ve put XeLaTex first here because it&rsquo;s the most compatible for compiling Chinese. If you use pdflatex to compile Chinese documents, you&rsquo;re likely to encounter issues.</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-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>   <span style="color:#f92672">&#34;latex-workshop.latex.recipes&#34;</span>: [
</span></span><span style="display:flex;"><span>         {
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;XeLaTeX&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;tools&#34;</span>: [
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;xelatex&#34;</span>
</span></span><span style="display:flex;"><span>            ]
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;latexmk 🔃&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;tools&#34;</span>: [
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;latexmk&#34;</span>
</span></span><span style="display:flex;"><span>            ]
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;latexmk (latexmkrc)&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;tools&#34;</span>: [
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;latexmk_rconly&#34;</span>
</span></span><span style="display:flex;"><span>            ]
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;latexmk (lualatex)&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;tools&#34;</span>: [
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;lualatexmk&#34;</span>
</span></span><span style="display:flex;"><span>            ]
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;pdflatex ➞ bibtex ➞ pdflatex × 2&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;tools&#34;</span>: [
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;pdflatex&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;bibtex&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;pdflatex&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;pdflatex&#34;</span>
</span></span><span style="display:flex;"><span>            ]
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;Compile Rnw files&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;tools&#34;</span>: [
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;rnw2tex&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;latexmk&#34;</span>
</span></span><span style="display:flex;"><span>            ]
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;Compile Jnw files&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;tools&#34;</span>: [
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;jnw2tex&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;latexmk&#34;</span>
</span></span><span style="display:flex;"><span>            ]
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;tectonic&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;tools&#34;</span>: [
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;tectonic&#34;</span>
</span></span><span style="display:flex;"><span>            ]
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    ],
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;latex-workshop.latex.tools&#34;</span>: [
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;xelatex&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;command&#34;</span>: <span style="color:#e6db74">&#34;xelatex&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;args&#34;</span>: [
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;-synctex=1&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;-interaction=nonstopmode&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;-file-line-error&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;%DOCFILE%&#34;</span>
</span></span><span style="display:flex;"><span>            ]
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;latexmk&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;command&#34;</span>: <span style="color:#e6db74">&#34;latexmk&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;args&#34;</span>: [
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;-synctex=1&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;-interaction=nonstopmode&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;-file-line-error&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;-pdf&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;-outdir=%OUTDIR%&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;%DOC%&#34;</span>
</span></span><span style="display:flex;"><span>            ],
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;env&#34;</span>: {}
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;lualatexmk&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;command&#34;</span>: <span style="color:#e6db74">&#34;latexmk&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;args&#34;</span>: [
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;-synctex=1&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;-interaction=nonstopmode&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;-file-line-error&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;-lualatex&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;-outdir=%OUTDIR%&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;%DOC%&#34;</span>
</span></span><span style="display:flex;"><span>            ],
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;env&#34;</span>: {}
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;latexmk_rconly&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;command&#34;</span>: <span style="color:#e6db74">&#34;latexmk&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;args&#34;</span>: [
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;%DOC%&#34;</span>
</span></span><span style="display:flex;"><span>            ],
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;env&#34;</span>: {}
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;pdflatex&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;command&#34;</span>: <span style="color:#e6db74">&#34;pdflatex&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;args&#34;</span>: [
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;-synctex=1&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;-interaction=nonstopmode&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;-file-line-error&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;%DOC%&#34;</span>
</span></span><span style="display:flex;"><span>            ],
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;env&#34;</span>: {}
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;bibtex&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;command&#34;</span>: <span style="color:#e6db74">&#34;bibtex&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;args&#34;</span>: [
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;%DOCFILE%&#34;</span>
</span></span><span style="display:flex;"><span>            ],
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;env&#34;</span>: {}
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;rnw2tex&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;command&#34;</span>: <span style="color:#e6db74">&#34;Rscript&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;args&#34;</span>: [
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;-e&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;knitr::opts_knit$set(concordance = TRUE); knitr::knit(&#39;%DOCFILE_EXT%&#39;)&#34;</span>
</span></span><span style="display:flex;"><span>            ],
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;env&#34;</span>: {}
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;jnw2tex&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;command&#34;</span>: <span style="color:#e6db74">&#34;julia&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;args&#34;</span>: [
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;-e&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;using Weave; weave(\&#34;%DOC_EXT%\&#34;, doctype=\&#34;tex\&#34;)&#34;</span>
</span></span><span style="display:flex;"><span>            ],
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;env&#34;</span>: {}
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;jnw2texmintex&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;command&#34;</span>: <span style="color:#e6db74">&#34;julia&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;args&#34;</span>: [
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;-e&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;using Weave; weave(\&#34;%DOC_EXT%\&#34;, doctype=\&#34;texminted\&#34;)&#34;</span>
</span></span><span style="display:flex;"><span>            ],
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;env&#34;</span>: {}
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;tectonic&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;command&#34;</span>: <span style="color:#e6db74">&#34;tectonic&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;args&#34;</span>: [
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;--synctex&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;--keep-logs&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;%DOC%.tex&#34;</span>
</span></span><span style="display:flex;"><span>            ],
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;env&#34;</span>: {}
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    ],
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;latex-workshop.view.pdf.viewer&#34;</span>: <span style="color:#e6db74">&#34;tab&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><ol>
<li>Create a folder.</li>
<li>Use VSCode to open this folder and create a .tex file with the following content:</li>
</ol>
<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-tex" data-lang="tex"><span style="display:flex;"><span><span style="color:#66d9ef">\documentclass</span><span style="color:#a6e22e">[UTF8]</span>{ctexart}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">\begin</span>{document}
</span></span><span style="display:flex;"><span>OK, it’s all set...
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">\end</span>{document}
</span></span></code></pre></div><p>It should automatically start compiling. Confirm to download any required dependencies if prompted. The compiled effect is as follows:</p>
<p><img alt="test" loading="lazy" src="/posts/how-to-configure-a-chinese-latex-environment-on-windows/20210331113405.webp"></p>
]]></content:encoded>
    </item>
    <item>
      <title>How to Automatically Publish a Blog Using GitHub Actions</title>
      <link>https://blog.minifish.org/posts/how-to-automatically-publish-a-blog-using-github-actions/</link>
      <pubDate>Wed, 16 Dec 2020 16:11:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-automatically-publish-a-blog-using-github-actions/</guid>
      <description>In this post, I used Travis to enable automatic blog publishing. However, I recently discovered that Travis does not run automatically anymore (though it works manually). I...</description>
      <content:encoded><![CDATA[<p>In <a href="/posts/travis-git-push">this post</a>, I used Travis to enable automatic blog publishing. However, I recently discovered that Travis does not run automatically anymore (though it works manually). I haven’t looked into it closely because GitHub Actions have been introduced, so I decided to move all dependencies to GitHub.</p>
<h2 id="go-action">Go Action</h2>
<p>Click on Actions on the repository page, and then New Workflow to see recommended actions. Since this blog uses Go code, it shows the Go Action.</p>
<p><img alt="goaction" loading="lazy" src="/posts/how-to-automatically-publish-a-blog-using-github-actions/20201216163209.webp"></p>
<p>The rest involves following Travis&rsquo;s approach to set up the workflow.</p>
<ol>
<li>Check out the blog&rsquo;s repo</li>
<li>Check out the publishing site repo</li>
<li><code>make</code></li>
<li>Commit the changes to the publishing site</li>
</ol>
<p>Note that write permissions are required for the publishing site, so you need to configure a token, similar to Travis.</p>
<ol>
<li>Generate a token with only repo permissions</li>
<li>Go to a particular repo and set up secrets (enter the token). Ideally, this should be set up on the publishing site, but it works when set in the blog repo. I haven&rsquo;t explored why yet.</li>
</ol>
<p>You will need two actions in total: one is GitHub&rsquo;s own <a href="https://github.com/actions/checkout">checkout</a>, and the other is a third-party action called <a href="https://github.com/marketplace/actions/push-directory-to-another-repository">“Push directory to another repository”</a>. There might be better options available, and I’ll explore them when I have more time.</p>
<p>Finally, here is my simple GitHub Action CI file:</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-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">name</span>: <span style="color:#ae81ff">CI</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">on</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">push</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">branches</span>: [ <span style="color:#ae81ff">master ]</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">jobs</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">build</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Build</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">runs-on</span>: <span style="color:#ae81ff">ubuntu-latest</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">steps</span>:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Set up Go 1.x</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/setup-go@v2</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">with</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">go-version</span>: <span style="color:#ae81ff">^1.13</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Check out code into the Go module directory</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/checkout@v2</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Get dependencies</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">run</span>: |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        go get -v -t -d ./...
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        if [ -f Gopkg.toml ]; then
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">            curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">            dep ensure
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        fi</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Check out my other private repo</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/checkout@v2</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">with</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">repository</span>: <span style="color:#ae81ff">jackysp/jackysp.github.io</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">token</span>: <span style="color:#ae81ff">${{ secrets.UPDATE_BLOG }}</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">path</span>: <span style="color:#ae81ff">public</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Build</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">run</span>: <span style="color:#ae81ff">make</span>
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Pushes to another repository</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">id</span>: <span style="color:#ae81ff">public</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">cpina/github-action-push-to-another-repository@cp_instead_of_deleting</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">env</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">API_TOKEN_GITHUB</span>: <span style="color:#ae81ff">${{ secrets.UPDATE_BLOG }}</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">with</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">source-directory</span>: <span style="color:#e6db74">&#39;public&#39;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">destination-github-username</span>: <span style="color:#e6db74">&#39;jackysp&#39;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">destination-repository-name</span>: <span style="color:#e6db74">&#39;jackysp.github.io&#39;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">user-email</span>: <span style="color:#ae81ff">your@email.com</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">commit-message</span>: <span style="color:#ae81ff">See ORIGIN_COMMIT</span>
</span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>How to Implement a Simple Load Using Sysbench</title>
      <link>https://blog.minifish.org/posts/how-to-implement-a-simple-load-using-sysbench/</link>
      <pubDate>Mon, 14 Dec 2020 12:06:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-implement-a-simple-load-using-sysbench/</guid>
      <description>Sysbench is a tool commonly used in database testing. Since version 1.0, it has supported more powerful custom functions, allowing users to conveniently write some Lua scripts to...</description>
      <content:encoded><![CDATA[<p><a href="https://github.com/akopytov/sysbench">Sysbench</a> is a tool commonly used in database testing. Since version 1.0, it has supported more powerful custom functions, allowing users to conveniently write some Lua scripts to simulate load. The purpose of writing this article is, firstly, because I wanted to explore Sysbench&rsquo;s custom load usage. Secondly, because I tried the mysqlslap tool provided by MySQL&rsquo;s official source, and found that it freezes easily during database performance testing, which could mislead users into thinking there is an issue with the database, causing trouble for many. Therefore, I want to help people avoid these pitfalls.</p>
<h2 id="a-simple-example">A Simple Example</h2>
<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-lua" data-lang="lua"><span style="display:flex;"><span><span style="color:#75715e">#!/usr/bin/env sysbench</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>require(<span style="color:#e6db74">&#34;oltp_common&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">prepare_statements</span>()
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">end</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">event</span>()
</span></span><span style="display:flex;"><span>    con:query(<span style="color:#e6db74">&#34;set autocommit = 1&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">end</span>
</span></span></code></pre></div><p>The first line <code>require</code> includes Sysbench&rsquo;s built-in basic library; the empty <code>prepare_statement</code> is a callback function from <code>oltp_common</code> that must be present; the specific execution of a single load is implemented in the <code>event</code> function.</p>
<p>Save this script as a Lua file, for example, named set.lua, and then execute it using sysbench.</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-shell" data-lang="shell"><span style="display:flex;"><span>sysbench --config-file<span style="color:#f92672">=</span>config --threads<span style="color:#f92672">=</span><span style="color:#ae81ff">100</span> set.lua --tables<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span> --table_size<span style="color:#f92672">=</span><span style="color:#ae81ff">1000000</span> run
</span></span></code></pre></div><p>You can use the above command. Of course, here <code>--tables=1</code> and <code>--table_size=1000000</code> are not useful for this load, so they are optional. <code>--threads</code> controls concurrency.</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-shell" data-lang="shell"><span style="display:flex;"><span>$ cat config
</span></span><span style="display:flex;"><span>time<span style="color:#f92672">=</span><span style="color:#ae81ff">120</span>
</span></span><span style="display:flex;"><span>db-driver<span style="color:#f92672">=</span>mysql
</span></span><span style="display:flex;"><span>mysql-host<span style="color:#f92672">=</span>172.16.5.33
</span></span><span style="display:flex;"><span>mysql-port<span style="color:#f92672">=</span><span style="color:#ae81ff">34000</span>
</span></span><span style="display:flex;"><span>mysql-user<span style="color:#f92672">=</span>root
</span></span><span style="display:flex;"><span>mysql-db<span style="color:#f92672">=</span>sbtest
</span></span><span style="display:flex;"><span>report-interval<span style="color:#f92672">=</span><span style="color:#ae81ff">10</span>
</span></span></code></pre></div><p>In the config file, parameters you don&rsquo;t frequently adjust are written once to avoid having a long string of parameters in the command line. These are required fields: <code>time</code> represents the test duration, <code>report-interval</code> is used to observe real-time performance results, and the others pertain to how to connect to the database.</p>
<p>The running output generally looks like:</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>[ 10s ] thds: 100 tps: 94574.34 qps: 94574.34 (r/w/o: 0.00/0.00/94574.34) lat (ms,95%): 3.68 err/s: 0.00 reconn/s: 0.00
</span></span><span style="display:flex;"><span>[ 20s ] thds: 100 tps: 77720.30 qps: 77720.30 (r/w/o: 0.00/0.00/77720.30) lat (ms,95%): 5.28 err/s: 0.00 reconn/s: 0.00
</span></span><span style="display:flex;"><span>[ 30s ] thds: 100 tps: 56080.10 qps: 56080.10 (r/w/o: 0.00/0.00/56080.10) lat (ms,95%): 9.22 err/s: 0.00 reconn/s: 0.00
</span></span><span style="display:flex;"><span>[ 40s ] thds: 100 tps: 93315.90 qps: 93315.90 (r/w/o: 0.00/0.00/93315.90) lat (ms,95%): 4.82 err/s: 0.00 reconn/s: 0.00
</span></span><span style="display:flex;"><span>[ 50s ] thds: 100 tps: 97491.02 qps: 97491.02 (r/w/o: 0.00/0.00/97491.02) lat (ms,95%): 4.65 err/s: 0.00 reconn/s: 0.00
</span></span><span style="display:flex;"><span>[ 60s ] thds: 100 tps: 94034.27 qps: 94034.27 (r/w/o: 0.00/0.00/94034.27) lat (ms,95%): 4.91 err/s: 0.00 reconn/s: 0.00
</span></span><span style="display:flex;"><span>[ 70s ] thds: 100 tps: 74707.37 qps: 74707.37 (r/w/o: 0.00/0.00/74707.37) lat (ms,95%): 6.79 err/s: 0.00 reconn/s: 0.00
</span></span><span style="display:flex;"><span>[ 80s ] thds: 100 tps: 89485.10 qps: 89485.10 (r/w/o: 0.00/0.00/89485.10) lat (ms,95%): 5.18 err/s: 0.00 reconn/s: 0.00
</span></span><span style="display:flex;"><span>[ 90s ] thds: 100 tps: 109296.44 qps: 109296.44 (r/w/o: 0.00/0.00/109296.44) lat (ms,95%): 2.91 err/s: 0.00 reconn/s: 0.00
</span></span></code></pre></div><p>Finally, there will be a summary report.</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>SQL statistics:
</span></span><span style="display:flex;"><span>    queries performed:
</span></span><span style="display:flex;"><span>        read:                            0
</span></span><span style="display:flex;"><span>        write:                           0
</span></span><span style="display:flex;"><span>        other:                           10424012
</span></span><span style="display:flex;"><span>        total:                           10424012
</span></span><span style="display:flex;"><span>    transactions:                        10424012 (86855.65 per sec.)
</span></span><span style="display:flex;"><span>    queries:                             10424012 (86855.65 per sec.)
</span></span><span style="display:flex;"><span>    ignored errors:                      0      (0.00 per sec.)
</span></span><span style="display:flex;"><span>    reconnects:                          0      (0.00 per sec.)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Throughput:
</span></span><span style="display:flex;"><span>    events/s (eps):                      86855.6517
</span></span><span style="display:flex;"><span>    time elapsed:                        120.0154s
</span></span><span style="display:flex;"><span>    total number of events:              10424012
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Latency (ms):
</span></span><span style="display:flex;"><span>         min:                                    0.09
</span></span><span style="display:flex;"><span>         avg:                                    1.15
</span></span><span style="display:flex;"><span>         max:                                 1527.74
</span></span><span style="display:flex;"><span>         95th percentile:                        4.91
</span></span><span style="display:flex;"><span>         sum:                             11994122.49
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Threads fairness:
</span></span><span style="display:flex;"><span>    events (avg/stddev):           104240.1200/600.21
</span></span><span style="display:flex;"><span>    execution time (avg/stddev):   119.9412/0.01
</span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>How to Read TiDB Source Code (Part 5)</title>
      <link>https://blog.minifish.org/posts/how-to-read-tidb-source-code-part-5/</link>
      <pubDate>Tue, 08 Sep 2020 11:36:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-read-tidb-source-code-part-5/</guid>
      <description>When using TiDB, you may occasionally encounter some exceptions, such as the &amp;#34;Lost connection to MySQL server during query&amp;#34; error. This indicates that the connection between the...</description>
      <content:encoded><![CDATA[<p>When using TiDB, you may occasionally encounter some exceptions, such as the &ldquo;Lost connection to MySQL server during query&rdquo; error. This indicates that the connection between the client and the database has been disconnected (not due to user action). The reasons for disconnection can vary. This article attempts to analyze some common TiDB errors from the perspective of exception handling and code analysis. Additionally, some exceptions are not errors but performance issues due to slow execution. In the second half of this article, we will also introduce common tools for tracking performance.</p>
<h2 id="lost-connection">Lost Connection</h2>
<p>There are generally three reasons for a Lost Connection:</p>
<ol>
<li>A timeout occurs either directly between the client and the database or at some point along the intermediate link, such as from the client to the Proxy or from the Proxy to the database.</li>
<li>A bug occurs during SQL execution, which can generally be recovered, thus preventing the TiDB server from crashing completely (panic).</li>
<li>TiDB itself crashes, often due to excessive memory use, causing an OOM (Out of Memory), or a user deliberately kills TiDB. Another possibility is an unrecovered bug, which typically appears more frequently in background threads.</li>
</ol>
<h3 id="timeout">Timeout</h3>
<h4 id="direct-timeout">Direct Timeout</h4>
<p>TiDB supports the MySQL-compatible <code>wait_timeout</code> variable, with a default value of 0, meaning no timeout is set, unlike MySQL&rsquo;s default of 8 hours.</p>
<p><img alt="lost" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-5/20200908132926.webp"></p>
<p>The only place it is used in the code is in <code>getSessionVarsWaitTimeout</code>. In the connection&rsquo;s Run section, its value is set for packet IO. If the variable is non-zero, a timeout is set before each <code>readPacket</code>.</p>
<p><img alt="lost" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-5/20200908134545.webp"></p>
<p>If the client does not send data beyond the specified time, the connection will be disconnected. At this time, a log message &ldquo;read packet timeout, close this connection&rdquo; will appear, along with the specific timeout duration.</p>
<h4 id="intermediate-link-timeout">Intermediate Link Timeout</h4>
<p>Another scenario is an intermediate link timeout. A normal timeout in an intermediate link (proxy) typically returns an EOF error to the database. In older versions, at least a connection closed log would be output.</p>
<p><img alt="lost" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-5/20200908141322.webp"></p>
<p>In the newer master version, product managers suggested changing this log to a debug level, so it is generally no longer output.</p>
<p>However, in the new version, a monitoring item called <code>DisconnectionCounter</code> has been added,</p>
<p><img alt="lost" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-5/20200908141537.webp"></p>
<p><img alt="lost" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-5/20200908142131.webp"></p>
<p>which records normal and abnormal disconnections as a supplement to downgraded logging.</p>
<h3 id="bugs-that-are-recovered">Bugs that Are Recovered</h3>
<p>TiDB &ldquo;basically&rdquo; can recover from panics caused by unknown bugs. However, if there is an array out-of-bounds, a null pointer reference, or intentional panic, it cannot guarantee correct results for the current and subsequent SQL, so terminating the current connection is a wise choice.</p>
<p><img alt="lost" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-5/20200908143849.webp"></p>
<p>At this time, an error log &ldquo;connection running loop panic&rdquo; will appear, along with a <code>lastSQL</code> field that outputs the current erroneous SQL.</p>
<h3 id="panic-not-recovered">Panic Not Recovered</h3>
<p>Whether it&rsquo;s an unrecovered panic or a system-level OOM-induced panic, they do not leave a log in TiDB&rsquo;s logs. TiDB clusters managed by deployment tools like Ansible or TiUP will automatically restart a crashed TiDB server. Consequently, the log will contain a new &ldquo;Welcome&rdquo; message, which might be overlooked. However, the Uptime in monitoring will show TiDB&rsquo;s Uptime reset to zero, making this issue relatively easy to detect. Of course, it&rsquo;s better to have accompanying alerts.</p>
<p>Unrecovered panic outputs are Golang&rsquo;s default outputs, usually redirected to <code>tidb_stderr.log</code> by deployment tools. Older versions of Ansible overwrite this file every restart, but now use an append mode.</p>
<p><img alt="lost" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-5/15992142135768.webp"></p>
<p>Nevertheless, it has some other drawbacks, like lacking timestamps. This makes it difficult to timestamp-match with TiDB logs. This <a href="https://github.com/pingcap/tidb/pull/18310">PR</a> implemented distinguishing <code>tidb_stderr.log</code> based on PID but hasn&rsquo;t been coordinated with the deployment tools and is temporarily disabled.</p>
<p>To get this standard panic output, you can use the panicparse introduced in the previous article to parse the panic results. Typically, you can look at the topmost stack. The example in the image evidently shows an out-of-memory error, commonly referred to as OOM. To identify which SQL caused the OOM, check TiDB&rsquo;s logs for resource-heavy SQL, which are usually logged with the <code>expensive_query</code> tag, and can be checked by grepping the logs. This will not be exemplified here.</p>
<h2 id="tracing">Tracing</h2>
<p>TiDB has supported tracing since version 2.1, but it hasn&rsquo;t been widely used. I think there are two main reasons:</p>
<ol>
<li>
<p>The initial version of tracing only supported the JSON format, requiring the output to be copied and pasted into a TiDB-specific web page at a special host port to view it. Although novel, the multiple steps involved prevented widespread adoption.</p>
<p><img alt="lost" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-5/trace-view.webp"></p>
</li>
<li>
<p>Another issue is that tracing provides insight only after a problem is known. If developers suspect a problem or slow execution in advance, they must proactively add events at those points. Often, unforeseen issues cannot be covered, leaving gaps.</p>
</li>
</ol>
<p>Once the framework of tracing is in place, adding events is relatively straightforward and involves adding code like the snippet below at the desired points:</p>
<p><img alt="lost" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-5/20200908165442.webp"></p>
<p>Interested individuals can add events to TiDB as needed, offering a good hands-on experience.</p>
<p>Eventually, tracing added <code>format='row'</code> and <code>format='log'</code> features. I personally favor <code>format='log'</code>.</p>
<h3 id="difference-between-tracing-and-explain-analyze">Difference between Tracing and Explain (Analyze)</h3>
<ol>
<li>Tracing operates at the function level, while Explain operates at the operator level. Tracing is easier to add and more granular and does not need to be part of a plan.</li>
<li>Tracing can trace any SQL, while Explain only shows data reading parts. For example, with an Insert, Explain shows almost nothing, whereas tracing provides detailed insights from SQL parsing to the full transaction commit.</li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>How to Read TiDB Source Code (Part 4)</title>
      <link>https://blog.minifish.org/posts/how-to-read-tidb-source-code-part-4/</link>
      <pubDate>Fri, 31 Jul 2020 10:58:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-read-tidb-source-code-part-4/</guid>
      <description>This article will introduce some key functions and the interpretation of logs in TiDB.</description>
      <content:encoded><![CDATA[<p>This article will introduce some key functions and the interpretation of logs in TiDB.</p>
<h2 id="key-functions">Key Functions</h2>
<p>The definition of key functions varies from person to person, so the content of this section is subjective.</p>
<h3 id="execute">execute</h3>
<p><img alt="func" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-4/20200812152326.webp"></p>
<p>The <code>execute</code> function is the necessary pathway for text protocol execution. It also nicely demonstrates the various processes of SQL handling.</p>
<ol>
<li>
<p>ParseSQL analyzes the SQL. The final implementation is in the parser, where SQL is parsed according to the rules introduced in the second article. Note that the parsed SQL may be a single statement or multiple statements. TiDB itself supports the multi-SQL feature, allowing multiple SQL statements to be executed at once.</p>
</li>
<li>
<p>After parsing, a <code>stmtNodes</code> array is returned, which is processed one-by-one in the for loop below. The first step is to compile, where the core of compile is optimization, generating a plan. By following the <code>Optimize</code> function, you can find logic similar to logical and physical optimization found in other common databases.</p>
<p><img alt="func" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-4/20200812153017.webp"></p>
</li>
<li>
<p>The last part is execution, where <code>executeStatement</code> and particularly the <code>runStmt</code> function are key functions.</p>
</li>
</ol>
<h3 id="runstmt">runStmt</h3>
<p>Judging from the call graph of <code>runStmt</code>, this function is almost the mandatory pathway for all SQL execution. Except for point query statements using the binary protocol with automatic commit, all other statements go through this function. This function is responsible for executing SQL, excluding SQL parsing and compilation (the binary protocol does not need repeated SQL parsing, nor does SQL compilation require plan caching).</p>
<p><img alt="func" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-4/20200731112400.webp"></p>
<p>The core part of the <code>runStmt</code> function is as shown above. From top to bottom:</p>
<ol>
<li>
<p>checkTxnAborted</p>
<p>When a transaction is already corrupted and cannot be committed, the user must actively close the transaction to end the already corrupted transaction. During execution, transactions may encounter errors that cannot be handled and must be terminated. The transaction cannot be silently closed because the user may continue to execute SQL and assume it is still within the transaction. This function ensures that all subsequent SQL commands by the user are not executed and directly return an error until the user uses rollback or commit to explicitly close the transaction for normal execution.</p>
</li>
<li>
<p>Exec</p>
<p>Execute the SQL and return the result set (rs).</p>
</li>
<li>
<p>IsReadOnly</p>
<p>After executing a SQL, it&rsquo;s necessary to determine whether it is a read-only SQL. If it is not read-only, it must be temporarily stored in the transaction&rsquo;s execution history. This execution history is used when a transaction conflict or other errors require the transaction to be retried. Read-only SQL is bypassed because the retry of the transaction is done during the commit phase, and at this point, the only feedback to the client can be success or failure of the commit; reading results is meaningless.</p>
<p>This section also includes <code>StmtCommit</code> and <code>StmtRollback</code>. TiDB supports MySQL-like statement commits and rollbacks—if a statement fails during a transaction, that single statement will be atomically rolled back, while other successfully executed statements will eventually commit with the transaction.</p>
<p>In TiDB, the feature of statement commit is implemented with a two-layer buffer: both the transaction and the statement have their own buffers. After a statement executes successfully, the statement’s buffer is merged into the transaction buffer. If a statement fails, the statement’s buffer is discarded, thus ensuring the atomicity of statement commits. Of course, a statement commit may fail, in which case the entire transaction buffer becomes unusable, and the transaction can only be rolled back.</p>
</li>
<li>
<p>finishStmt</p>
<p>Once a statement is executed, should it be committed? This depends on whether the transaction was explicitly started (i.e., with <code>begin</code> or <code>start transaction</code>) and whether autocommit is enabled. The role of <code>finishStmt</code> is to, post-execution, check if it should be committed based on the above conditions. It&rsquo;s essentially for cleaning up and checking after each statement execution.</p>
</li>
<li>
<p>pending section</p>
<p>Some SQLs in TiDB do not require a transaction (e.g., the <code>set</code> statement). However, before parsing, the database doesn’t know whether the statement requires a transaction. The latency of starting a transaction in TiDB is relatively high because it requires obtaining a TSO (timestamp oracle) from PD. TiDB has an optimization to asynchronously obtain a TSO, meaning a TSO is prepared regardless of whether a transaction is eventually needed. If a statement indeed doesn’t require a TSO and a transaction is not activated, remaining in a pending status, the pending transaction must be closed.</p>
</li>
</ol>
<h2 id="logs">Logs</h2>
<p>Let&rsquo;s first look at a section of logs from TiDB at initial startup, divided into several parts:</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>[2020/08/12 16:12:07.282 +08:00] [INFO] [printer.go:42] [&#34;Welcome to TiDB.&#34;] [&#34;Release Version&#34;=None] [Edition=None] [&#34;Git Commit Hash&#34;=None] [&#34;Git Branch&#34;=None] [&#34;UTC Build Time&#34;=None] [GoVersion=go1.15] [&#34;Race Enabled&#34;=false] [&#34;Check Table Before Drop&#34;=false] [&#34;TiKV Min Version&#34;=v3.0.0-60965b006877ca7234adaced7890d7b029ed1306]
</span></span><span style="display:flex;"><span>[2020/08/12 16:12:07.300 +08:00] [INFO] [printer.go:56] [&#34;loaded config&#34;] [config=&#34;{\&#34;host\&#34;:\&#34;0.0.0.0\&#34;,\&#34;advertise-address\&#34;:\&#34;0.0.0.0\&#34;,\&#34;port\&#34;:4000,\&#34;cors\&#34;:\&#34;\&#34;,\&#34;store\&#34;:\&#34;mocktikv\&#34;,\&#34;path\&#34;:\&#34;/tmp/tidb\&#34;,\&#34;socket\&#34;:\&#34;\&#34;,\&#34;lease\&#34;:\&#34;45s\&#34;,\&#34;run-ddl\&#34;:true,\&#34;split-table\&#34;:true,\&#34;token-limit\&#34;:1000,\&#34;oom-use-tmp-storage\&#34;:true,\&#34;tmp-storage-path\&#34;:\&#34;C:\\\\Users\\\\username\\\\AppData\\\\Local\\\\Temp\\\\tidb\\\\tmp-storage\&#34;,\&#34;oom-action\&#34;:\&#34;log\&#34;,\&#34;mem-quota-query\&#34;:1073741824,\&#34;tmp-storage-quota\&#34;:-1,\&#34;enable-streaming\&#34;:false,\&#34;enable-batch-dml\&#34;:false,\&#34;lower-case-table-names\&#34;:2,\&#34;server-version\&#34;:\&#34;\&#34;,\&#34;log\&#34;:{\&#34;level\&#34;:\&#34;info\&#34;,\&#34;format\&#34;:\&#34;text\&#34;,\&#34;disable-timestamp\&#34;:null,\&#34;enable-timestamp\&#34;:null,\&#34;disable-error-stack\&#34;:null,\&#34;enable-error-stack\&#34;:null,\&#34;file\&#34;:{\&#34;filename\&#34;:\&#34;\&#34;,\&#34;max-size\&#34;:300,\&#34;max-days\&#34;:0,\&#34;max-backups\&#34;:0},\&#34;enable-slow-log\&#34;:true,\&#34;slow-query-file\&#34;:\&#34;tidb-slow.log\&#34;,\&#34;slow-threshold\&#34;:300,\&#34;expensive-threshold\&#34;:10000,\&#34;query-log-max-len\&#34;:4096,\&#34;record-plan-in-slow-log\&#34;:1},\&#34;security\&#34;:{\&#34;skip-grant-table\&#34;:false,\&#34;ssl-ca\&#34;:\&#34;\&#34;,\&#34;ssl-cert\&#34;:\&#34;\&#34;,\&#34;ssl-key\&#34;:\&#34;\&#34;,\&#34;require-secure-transport\&#34;:false,\&#34;cluster-ssl-ca\&#34;:\&#34;\&#34;,\&#34;cluster-ssl-cert\&#34;:\&#34;\&#34;,\&#34;cluster-ssl-key\&#34;:\&#34;\&#34;,\&#34;cluster-verify-cn\&#34;:null},\&#34;status\&#34;:{\&#34;status-host\&#34;:\&#34;0.0.0.0\&#34;,\&#34;metrics-addr\&#34;:\&#34;\&#34;,\&#34;status-port\&#34;:10080,\&#34;metrics-interval\&#34;:15,\&#34;report-status\&#34;:true,\&#34;record-db-qps\&#34;:false},\&#34;performance\&#34;:{\&#34;max-procs\&#34;:0,\&#34;max-memory\&#34;:0,\&#34;stats-lease\&#34;:\&#34;3s\&#34;,\&#34;stmt-count-limit\&#34;:5000,\&#34;feedback-probability\&#34;:0.05,\&#34;query-feedback-limit\&#34;:1024,\&#34;pseudo-estimate-ratio\&#34;:0.8,\&#34;force-priority\&#34;:\&#34;NO_PRIORITY\&#34;,\&#34;bind-info-lease\&#34;:\&#34;3s\&#34;,\&#34;txn-total-size-limit\&#34;:104857600,\&#34;tcp-keep-alive\&#34;:true,\&#34;cross-join\&#34;:true,\&#34;run-auto-analyze\&#34;:true,\&#34;agg-push-down-join\&#34;:false,\&#34;committer-concurrency\&#34;:16,\&#34;max-txn-ttl\&#34;:600000},\&#34;prepared-plan-cache\&#34;:{\&#34;enabled\&#34;:false,\&#34;capacity\&#34;:100,\&#34;memory-guard-ratio\&#34;:0.1},\&#34;opentracing\&#34;:{\&#34;enable\&#34;:false,\&#34;rpc-metrics\&#34;:false,\&#34;sampler\&#34;:{\&#34;type\&#34;:\&#34;const\&#34;,\&#34;param\&#34;:1,\&#34;sampling-server-url\&#34;:\&#34;\&#34;,\&#34;max-operations\&#34;:0,\&#34;sampling-refresh-interval\&#34;:0},\&#34;reporter\&#34;:{\&#34;queue-size\&#34;:0,\&#34;buffer-flush-interval\&#34;:0,\&#34;log-spans\&#34;:false,\&#34;local-agent-host-port\&#34;:\&#34;\&#34;}},\&#34;proxy-protocol\&#34;:{\&#34;networks\&#34;:\&#34;\&#34;,\&#34;header-timeout\&#34;:5},\&#34;tikv-client\&#34;:{\&#34;grpc-connection-count\&#34;:4,\&#34;grpc-keepalive-time\&#34;:10,\&#34;grpc-keepalive-timeout\&#34;:3,\&#34;commit-timeout\&#34;:\&#34;41s\&#34;,\&#34;max-batch-size\&#34;:128,\&#34;overload-threshold\&#34;:200,\&#34;max-batch-wait-time\&#34;:0,\&#34;batch-wait-size\&#34;:8,\&#34;enable-chunk-rpc\&#34;:true,\&#34;region-cache-ttl\&#34;:600,\&#34;store-limit\&#34;:0,\&#34;store-liveness-timeout\&#34;:\&#34;120s\&#34;,\&#34;copr-cache\&#34;:{\&#34;enable\&#34;:false,\&#34;capacity-mb\&#34;:1000,\&#34;admission-max-result-mb\&#34;:10,\&#34;admission-min-process-ms\&#34;:5}},\&#34;binlog\&#34;:{\&#34;enable\&#34;:false,\&#34;ignore-error\&#34;:false,\&#34;write-timeout\&#34;:\&#34;15s\&#34;,\&#34;binlog-socket\&#34;:\&#34;\&#34;,\&#34;strategy\&#34;:\&#34;range\&#34;},\&#34;compatible-kill-query\&#34;:false,\&#34;plugin\&#34;:{\&#34;dir\&#34;:\&#34;\&#34;,\&#34;load\&#34;:\&#34;\&#34;},\&#34;pessimistic-txn\&#34;:{\&#34;enable\&#34;:true,\&#34;max-retry-count\&#34;:256},\&#34;check-mb4-value-in-utf8\&#34;:true,\&#34;max-index-length\&#34;:3072,\&#34;alter-primary-key\&#34;:false,\&#34;treat-old-version-utf8-as-utf8mb4\&#34;:true,\&#34;enable-table-lock\&#34;:false,\&#34;delay-clean-table-lock\&#34;:0,\&#34;split-region-max-num\&#34;:1000,\&#34;stmt-summary\&#34;:{\&#34;enable\&#34;:true,\&#34;enable-internal-query\&#34;:false,\&#34;max-stmt-count\&#34;:200,\&#34;max-sql-length\&#34;:4096,\&#34;refresh-interval\&#34;:1800,\&#34;history-size\&#34;:24},\&#34;repair-mode\&#34;:false,\&#34;repair-table-list\&#34;:[],\&#34;isolation-read\&#34;:{\&#34;engines\&#34;:[\&#34;tikv\&#34;,\&#34;tiflash\&#34;,\&#34;tidb\&#34;]},\&#34;max-server-connections\&#34;:0,\&#34;new_collations_enabled_on_first_bootstrap\&#34;:false,\&#34;experimental\&#34;:{\&#34;allow-auto-random\&#34;:false,\&#34;allow-expression-index\&#34;:false}}&#34;]
</span></span></code></pre></div><ol>
<li>Mandatory startup outputs: &ldquo;Welcome to TiDB,&rdquo; git hash, Golang version, etc.</li>
<li>Actually loaded configuration (this section is somewhat difficult to read)</li>
</ol>
<p>The remainder are some routine startup logs. The process can be referenced from the main function section introduced in the first article, mainly outputting the initial system table creation process.</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>[2020/08/12 16:12:07.300 +08:00] [INFO] [main.go:341] [&#34;disable Prometheus push client&#34;]
</span></span><span style="display:flex;"><span>[2020/08/12 16:12:07.300 +08:00] [INFO] [store.go:68] [&#34;new store&#34;] [path=mocktikv:///tmp/tidb]
</span></span><span style="display:flex;"><span>[2020/08/12 16:12:07.300 +08:00] [INFO] [systime_mon.go:25] [&#34;start system time monitor&#34;]
</span></span><span style="display:flex;"><span>[2020/08/12 16:12:07.310 +08:00] [INFO] [store.go:74] [&#34;new store with retry success&#34;]
</span></span><span style="display:flex;"><span>[2020/08/12 16:12:07.310 +08:00] [INFO] [tidb.go:71] [&#34;new domain&#34;] [store=8d19232e-a273-4e31-ba9b-a3467998345c] [&#34;ddl lease&#34;=45s] [&#34;stats lease&#34;=3s]
</span></span><span style="display:flex;"><span>[2020/08/12 16:12:07.315 +08:00] [INFO] [ddl.go:321] [&#34;[ddl] start DDL&#34;] [ID=0e1bd28e-03ed-4900-bf71-f58b3b9d954a] [runWorker=true]
</span></span><span style="display:flex;"><span>[2020/08/12 16:12:07.315 +08:00] [INFO] [ddl.go:309] [&#34;[ddl] start delRangeManager OK&#34;] [&#34;is a emulator&#34;=true]
</span></span><span style="display:flex;"><span>[2020/08/12 16:12:07.315 +08:00] [INFO] [ddl_worker.go:130] [&#34;[ddl] start DDL worker&#34;] [worker=&#34;worker 1, tp general&#34;]
</span></span><span style="display:flex;"><span>[2020/08/12 16:12:07.315 +08:00] [INFO] [ddl_worker.go:130] [&#34;[ddl] start DDL worker&#34;] [worker=&#34;worker 2, tp add index&#34;]
</span></span><span style="display:flex;"><span>[2020/08/12 16:12:07.315 +08:00] [INFO] [delete_range.go:133] [&#34;[ddl] start delRange emulator&#34;]
</span></span><span style="display:flex;"><span>[2020/08/12 16:12:07.317 +08:00] [INFO] [domain.go:144] [&#34;full load InfoSchema success&#34;] [usedSchemaVersion=0] [neededSchemaVersion=0] [&#34;start time&#34;=2.0015ms]
</span></span><span style="display:flex;"><span>[2020/08/12 16:12:07.317 +08:00] [INFO] [domain.go:368] [&#34;full load and reset schema validator&#34;]
</span></span><span style="display:flex;"><span>[2020/08/12 16:12:07.317 +08:00] [INFO] [tidb.go:199] [&#34;rollbackTxn for ddl/autocommit failed&#34;]
</span></span></code></pre></div><p>Because DDL logs are very numerous, the TiDB logs basically record each step of the DDL execution, so I&rsquo;ve truncated this part of the log here. However, the basic outline can be sorted out. Firstly, the DDL execution is initiated from ddl_api, at this time recording <code>[&quot;CRUCIAL OPERATION&quot;]</code> style logs. DDL is a crucial operation, so it belongs to CRUCIAL type logs. Then, we can see a series of logs with the ddl keyword linked together, such as <code>[ddl] add DDL jobs</code>, <code>[ddl] start DDL job</code>, <code>[ddl] run DDL job</code>, <code>[ddl] finish DDL job</code>, and <code>[ddl] DDL job is finished</code>. These represent the process from when the DDL owner acquires a job to its final execution completion. Moreover, they have a unique job ID, which can be used to link a DDL in the log with something like <code>jobs=&quot;ID:2</code>.</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>[2020/08/12 16:12:07.518 +08:00] [INFO] [server.go:235] [&#34;server is running MySQL protocol&#34;] [addr=0.0.0.0:4000]
</span></span><span style="display:flex;"><span>[2020/08/12 16:12:07.518 +08:00] [INFO] [http_status.go:80] [&#34;for status and metrics report&#34;] [&#34;listening on addr&#34;=0.0.0.0:10080]
</span></span><span style="display:flex;"><span>[2020/08/12 16:12:07.520 +08:00] [INFO] [domain.go:1015] [&#34;init stats info time&#34;] [&#34;take time&#34;=3.0126ms]
</span></span><span style="display:flex;"><span>[2020/08/12 16:15:41.482 +08:00] [INFO] [server.go:388] [&#34;new connection&#34;] [conn=1] [remoteAddr=127.0.0.1:64888]
</span></span><span style="display:flex;"><span>[2020/08/12 21:03:19.954 +08:00] [INFO] [server.go:391] [&#34;connection closed&#34;] [conn=1]
</span></span></code></pre></div><p>Thereafter, the appearance of <code>server is running MySQL protocol</code> means that TiDB can provide services externally. Later, there are logs corresponding to the creation and closing of each connection, namely <code>new connection</code> and <code>connection closed</code>. Of course, they also have their corresponding connection ID, which is unique for a TiDB. You can use the keyword <code>conn=1</code> in the log to contextually link them together.</p>
<h3 id="stack-logs">Stack Logs</h3>
<p>Most of TiDB&rsquo;s SQL errors (except for duplicate entry and syntax errors) will output the complete stack information. Due to the requirements of unified log format, the stack now looks very unsightly&hellip;</p>
<p>For this stack trace, I believe no one really enjoys reading it. Therefore, we need to paste it into Vim and execute <code>%s/\\n/\r/g</code> and <code>%s/\\t/    /g</code> to turn it into a Golang-style stack.</p>
<p>When you see which module it&rsquo;s stuck in, like the plan part here, you can find the corresponding colleague for support.</p>
<p>However, there is a more user-friendly tool for dealing with Golang’s lengthy stack called <a href="https://github.com/maruel/panicparse">panicparse</a>. To install it, simply run
<code>go get github.com/maruel/panicparse/v2/cmd/pp</code>. The effect is as follows:</p>
<p><img alt="func" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-4/20200813172149.webp"></p>
<p>Whether it&rsquo;s TiDB running goroutines or panic outputs, it can be parsed using this. It has several features:</p>
<ol>
<li>It can display active and inactive goroutines.</li>
<li>It can show the relationships between goroutines.</li>
<li>Keyword highlighting.</li>
<li>Supports Windows.</li>
</ol>
<p>The latest 2.0.0 version supports race detector and HTML formatted output.</p>
<p>This concludes the introduction to the analysis of key functions and logs (startup, DDL, connection, error stack).</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to Read TiDB Source Code (Part 3)</title>
      <link>https://blog.minifish.org/posts/how-to-read-tidb-source-code-part-3/</link>
      <pubDate>Tue, 28 Jul 2020 11:47:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-read-tidb-source-code-part-3/</guid>
      <description>In the previous article, we introduced methods for viewing syntax and configurations. In this article, we will discuss how to view system variables, including default values,...</description>
      <content:encoded><![CDATA[<p>In the previous article, we introduced methods for viewing syntax and configurations. In this article, we will discuss how to view system variables, including default values, scopes, and how to monitor metrics.</p>
<h2 id="system-variables">System Variables</h2>
<p>The system variable names in TiDB are defined in <a href="https://github.com/pingcap/tidb/blob/db0310b17901b1a59f7f728294455ed9667f88ac/sessionctx/variable/tidb_vars.go">tidb_vars.go</a>. This file also includes some default values for variables, but the place where they are actually assembled is <a href="https://github.com/pingcap/tidb/blob/12aac547a9068c404ad18093ae4d0ea4d060a465/sessionctx/variable/sysvar.go#L96">defaultSysVars</a>.</p>
<p><img alt="defaultSysVars" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-3/20200728151254.webp"></p>
<p>This large struct array defines the scope, variable names, and default values for all variables in TiDB. Besides TiDB&rsquo;s own system variables, it also includes compatibility with MySQL&rsquo;s system variables.</p>
<h3 id="scope">Scope</h3>
<p>In TiDB, there are three types of variable scopes literally:</p>
<p><img alt="defaultSysVars" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-3/20200728151833.webp"></p>
<p>They are ScopeNone, ScopeGlobal, and ScopeSession. They represent:</p>
<ul>
<li>ScopeNone: Read-only variables</li>
<li>ScopeGlobal: Global variables</li>
<li>ScopeSession: Session variables</li>
</ul>
<p>The actual effect of these scopes is that when you use SQL to read or write them, you need to use the corresponding syntax. If the SQL fails, the SQL operation does not take effect. If the SQL executes successfully, it merely means the setting is complete, but it does not mean that it takes effect according to the corresponding scope.</p>
<p>Let&rsquo;s use the method mentioned in the first article to start a single-node TiDB for demonstration:</p>
<h4 id="scopenone">ScopeNone</h4>
<p>Take <code>performance_schema_max_mutex_classes</code> as an example,</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-sql" data-lang="sql"><span style="display:flex;"><span>MySQL  <span style="color:#ae81ff">127</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">1</span>:<span style="color:#ae81ff">4000</span>  <span style="color:#66d9ef">SQL</span> <span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">select</span> <span style="color:#f92672">@@</span>performance_schema_max_mutex_classes;
</span></span><span style="display:flex;"><span><span style="color:#f92672">+</span><span style="color:#75715e">----------------------------------------+
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">|</span> <span style="color:#f92672">@@</span>performance_schema_max_mutex_classes <span style="color:#f92672">|</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">+</span><span style="color:#75715e">----------------------------------------+
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">|</span> <span style="color:#ae81ff">200</span>                                    <span style="color:#f92672">|</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">+</span><span style="color:#75715e">----------------------------------------+
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff">1</span> <span style="color:#66d9ef">row</span> <span style="color:#66d9ef">in</span> <span style="color:#66d9ef">set</span> (<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0002</span> sec)
</span></span><span style="display:flex;"><span>MySQL  <span style="color:#ae81ff">127</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">1</span>:<span style="color:#ae81ff">4000</span>  <span style="color:#66d9ef">SQL</span> <span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">select</span> <span style="color:#f92672">@@</span><span style="color:#66d9ef">global</span>.performance_schema_max_mutex_classes;
</span></span><span style="display:flex;"><span><span style="color:#f92672">+</span><span style="color:#75715e">-----------------------------------------------+
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">|</span> <span style="color:#f92672">@@</span><span style="color:#66d9ef">global</span>.performance_schema_max_mutex_classes <span style="color:#f92672">|</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">+</span><span style="color:#75715e">-----------------------------------------------+
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">|</span> <span style="color:#ae81ff">200</span>                                           <span style="color:#f92672">|</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">+</span><span style="color:#75715e">-----------------------------------------------+
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff">1</span> <span style="color:#66d9ef">row</span> <span style="color:#66d9ef">in</span> <span style="color:#66d9ef">set</span> (<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0004</span> sec)
</span></span><span style="display:flex;"><span>MySQL  <span style="color:#ae81ff">127</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">1</span>:<span style="color:#ae81ff">4000</span>  <span style="color:#66d9ef">SQL</span> <span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">select</span> <span style="color:#f92672">@@</span><span style="color:#66d9ef">session</span>.performance_schema_max_mutex_classes;
</span></span><span style="display:flex;"><span>ERROR: <span style="color:#ae81ff">1238</span> (HY000): <span style="color:#66d9ef">Variable</span> <span style="color:#e6db74">&#39;performance_schema_max_mutex_classes&#39;</span> <span style="color:#66d9ef">is</span> a <span style="color:#66d9ef">GLOBAL</span> <span style="color:#66d9ef">variable</span>
</span></span></code></pre></div><p>As you can see, the scope of ScopeNone can be read as a global variable,</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-sql" data-lang="sql"><span style="display:flex;"><span>MySQL  <span style="color:#ae81ff">127</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">1</span>:<span style="color:#ae81ff">4000</span>  <span style="color:#66d9ef">SQL</span> <span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">set</span> <span style="color:#66d9ef">global</span> performance_schema_max_mutex_classes <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span>;
</span></span><span style="display:flex;"><span>ERROR: <span style="color:#ae81ff">1105</span> (HY000): <span style="color:#66d9ef">Variable</span> <span style="color:#e6db74">&#39;performance_schema_max_mutex_classes&#39;</span> <span style="color:#66d9ef">is</span> a <span style="color:#66d9ef">read</span><span style="color:#f92672">-</span><span style="color:#66d9ef">only</span> <span style="color:#66d9ef">variable</span>
</span></span><span style="display:flex;"><span>MySQL  <span style="color:#ae81ff">127</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">1</span>:<span style="color:#ae81ff">4000</span>  <span style="color:#66d9ef">SQL</span> <span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">set</span> performance_schema_max_mutex_classes <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span>;
</span></span><span style="display:flex;"><span>ERROR: <span style="color:#ae81ff">1105</span> (HY000): <span style="color:#66d9ef">Variable</span> <span style="color:#e6db74">&#39;performance_schema_max_mutex_classes&#39;</span> <span style="color:#66d9ef">is</span> a <span style="color:#66d9ef">read</span><span style="color:#f92672">-</span><span style="color:#66d9ef">only</span> <span style="color:#66d9ef">variable</span>
</span></span><span style="display:flex;"><span>MySQL  <span style="color:#ae81ff">127</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">1</span>:<span style="color:#ae81ff">4000</span>  <span style="color:#66d9ef">SQL</span> <span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">set</span> <span style="color:#66d9ef">session</span> performance_schema_max_mutex_classes <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span>;
</span></span><span style="display:flex;"><span>ERROR: <span style="color:#ae81ff">1105</span> (HY000): <span style="color:#66d9ef">Variable</span> <span style="color:#e6db74">&#39;performance_schema_max_mutex_classes&#39;</span> <span style="color:#66d9ef">is</span> a <span style="color:#66d9ef">read</span><span style="color:#f92672">-</span><span style="color:#66d9ef">only</span> <span style="color:#66d9ef">variable</span>
</span></span></code></pre></div><p>But it cannot be set in any way.</p>
<p>To trace the usage of ScopeNone, you will see</p>
<p><img alt="defaultSysVars" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-3/20200728155134.webp"></p>
<p>In <code>setSysVariable</code>, when this type of scope variable is encountered, an error is directly returned.</p>
<p><img alt="defaultSysVars" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-3/20200728155332.webp"></p>
<p>In <code>ValidateGetSystemVar</code>, it is handled as a global variable.
From a theoretical standpoint, these ScopeNone variables are essentially a single copy in the code. Once TiDB is started, they exist in memory as read-only and are not actually stored in TiKV.</p>
<h4 id="scopeglobal">ScopeGlobal</h4>
<p>Using <code>gtid_mode</code> as an example,</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-sql" data-lang="sql"><span style="display:flex;"><span>MySQL  <span style="color:#ae81ff">127</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">1</span>:<span style="color:#ae81ff">4000</span>  <span style="color:#66d9ef">SQL</span> <span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">select</span> <span style="color:#f92672">@@</span>gtid_mode;
</span></span><span style="display:flex;"><span><span style="color:#f92672">+</span><span style="color:#75715e">-------------+
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">|</span> <span style="color:#f92672">@@</span>gtid_mode <span style="color:#f92672">|</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">+</span><span style="color:#75715e">-------------+
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">|</span> <span style="color:#66d9ef">OFF</span>         <span style="color:#f92672">|</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">+</span><span style="color:#75715e">-------------+
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff">1</span> <span style="color:#66d9ef">row</span> <span style="color:#66d9ef">in</span> <span style="color:#66d9ef">set</span> (<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0003</span> sec)
</span></span><span style="display:flex;"><span>MySQL  <span style="color:#ae81ff">127</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">1</span>:<span style="color:#ae81ff">4000</span>  <span style="color:#66d9ef">SQL</span> <span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">select</span> <span style="color:#f92672">@@</span><span style="color:#66d9ef">global</span>.gtid_mode;
</span></span><span style="display:flex;"><span><span style="color:#f92672">+</span><span style="color:#75715e">--------------------+
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">|</span> <span style="color:#f92672">@@</span><span style="color:#66d9ef">global</span>.gtid_mode <span style="color:#f92672">|</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">+</span><span style="color:#75715e">--------------------+
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">|</span> <span style="color:#66d9ef">OFF</span>                <span style="color:#f92672">|</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">+</span><span style="color:#75715e">--------------------+
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff">1</span> <span style="color:#66d9ef">row</span> <span style="color:#66d9ef">in</span> <span style="color:#66d9ef">set</span> (<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0006</span> sec)
</span></span><span style="display:flex;"><span>MySQL  <span style="color:#ae81ff">127</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">1</span>:<span style="color:#ae81ff">4000</span>  <span style="color:#66d9ef">SQL</span> <span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">select</span> <span style="color:#f92672">@@</span><span style="color:#66d9ef">session</span>.gtid_mode;
</span></span><span style="display:flex;"><span>ERROR: <span style="color:#ae81ff">1238</span> (HY000): <span style="color:#66d9ef">Variable</span> <span style="color:#e6db74">&#39;gtid_mode&#39;</span> <span style="color:#66d9ef">is</span> a <span style="color:#66d9ef">GLOBAL</span> <span style="color:#66d9ef">variable</span>
</span></span></code></pre></div><p>It works the same way as MySQL global variable reading,</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-sql" data-lang="sql"><span style="display:flex;"><span>MySQL  <span style="color:#ae81ff">127</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">1</span>:<span style="color:#ae81ff">4000</span>  <span style="color:#66d9ef">SQL</span> <span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">set</span> gtid_mode<span style="color:#f92672">=</span><span style="color:#66d9ef">on</span>;
</span></span><span style="display:flex;"><span>ERROR: <span style="color:#ae81ff">1105</span> (HY000): <span style="color:#66d9ef">Variable</span> <span style="color:#e6db74">&#39;gtid_mode&#39;</span> <span style="color:#66d9ef">is</span> a <span style="color:#66d9ef">GLOBAL</span> <span style="color:#66d9ef">variable</span> <span style="color:#66d9ef">and</span> should be <span style="color:#66d9ef">set</span> <span style="color:#66d9ef">with</span> <span style="color:#66d9ef">SET</span> <span style="color:#66d9ef">GLOBAL</span>
</span></span><span style="display:flex;"><span>MySQL  <span style="color:#ae81ff">127</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">1</span>:<span style="color:#ae81ff">4000</span>  <span style="color:#66d9ef">SQL</span> <span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">set</span> <span style="color:#66d9ef">session</span> gtid_mode<span style="color:#f92672">=</span><span style="color:#66d9ef">on</span>;
</span></span><span style="display:flex;"><span>ERROR: <span style="color:#ae81ff">1105</span> (HY000): <span style="color:#66d9ef">Variable</span> <span style="color:#e6db74">&#39;gtid_mode&#39;</span> <span style="color:#66d9ef">is</span> a <span style="color:#66d9ef">GLOBAL</span> <span style="color:#66d9ef">variable</span> <span style="color:#66d9ef">and</span> should be <span style="color:#66d9ef">set</span> <span style="color:#66d9ef">with</span> <span style="color:#66d9ef">SET</span> <span style="color:#66d9ef">GLOBAL</span>
</span></span><span style="display:flex;"><span>MySQL  <span style="color:#ae81ff">127</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">1</span>:<span style="color:#ae81ff">4000</span>  <span style="color:#66d9ef">SQL</span> <span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">set</span> <span style="color:#66d9ef">global</span> gtid_mode<span style="color:#f92672">=</span><span style="color:#66d9ef">on</span>;
</span></span><span style="display:flex;"><span>Query OK, <span style="color:#ae81ff">0</span> <span style="color:#66d9ef">rows</span> affected (<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0029</span> sec)
</span></span><span style="display:flex;"><span>MySQL  <span style="color:#ae81ff">127</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">1</span>:<span style="color:#ae81ff">4000</span>  <span style="color:#66d9ef">SQL</span> <span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">select</span> <span style="color:#f92672">@@</span><span style="color:#66d9ef">global</span>.gtid_mode;
</span></span><span style="display:flex;"><span><span style="color:#f92672">+</span><span style="color:#75715e">--------------------+
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">|</span> <span style="color:#f92672">@@</span><span style="color:#66d9ef">global</span>.gtid_mode <span style="color:#f92672">|</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">+</span><span style="color:#75715e">--------------------+
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">|</span> <span style="color:#66d9ef">ON</span>                 <span style="color:#f92672">|</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">+</span><span style="color:#75715e">--------------------+
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff">1</span> <span style="color:#66d9ef">row</span> <span style="color:#66d9ef">in</span> <span style="color:#66d9ef">set</span> (<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0005</span> sec)
</span></span><span style="display:flex;"><span>MySQL  <span style="color:#ae81ff">127</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">1</span>:<span style="color:#ae81ff">4000</span>  <span style="color:#66d9ef">SQL</span> <span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">select</span> <span style="color:#f92672">@@</span>gtid_mode;
</span></span><span style="display:flex;"><span><span style="color:#f92672">+</span><span style="color:#75715e">-------------+
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">|</span> <span style="color:#f92672">@@</span>gtid_mode <span style="color:#f92672">|</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">+</span><span style="color:#75715e">-------------+
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">|</span> <span style="color:#66d9ef">ON</span>          <span style="color:#f92672">|</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">+</span><span style="color:#75715e">-------------+
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff">1</span> <span style="color:#66d9ef">row</span> <span style="color:#66d9ef">in</span> <span style="color:#66d9ef">set</span> (<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0006</span> sec)
</span></span></code></pre></div><p>The setting method is also compatible with MySQL. At this point, we can shut down the single-instance TiDB and restart it,</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-sql" data-lang="sql"><span style="display:flex;"><span>MySQL  <span style="color:#ae81ff">127</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">1</span>:<span style="color:#ae81ff">4000</span>  <span style="color:#66d9ef">SQL</span> <span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">select</span> <span style="color:#f92672">@@</span>gtid_mode;
</span></span><span style="display:flex;"><span><span style="color:#f92672">+</span><span style="color:#75715e">-------------+
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">|</span> <span style="color:#f92672">@@</span>gtid_mode <span style="color:#f92672">|</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">+</span><span style="color:#75715e">-------------+
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">|</span> <span style="color:#66d9ef">ON</span>          <span style="color:#f92672">|</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">+</span><span style="color:#75715e">-------------+
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff">1</span> <span style="color:#66d9ef">row</span> <span style="color:#66d9ef">in</span> <span style="color:#66d9ef">set</span> (<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0003</span> sec)
</span></span></code></pre></div><p>And you can see that the result can still be read, meaning that this setting was persisted to the storage engine.
Looking closely at the code, you can see:</p>
<p><img alt="defaultSysVars" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-3/20200728164505.webp"></p>
<p>The actual implementation involves executing an internal replace statement to update the original value. This constitutes a complete transaction involving acquiring two TSOs and committing the entire process, making it slower compared to setting session variables.</p>
<h4 id="scopesession">ScopeSession</h4>
<p>Using <code>rand_seed2</code> as an example,</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-sql" data-lang="sql"><span style="display:flex;"><span>MySQL  <span style="color:#ae81ff">127</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">1</span>:<span style="color:#ae81ff">4000</span>  <span style="color:#66d9ef">SQL</span> <span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">select</span> <span style="color:#f92672">@@</span>rand_seed2;
</span></span><span style="display:flex;"><span><span style="color:#f92672">+</span><span style="color:#75715e">--------------+
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">|</span> <span style="color:#f92672">@@</span>rand_seed2 <span style="color:#f92672">|</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">+</span><span style="color:#75715e">--------------+
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">|</span>              <span style="color:#f92672">|</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">+</span><span style="color:#75715e">--------------+
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff">1</span> <span style="color:#66d9ef">row</span> <span style="color:#66d9ef">in</span> <span style="color:#66d9ef">set</span> (<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0005</span> sec)
</span></span><span style="display:flex;"><span>MySQL  <span style="color:#ae81ff">127</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">1</span>:<span style="color:#ae81ff">4000</span>  <span style="color:#66d9ef">SQL</span> <span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">select</span> <span style="color:#f92672">@@</span><span style="color:#66d9ef">session</span>.rand_seed2;
</span></span><span style="display:flex;"><span><span style="color:#f92672">+</span><span style="color:#75715e">----------------------+
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">|</span> <span style="color:#f92672">@@</span><span style="color:#66d9ef">session</span>.rand_seed2 <span style="color:#f92672">|</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">+</span><span style="color:#75715e">----------------------+
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">|</span>                      <span style="color:#f92672">|</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">+</span><span style="color:#75715e">----------------------+
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff">1</span> <span style="color:#66d9ef">row</span> <span style="color:#66d9ef">in</span> <span style="color:#66d9ef">set</span> (<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0003</span> sec)
</span></span><span style="display:flex;"><span>MySQL  <span style="color:#ae81ff">127</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">1</span>:<span style="color:#ae81ff">4000</span>  <span style="color:#66d9ef">SQL</span> <span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">select</span> <span style="color:#f92672">@@</span><span style="color:#66d9ef">global</span>.rand_seed2;
</span></span><span style="display:flex;"><span>ERROR: <span style="color:#ae81ff">1238</span> (HY000): <span style="color:#66d9ef">Variable</span> <span style="color:#e6db74">&#39;rand_seed2&#39;</span> <span style="color:#66d9ef">is</span> a <span style="color:#66d9ef">SESSION</span> <span style="color:#66d9ef">variable</span>
</span></span></code></pre></div><p>Reading is compatible with MySQL.</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-sql" data-lang="sql"><span style="display:flex;"><span>MySQL  <span style="color:#ae81ff">127</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">1</span>:<span style="color:#ae81ff">4000</span>  <span style="color:#66d9ef">SQL</span> <span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">set</span> rand_seed2<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;abc&#39;</span>;
</span></span><span style="display:flex;"><span>Query OK, <span style="color:#ae81ff">0</span> <span style="color:#66d9ef">rows</span> affected (<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0006</span> sec)
</span></span><span style="display:flex;"><span>MySQL  <span style="color:#ae81ff">127</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">1</span>:<span style="color:#ae81ff">4000</span>  <span style="color:#66d9ef">SQL</span> <span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">set</span> <span style="color:#66d9ef">session</span> rand_seed2<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;bcd&#39;</span>;
</span></span><span style="display:flex;"><span>Query OK, <span style="color:#ae81ff">0</span> <span style="color:#66d9ef">rows</span> affected (<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0004</span> sec)
</span></span><span style="display:flex;"><span>MySQL  <span style="color:#ae81ff">127</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">1</span>:<span style="color:#ae81ff">4000</span>  <span style="color:#66d9ef">SQL</span> <span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">set</span> <span style="color:#66d9ef">global</span> rand_seed2<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;cde&#39;</span>;
</span></span><span style="display:flex;"><span>ERROR: <span style="color:#ae81ff">1105</span> (HY000): <span style="color:#66d9ef">Variable</span> <span style="color:#e6db74">&#39;rand_seed2&#39;</span> <span style="color:#66d9ef">is</span> a <span style="color:#66d9ef">SESSION</span> <span style="color:#66d9ef">variable</span> <span style="color:#66d9ef">and</span> can<span style="color:#e6db74">&#39;t be used with SET GLOBAL
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">MySQL  127.0.0.1:4000  SQL &gt; select @@rand_seed2;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">+--------------+
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">| @@rand_seed2 |
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">+--------------+
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">| bcd          |
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">+--------------+
</span></span></span></code></pre></div><p>The setting is also compatible with MySQL. It can be simply observed that this operation only changes the session&rsquo;s memory.
The actual place where it finally takes effect is <a href="https://github.com/pingcap/tidb/blob/f360ad7a434e4edd4d7ebce5ed5dc2b9826b6ed0/sessionctx/variable/session.go#L998">SetSystemVar</a>.</p>
<p><img alt="defaultSysVars" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-3/20200728171914.webp"></p>
<p>There are some tricks here.</p>
<h3 id="actual-scope-of-variables">Actual Scope of Variables</h3>
<p>The previous section covered setting session variables. Based on MySQL&rsquo;s variable rules, setting a global variable does not affect the current session. Only newly created sessions will load global variables for session variable assignment. Ultimately, the active session variable take effect. Global variables without session properties still have unique characteristics, and this chapter will cover:</p>
<ol>
<li>Activation of session variables</li>
<li>Activation of pure global variables</li>
<li>Mechanism of global variable function</li>
</ol>
<p>These three aspects.</p>
<h4 id="activation-of-session-variables">Activation of Session Variables</h4>
<p>Whether a session variable is also a global variable only affects whether it needs to load global variable data from the storage engine when the session starts. The default value in the code is the initial value for eternity if no loading is required.</p>
<p>The actual range where a variable operates can only be observed in <a href="https://github.com/pingcap/tidb/blob/f360ad7a434e4edd4d7ebce5ed5dc2b9826b6ed0/sessionctx/variable/session.go#L998">SetSystemVar</a>.</p>
<p><img alt="defaultSysVars" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-3/20200728173351.webp"></p>
<p>For example, in this part, <code>s.MemQuotaNestedLoopApply = tidbOptInt64(val, DefTiDBMemQuotaNestedLoopApply)</code> changes the <code>s</code> structure, effectively changing the current session,</p>
<p>Whereas <code>atomic.StoreUint32(&amp;ProcessGeneralLog, uint32(tidbOptPositiveInt32(val, DefTiDBGeneralLog)))</code> changes the value of the global variable <code>ProcessGeneralLog</code>, thereby affecting the entire TiDB instance when <code>set tidb_general_log = 1</code> is executed.</p>
<h4 id="activation-of-pure-global-variables">Activation of Pure Global Variables</h4>
<p>Pure global variables in current TiDB are used for background threads like DDL, statistics, etc.</p>
<p><img alt="defaultSysVars" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-3/20200728174207.webp">
<img alt="defaultSysVars" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-3/20200728174243.webp"></p>
<p>Because only one TiDB server requires them, session-level variables hold no meaning for these.</p>
<h4 id="mechanism-of-global-variable-function">Mechanism of Global Variable Function</h4>
<p>Global variables in TiDB don&rsquo;t activate immediately after setting. A connection fetches the latest global system variables from TiKV to assign them to the current session the first time it&rsquo;s established. Concurrent connection creation results in frequent access to the TiKV node holding a few global variables. Thus, TiDB caches global variables, updating them every two seconds, significantly reducing TiKV load.
The problem arises that after setting a global variable, a brief wait is necessary before creating a new connection, ensuring new connections will read the latest global variable. This is one of the few eventual consistency locations within TiDB.</p>
<p>For specific details, see <a href="https://github.com/pingcap/tidb/blob/838b6a0cf2df2d1907508e56d9de9ba7fab502e5/session/session.go#L1990">this commentary</a> in <code>loadCommonGlobalVariablesIfNeeded</code>.</p>
<p><img alt="defaultSysVars" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-3/20200728191527.webp"></p>
<h2 id="metrics">Metrics</h2>
<p>Compared to system variables, Metrics in TiDB are simpler, or straightforward. The most common Metrics are Histogram and Counter, the former is used to record actual values for an operation and the latter records occurrences of fixed events.
All Metrics in TiDB are uniformly located <a href="https://github.com/pingcap/tidb/tree/cbc225fa17c93a3f58bef41b5accb57beb0d9586/metrics">here</a>, with AlertManager and Grafana scripts also available separately under alertmanager and grafana.</p>
<p>There are many Metrics, and from a beginner&rsquo;s perspective, it&rsquo;s best to focus on a specific monitoring example. Let&rsquo;s take the TPS (transactions per second) panel as an example.</p>
<p><img alt="tps" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-3/20200729205545.webp"></p>
<p>Click EDIT and you will see the monitoring formula is:</p>
<p><img alt="tps2" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-3/20200729210124.webp"></p>
<p>The <code>tidb_session_transaction_duration_seconds</code> is the name of this specific metric. Since it is a histogram, it can actually be expressed as three types of values: sum, count, and bucket, which represent the total sum of values, the count (which functions the same as a counter), and the distribution by bucket, respectively.</p>
<p>In this context, [1m] represents a time window of 1 minute, indicating the precision of the measurement. The rate function calculates the slope, essentially the rate of change, indicating how many times something occurs per second. The sum function is used for aggregation, and when combined with by (type, txn_mode), it represents aggregation by the dimensions of type and txn_mode.</p>
<p>The Legend below displays the dimensions above using {{type}}-{{txn_mode}}. When surrounded by {{}}, it can display the actual label names.</p>
<p>In this representation, the final states of transactions are commit, abort, and rollback. A commit indicates a successful user-initiated transaction, rollback indicates a user-initiated rollback (which cannot fail), and abort indicates a user-initiated commit that failed.</p>
<p>The second label, txn_mode, refers to two modes: optimistic and pessimistic transactions. There&rsquo;s nothing further to explain about these modes.</p>
<p>Corresponding to the code:</p>
<p><img alt="alt text" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-3/20200729211352.webp"></p>
<p>This segment of code shows that <code>tidb_session_transaction_duration_seconds</code> is divided into several parts, including namespace and subsystem. Generally, to find a variable in a formula like <code>tidb_session_transaction_duration_seconds_count</code> within TiDB code, you need to remove the first two words and the last word.</p>
<p>From this code snippet, you can see it&rsquo;s a histogram, specifically a HistogramVec, which is an array of histograms because it records data with several different labels. The labels LblTxnMode and LblType are these two labels.</p>
<p><img alt="alt text" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-3/20200729211511.webp"></p>
<p>Checking the references, there is a place for registration, which is in the main function we discussed in the first article, where metrics are registered.</p>
<p><img alt="alt text" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-3/20200729211725.webp"></p>
<p>Other references show how metrics are instantiated. Why do we do this? Mainly because as the number of labels increases, the performance of metrics becomes poorer, which is related to Prometheus&rsquo;s implementation. We had no choice but to create many instantiated global variables.</p>
<p><img alt="alt text" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-3/20200729211935.webp"></p>
<p>Taking the implementation of Rollback as an example, its essence is to record the actual execution time of a transaction when Rollback is truly executed. Since it’s a histogram, it is also used as a counter in this instance.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to Read TiDB Source Code (Part 2)</title>
      <link>https://blog.minifish.org/posts/how-to-read-tidb-source-code-part-2/</link>
      <pubDate>Sun, 12 Jul 2020 12:09:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-read-tidb-source-code-part-2/</guid>
      <description>Continuing from the previous article, we learned how to set up the environment for reading code and where to start reading the code. In this part, we&amp;#39;ll introduce methods for...</description>
      <content:encoded><![CDATA[<p>Continuing from <a href="/posts/tidb1">the previous article</a>, we learned how to set up the environment for reading code and where to start reading the code. In this part, we&rsquo;ll introduce methods for viewing code based on some common needs.</p>
<h2 id="how-to-check-the-support-level-of-a-syntax">How to Check the Support Level of a Syntax</h2>
<p>There are usually two methods:</p>
<ol>
<li>Check through the parser repo</li>
<li>Directly check within the TiDB repo</li>
</ol>
<p>Both of these methods require the <a href="/posts/tidb1#%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA">environment setup from the previous article</a>. If you haven&rsquo;t tried that yet, give it a go.</p>
<h3 id="preparation">Preparation</h3>
<ol>
<li>
<p>Install GoYacc Support</p>
<p><img alt="goyacc" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-2/20200712124300.webp"></p>
<p>The GoYacc Support plugin is a creation by a colleague at our company, a third-party plugin officially accepted by JetBrains, a well-regarded product. It includes syntax highlighting and intelligence, which is great!</p>
</li>
<li>
<p>Download <a href="https://github.com/pingcap/parser">parser repo</a></p>
<p>If you&rsquo;re checking syntax directly from the parser, you need to download it manually. If you&rsquo;re navigating from TiDB, IDEA will automatically download the code, so no extra steps are needed.</p>
</li>
</ol>
<h3 id="check-via-parser-repo">Check via parser repo</h3>
<p>Open the parser using IDEA, switch to the branch you need, and locate the parser.y file. However, it is more recommended to check from within TiDB.</p>
<h3 id="check-via-tidb-repo">Check via TiDB repo</h3>
<ol>
<li>
<p>Open the TiDB project with IDEA and switch to the required branch</p>
<p><img alt="co" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-2/20200712183012.webp"></p>
</li>
<li>
<p>Find the parser.y file; make sure to select the broadest search scope</p>
<p><img alt="parser.y" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-2/20200712183658.webp"></p>
<p>Alternatively, you can find it in the file list,</p>
<p><img alt="parser.y2" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-2/20200712184101.webp"></p>
<p><img alt="parser.y3" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-2/20200712184157.webp"></p>
</li>
</ol>
<p>Let&rsquo;s take checking the <code>SHOW ENGINES</code> SQL statement as an example.</p>
<p>The entry point for the entire statement parsing is <a href="https://github.com/pingcap/parser/blob/f56688124d8bbba98ca103dbcc667d0e3b9bef30/parser.y#L1309-L1308">Start</a>. Below it is the StatementList, followed by Statement. Under the large list of Statements, you can find ShowStmt.</p>
<p><img alt="parser.y4" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-2/20200712184841.webp"></p>
<p>However, ShowStmt is actually quite complex. Another way is to directly search for <code>ShowEngines</code> within parser.y, since naming follows Golang conventions, with camel case and capitalized letters for public exposure. Naturally, if familiar with the code, you&rsquo;d know <code>ShowEngines</code> is under <code>ShowTargetFilterable</code>. Its first branch is <code>ShowEngines</code>.</p>
<p><img alt="parser.y5" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-2/20200712185533.webp"></p>
<p><strong>What is the level of support for <code>SHOW ENGINES</code>?</strong></p>
<p>You can look at how <code>ast.ShowEngines</code> is processed. Here, you can&rsquo;t just jump to it; you need to copy and search.</p>
<p><img alt="parser.y6" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-2/20200712190242.webp"></p>
<p>You only need to see how it&rsquo;s processed under TiDB, and you can skip test files.</p>
<p><img alt="parser.y7" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-2/20200712190752.webp"></p>
<p>One is the actual implementation,</p>
<p><img alt="parser.y7" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-2/20200712190839.webp"></p>
<p>The other is the build schema, which you can ignore for now,</p>
<p><img alt="parser.y7" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-2/20200712190956.webp"></p>
<p>Entering <code>fetchShowEngines</code>, you can see the specific implementation is simple, running an internal SQL to read a system table.</p>
<p><img alt="parser.y7" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-2/20200712191054.webp"></p>
<p>Checking <code>SHOW ENGINES</code> ends here. You can see that it&rsquo;s fully supported.</p>
<p><strong>Which statements only have syntax support?</strong></p>
<p>Taking the temporary table creation syntax as an example, find its position in the parser.y file.</p>
<p><img alt="parser.y8" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-2/20200712191711.webp"></p>
<p>It&rsquo;s an option.</p>
<p><img alt="parser.y9" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-2/20200712191843.webp"></p>
<p>You can see that if the temporary table option is specified, it simply returns true with an attached warning, stating that the table is still treated as a regular table. Previously, the parser had a lot of operations that only returned without doing anything, not even a warning, but these are now rare.</p>
<h4 id="advantages-of-querying-via-tidb-repo">Advantages of Querying via TiDB repo</h4>
<p>You can see that checking via the TiDB repo allows you to find the parser&rsquo;s detailed hash using IDEA. If you check directly via the parser, you need to first look up the parser’s hash in TiDB’s go.mod, then check out to the corresponding hash in the parser. If you need to check specific implementations, you have to go back to TiDB, making back-and-forth checks less convenient compared to looking within a single project. The only advantage is the ease of blaming commit history.</p>
<h2 id="viewing-and-modifying-default-configuration">Viewing and Modifying Default Configuration</h2>
<p>The default configurations can be easily viewed in TiDB, specifically the variable <a href="https://github.com/pingcap/tidb/blob/72f6a0405837b92e40de979a4f3134d9aa19a5b3/config/config.go#L547">defaultConf</a>. The configurations listed here are the actual default settings.</p>
<p><img alt="conf1" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-2/20200713172228.webp"></p>
<p>Taking the first Host configuration as an example, it has a mapping to toml and json files.</p>
<p><img alt="conf2" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-2/20200713172535.webp"></p>
<p>This essentially shows how it&rsquo;s written in a toml file. The <code>DefHost</code> following it is the specific default value.</p>
<p><img alt="conf3" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-2/20200713180137.webp"></p>
<p>Something important to note is that configurations have a hierarchical relationship. For example, the log-related configuration in the configuration file is:</p>
<p><img alt="conf4" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-2/20200715164756.webp"></p>
<p>In the code, it is represented as:</p>
<p><img alt="conf5" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-2/20200715164930.webp"></p>
<p>This denotes a configuration called &ldquo;level&rdquo; under the log configuration.</p>
<p>What if you want to add more levels? For instance, the most complex configuration for CopCache adds another layer under tikv-client called copr-cache.</p>
<p><img alt="conf6" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-2/20200715165243.webp"></p>
<p>Since toml files do not support multi-level nesting, this leads to the most complex configuration writing in TiDB.</p>
<p><img alt="conf6" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-2/20200715165456.webp"></p>
<p>To use non-default configurations with the TiDB started through IDEA as mentioned above, the simplest way is to modify this defaultConf.</p>
<h2 id="summary">Summary</h2>
<p>From this, you can see that checking whether a statement is supported, and whether it’s just syntax support or has a specific implementation, can be achieved with the described methods. You also learned how to view and modify default configurations, allowing you to conduct some verifications yourself. In the next article, I plan to introduce TiDB’s system variables.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to Read TiDB Source Code (Part 1)</title>
      <link>https://blog.minifish.org/posts/how-to-read-tidb-source-code-part-1/</link>
      <pubDate>Mon, 06 Jul 2020 16:51:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-read-tidb-source-code-part-1/</guid>
      <description>There are many articles on reading the source code of TiDB, often referred to as the &amp;#34;Twenty-Four Chapters Scriptures&amp;#34;. However, these introductions typically proceed from a macro...</description>
      <content:encoded><![CDATA[<h2 id="background">Background</h2>
<p>There are many articles on reading the source code of TiDB, often referred to as <a href="https://pingcap.com/blog-cn/#TiDB-%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB">the &ldquo;Twenty-Four Chapters Scriptures&rdquo;</a>. However, these introductions typically proceed from a macro to a micro perspective. This series attempts to introduce how to read TiDB&rsquo;s source code from an easier angle. The goals we aim to achieve are:</p>
<ol>
<li>Enable readers to start reading TiDB&rsquo;s code themselves, rather than understanding it passively through pre-written articles.</li>
<li>Provide some common examples of looking into the details of the code, such as examining the scope of a variable.</li>
</ol>
<p>After all, teaching people to fish is better than giving them fish. While the code changes often, the methods remain mostly unchanged.</p>
<p>Why choose TiDB to read?</p>
<ol>
<li>
<p>I am not familiar with TiKV or PD.</p>
</li>
<li>
<p>TiDB is the entry point directly interacting with users and is also the most likely to be questioned.</p>
</li>
<li>
<p>TiDB can run independently and be debugged. If you want to run some SQL after reading the code to verify your understanding, it can be easily done.</p>
</li>
</ol>
<h2 id="preparations">Preparations</h2>
<ol>
<li>
<p>A development machine</p>
<p>TiDB is a pure Golang project. It can be conveniently developed on Linux, MacOS, and even Windows. My environment is Windows 10.</p>
</li>
<li>
<p>A copy of the TiDB source code, available for download at the <a href="https://github.com/pingcap/tidb">official repo</a>.</p>
</li>
<li>
<p><a href="https://golang.org/">Golang</a> environment, following the official guide is straightforward.</p>
</li>
<li>
<p>Goland or IntelliJ IDEA + Golang plugin</p>
<p>I personally feel there&rsquo;s no difference between the two. Why not recommend VSCode + Golang plugin? Mainly because I&rsquo;m used to the JetBrains suite, and indeed commercial software tends to be higher quality than community software. For long-term use, it&rsquo;s recommended to pay for it. Students can use it for free, but need to renew the license every year.</p>
</li>
</ol>
<h2 id="environment-setup">Environment Setup</h2>
<ol>
<li>
<p>After installing the Golang environment, remember to set the GOPATH.</p>
</li>
<li>
<p>The TiDB code doesn&rsquo;t need to be developed under the GOPATH, so you can place it anywhere. I usually create a directory called work and throw various codes in there.</p>
</li>
<li>
<p>Open Goland/IDEA. I use IDEA because I often look at code in other languages.</p>
</li>
<li>
<p>Open with IDEA, select the tidb directory.</p>
<p><img alt="src" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-1/20200706174108.webp"></p>
</li>
<li>
<p>At this point, IDEA typically prompts you to set up GOROOT and enable Go Modules. Follow the recommendations.</p>
</li>
</ol>
<p>The environment setup is now complete.</p>
<h2 id="entry-points">Entry Points</h2>
<p>At the beginning, someone advised me to start with the session package. However, after some experience, I personally feel there are two better entry points: the <code>main</code> function and the <code>dispatch</code> function.</p>
<h3 id="main-function">main Function</h3>
<p>The <code>main</code> function of TiDB can be seen at <a href="https://github.com/pingcap/tidb/blob/6b6096f1f18a03d655d04d67a2f21d7fbfca2e3f/tidb-server/main.go#L160">link</a>. You can roughly go through what happens when starting a tidb-server from top to bottom.</p>
<p><img alt="main" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-1/20200706220211.webp"></p>
<p>From top to bottom:</p>
<ul>
<li>
<p>Parse flags</p>
</li>
<li>
<p>Output version information and exit</p>
</li>
<li>
<p>Register store and monitoring</p>
</li>
<li>
<p>Configuration file check</p>
</li>
<li>
<p>Initialize temporary folders, etc.</p>
</li>
<li>
<p>Set global variables, CPU affinity, log, trace, print server information, set binlog, set monitoring</p>
</li>
<li>
<p>Create store and domain</p>
<p>The <code>createStoreAndDomain</code> method is important, as critical background threads are created here.</p>
</li>
<li>
<p>Create server and register stop signal function</p>
</li>
<li>
<p>Start the server</p>
<p>Within <code>runServer</code>, the <code>srv.Run()</code> actually brings up the tidb-server.
<img alt="run" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-1/20200706221611.webp">
In the <code>Run()</code> function here, the server continuously listens to network requests, creating a new connection for each new request and using a new goroutine to serve it continually.</p>
</li>
<li>
<p>After this, cleanup work is done when the server needs to stop, ultimately writing out the logs.</p>
</li>
</ul>
<p>Thus, the entire <code>main</code> function process ends. Through the <code>main</code> function, you can see the complete lifecycle of a server from creation to destruction.</p>
<p>Additionally, with IDEA, you can easily start and debug TiDB. Click on this triangle symbol as shown in the image below:</p>
<p><img alt="run1" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-1/20200706222247.webp"></p>
<p><img alt="run2" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-1/20200706222457.webp"></p>
<p>A pop-up with options to run and debug the <code>main</code> function will appear. Essentially, this starts a TiDB with default configurations. TiDB defaults to using mocktikv as the storage engine, so it can be started on a single machine for various testing and validation.</p>
<p>As for how to modify the configuration for starting and debugging, this will be introduced in subsequent articles in the series.</p>
<h3 id="dispatch-function">dispatch Function</h3>
<p>From here, we can proceed further to another suitable entry point function, <code>dispatch</code>.</p>
<p>The <code>dispatch</code> function has several characteristics:</p>
<ol>
<li>
<p>Requests coming from clients only enter the <code>dispatch</code> function, meaning from this point onward, user requests are executed. If you set breakpoints here, you can conveniently filter out SQL executed by internal threads.</p>
</li>
<li>
<p>From here, various requests are dispatched into different processing logic, ensuring you don’t miss any user requests. It avoids situations like spending significant time reading text protocol code only to find out the user is actually using a binary protocol.</p>
</li>
<li>
<p><code>dispatch</code> itself is located at a very early stage, meaning its parameters mostly come directly from the client&rsquo;s initial information. If it&rsquo;s a text protocol, directly reading parameters can parse out the SQL text.</p>
</li>
</ol>
<p><img alt="dispatch1" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-1/20200707150344.webp"></p>
<p>At the start, <code>dispatch</code> primarily focuses on obtaining tokens corresponding to the token-limit parameter. Requests that can&rsquo;t get a token won&rsquo;t execute, which explains why you can create many connections but only 1000 SQL executions are allowed simultaneously by default.</p>
<p>Next, we enter the most crucial switch case:</p>
<p><img alt="dispatch2" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-1/20200707150736.webp"></p>
<p>These commands are MySQL protocol commands, so it&rsquo;s apparent from here exactly what TiDB implements. For comparison, you can refer to <a href="https://dev.mysql.com/doc/internals/en/text-protocol.html">this link</a> (this link is only for the text protocol). For full details, see the figure below:</p>
<p><img alt="dispatch3" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-1/20200707151452.webp"></p>
<p>Within <code>dispatch</code>, the most important are <code>mysql.ComQuery</code>, as well as the trio <code>mysql.ComStmtPrepare</code>, <code>mysql.ComStmtExecute</code>, and <code>mysql.ComStmtClose</code>. The latter trio is more frequently used in actual production, hence even more important. In contrast, <code>mysql.ComQuery</code> is generally used only for some simple tests and validations.</p>
<p>Since <code>dispatch</code> is the entry point for interfacing with clients, it can conveniently tally how many requests the database has handled. The so-called QPS derived from monitoring statistics is essentially the number of times this function executes per second. Here arises an issue: in cases like multi-query requests, such as <code>select 1; select 1; select 1;</code>, multiple statements sent together are regarded as a single request by <code>dispatch</code>, but as multiple by clients. While using the binary protocol, some clients prepare a statement, then execute, and finally close it. Seemingly equivalent to executing a single SQL from the client&rsquo;s perspective, the database actually completes three requests.</p>
<p>In summary, users’ perceived QPS may not necessarily align with the number of <code>dispatch</code> function calls. In later versions, the QPS panel in TiDB&rsquo;s monitoring was changed to CPS, which stands for Commands Per Second, representing the number of commands executed per second.</p>
<p>Looking at the callers of <code>dispatch</code> can also reveal information that helps explain some frequently asked questions:</p>
<p><img alt="dispatch4" loading="lazy" src="/posts/how-to-read-tidb-source-code-part-1/20200707154120.webp"></p>
<ol>
<li>
<p>An EOF error in <code>dispatch</code> typically means the client has actively disconnected, so there&rsquo;s no need to maintain the database connection, and it is severed.</p>
</li>
<li>
<p>In case of an undetermined error (indicating a transaction&rsquo;s commit is uncertain—whether it has succeeded or failed needs manual intervention for verification), manual intervention is required immediately, and the connection will be closed.</p>
</li>
<li>
<p>If writing binlog fails and <code>ignore-error = false</code>, previously the tidb-server process wouldn&rsquo;t exit but couldn&rsquo;t provide services. Now, the tidb-server will exit directly.</p>
</li>
<li>
<p>For all other <code>dispatch</code> errors, the connection will not be closed, allowing service to continue, but the failure information will be logged as &ldquo;command dispatched failed&rdquo;, which is arguably one of the most critical logs for TiDB.</p>
</li>
</ol>
<h2 id="conclusion">Conclusion</h2>
<p>This concludes the introduction from setting up the environment to finding a reasonable entry point to start reading code. Subsequent posts in the series will cover aspects such as configuration (adjustments, default values), variables (default values, scope, actual range, activation), supported syntax, etc. Stay tuned.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to Use Docker on Windows</title>
      <link>https://blog.minifish.org/posts/how-to-use-docker-on-windows/</link>
      <pubDate>Mon, 13 Apr 2020 10:34:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-use-docker-on-windows/</guid>
      <description>I had to install Docker on Windows to reproduce a bug.</description>
      <content:encoded><![CDATA[<h2 id="background">Background</h2>
<p>I had to install Docker on Windows to reproduce a bug.</p>
<h2 id="process">Process</h2>
<ol>
<li>Using Windows 10 as an example, if you have the Home Basic version, you&rsquo;ll need to pay to upgrade to the Pro version because you need to enable Hyper-V and Container features, which costs about 800 RMB.</li>
<li>Install everything with the default settings, and do not switch to Windows Containers, since most images are still under Linux. If you do switch, you can restore it after starting up.</li>
<li>If you encounter permission issues with shared folders, follow the instructions at <a href="https://github.com/docker/for-win/issues/3174">link</a>. However, this might not solve the problem, and you might encounter a sharing failure. In that case, go to the settings, troubleshoot, and reset to factory defaults. After resetting, ensure the shared folders are selected.</li>
<li>When you encounter errors during use, just try a few more times. It might work; if not, reset it.</li>
</ol>
<h2 id="impressions">Impressions</h2>
<p>Initially, there were no issues using Docker on Linux; Docker itself was simple back then. Later, using it on Mac brought changes, including a user interface, various colors, and numerous bugs. Right from the start, I encountered bugs. Docker did not support Windows a long time ago, and given the various bugs on Mac, I didn&rsquo;t have high expectations. The results were still quite surprising. In summary, here are a few points:</p>
<ol>
<li>It&rsquo;s easier to use.</li>
<li>There are more bugs. Do not expect much, and be prepared to reset at any time. Fortunately, resetting offers a shortcut, making it a pretty usable tool.</li>
<li>It&rsquo;s incredibly slow.</li>
</ol>
<p>At this point, I have no optimism for Docker, Kubernetes, or similar technologies. Done~</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to Prevent a Linux Laptop from Entering Sleep Mode When the Lid is Closed</title>
      <link>https://blog.minifish.org/posts/how-to-prevent-a-linux-laptop-from-entering-sleep-mode-when-the-lid-is-closed/</link>
      <pubDate>Tue, 31 Mar 2020 20:31:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-prevent-a-linux-laptop-from-entering-sleep-mode-when-the-lid-is-closed/</guid>
      <description>Initially, I thought it would be a simple setting adjustment, so I casually Googled it. Sure enough, there was a unanimous solution: modify /etc/systemd/logind.conf, change...</description>
      <content:encoded><![CDATA[<p>Initially, I thought it would be a simple setting adjustment, so I casually Googled it. Sure enough, there was a unanimous solution: modify <code>/etc/systemd/logind.conf</code>, change <code>HandleLidSwitch</code> to <code>ignore</code> or <code>lock</code>, and then restart <code>logind</code> or reboot.</p>
<p>I tried this, but it didn&rsquo;t work at all on my Thinkpad X230. I then tried changing some other options in the aforementioned file, but none worked, and surprisingly, <code>Ubuntu</code> even reported errors.</p>
<p>So, I reinstalled the more preferred <code>Debian</code>. Tried again, and it still didn&rsquo;t work. Finally, I found a more brutal method.</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-shell" data-lang="shell"><span style="display:flex;"><span>systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target
</span></span></code></pre></div><p>This directly points these units to /dev/null&hellip;</p>
<p>To revert, simply use:</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-shell" data-lang="shell"><span style="display:flex;"><span>systemctl unmask sleep.target suspend.target hibernate.target hybrid-sleep.target
</span></span></code></pre></div><p>It&rsquo;s simple and effective.</p>
<p><strong>Update:</strong></p>
<p>If you only mask them, the CPU usage of systemd-logind will be very high because it continuously attempts to sleep. Therefore, you also need to change <code>HandleLidSwitch</code> and others to <code>ignore</code>. As follows:</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>HandleSuspendKey=ignore
</span></span><span style="display:flex;"><span>HandleHibernateKey=ignore
</span></span><span style="display:flex;"><span>HandleLidSwitch=ignore
</span></span><span style="display:flex;"><span>HandleLidSwitchExternalPower=ignore
</span></span><span style="display:flex;"><span>HandleLidSwitchDocked=ignore
</span></span></code></pre></div><p>Then, execute <code>systemctl restart systemd-logind</code>. For more details, refer to this: <a href="https://tothecloud.dev/systemd-logind-high-cpu-usage/">SystemD-LoginD High CPU Usage</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to Dynamically Update DNS Records for Namesilo</title>
      <link>https://blog.minifish.org/posts/how-to-dynamically-update-dns-records-for-namesilo/</link>
      <pubDate>Sat, 01 Feb 2020 12:27:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-dynamically-update-dns-records-for-namesilo/</guid>
      <description>The purpose of dynamic updates is pretty simple. As a long-term user of China Unicom&amp;#39;s broadband, although Unicom provides a public address, it is essentially a dynamic address,...</description>
      <content:encoded><![CDATA[<h2 id="dynamically-updating-dns-records">Dynamically Updating DNS Records</h2>
<p>The purpose of dynamic updates is pretty simple. As a long-term user of China Unicom&rsquo;s broadband, although Unicom provides a public address, it is essentially a dynamic address, not a fixed one. If you want to access your home devices using an IP address from outside, you need to use dynamic DNS (DDNS).</p>
<p>Many routers come with built-in DDNS functionality, but they mostly wrap the interfaces of the commonly used service providers. These providers generally have a few characteristics: 1. Blocked by the Great Firewall; 2. Not cheap; 3. Domestic providers may have security issues; 4. The company might have gone out of business. Rather than relying on these unreliable services, it&rsquo;s better to write your own script for updating.</p>
<p>Thus, the scripting approach comes into play. Initially, I planned to use Cloudflare, as it is the recommended way. Later, I discovered that my domain provider Namesilo offers an API to update DNS. However, it returns data in XML format, and I wasn&rsquo;t sure how to parse this with shell scripts.</p>
<p>Then, I thought of using Go. Since this DDNS client would likely be deployed on a router-like device, languages like Python or Java require a runtime environment, and C might need some dynamic libraries to run, which I wasn&rsquo;t sure how to handle. The fact that Go doesn&rsquo;t require dynamic libraries was a significant advantage. So, I handwrote a tool called <a href="https://github.com/jackysp/und">und</a>.</p>
<ol>
<li>First, create a DNS record in Namesilo.</li>
<li>Obtain the binary of <code>und</code> suitable for your platform. I only provide binaries for arm64|linux and amd64|three mainstream OS in this case. For other platforms, you&rsquo;ll need to compile it yourself. You can refer to the Makefile.</li>
<li>Generate an API key from Namesilo, then start <code>und</code> according to its usage documentation. You might need to run it in the background, so use <code>nohup</code>.</li>
</ol>
<h2 id="github-features-experience">GitHub Features Experience</h2>
<h3 id="github-actions">GitHub Actions</h3>
<p>It feels like a replacement for chaotic third-party CI services. I directly chose the Go option for <code>und</code>. By default, it simply runs <code>go build -v .</code> in Ubuntu.</p>
<h3 id="release">Release</h3>
<p>I used this feature when releasing TiDB before, but didn&rsquo;t remember to upload/automatically generate binaries. Since TiDB is not something that can run completely with just one component, releasing a single binary doesn&rsquo;t make much sense.</p>
<p>This time, the experience led me to believe:</p>
<ol>
<li>When releasing, tagging is best done directly using the release feature.</li>
<li>After the release, since you can edit it, it&rsquo;s a good time to <code>make</code> each binary and upload them. Based on the Makefile setup, you can generate a version. This is quite an important feature.</li>
</ol>
<p>These two steps are quite convenient.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to Start a PowerShell Script in the Background at Windows Startup</title>
      <link>https://blog.minifish.org/posts/how-to-start-a-powershell-script-in-the-background-at-windows-startup/</link>
      <pubDate>Tue, 14 Jan 2020 08:56:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-start-a-powershell-script-in-the-background-at-windows-startup/</guid>
      <description>Note: Start-Process seems to perform a fork-like action, and by default, it opens a new PowerShell window to execute. That&amp;#39;s why -WindowStyle Hidden is added at the end. You can&amp;#39;t...</description>
      <content:encoded><![CDATA[<ul>
<li>
<p>Create a script and place it in</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-powershell" data-lang="powershell"><span style="display:flex;"><span>C:\Users\name\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\`  
</span></span></code></pre></div></li>
<li>
<p>Fill the script with:</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-shell" data-lang="shell"><span style="display:flex;"><span>Start-Process -FilePath <span style="color:#e6db74">&#34;C:\Users\name\bin\gost-windows-amd64.exe&#34;</span> -ArgumentList <span style="color:#e6db74">&#34;-L=&#34;</span>, <span style="color:#e6db74">&#34;-F=&#34;</span> -RedirectStandardOutput <span style="color:#e6db74">&#34;C:\Users\name\bin\gost-windows-amd64.log&#34;</span> -RedirectStandardError <span style="color:#e6db74">&#34;C:\Users\name\bin\gost-windows-amd64.err&#34;</span> -WindowStyle Hidden
</span></span></code></pre></div></li>
</ul>
<p>Note: <code>Start-Process</code> seems to perform a fork-like action, and by default, it opens a new PowerShell window to execute. That&rsquo;s why <code>-WindowStyle Hidden</code> is added at the end. You can&rsquo;t use <code>-NoNewWindow</code> here because it only prevents the creation of a new window for executing <code>Start-Process</code>, but the old window will not exit.<br>
Note 2: After the old window exits, the forked process seems to become an orphan and is managed elsewhere, so permissions, such as network connection permissions, might need to be requested again.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to Deploy an HTTPS Proxy Service</title>
      <link>https://blog.minifish.org/posts/how-to-deploy-an-https-proxy-service/</link>
      <pubDate>Sun, 12 Jan 2020 19:43:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-deploy-an-https-proxy-service/</guid>
      <description>One day, I came across an article by Chen Hao on Twitter. Having benefited from several of his blog posts, I instinctively felt it was reliable, so I read it and decided to write...</description>
      <content:encoded><![CDATA[<h2 id="preface">Preface</h2>
<p>One day, I came across an article by Chen Hao on Twitter. Having benefited from several of his blog posts, I instinctively felt it was reliable, so I read it and decided to write this practical guide.</p>
<h2 id="why-use-an-https-proxy">Why Use an HTTPS Proxy</h2>
<p>In the <a href="https://haoel.github.io/">guide</a>, it’s clearly explained why, plus my own experiences of several shadowsocks being banned, I felt it was necessary to switch to a more secure proxy method.</p>
<h2 id="how-to-deploy-an-https-proxy">How to Deploy an HTTPS Proxy</h2>
<h3 id="gost">gost</h3>
<p><a href="https://github.com/ginuerzh/gost">gost</a> is the tool most recommended in the <a href="https://haoel.github.io/">guide</a>. At first, I misunderstood it as a method similar to kcptun, still relying on shadowsocks. In fact, gost implements multiple proxy types, meaning you don’t need other proxies if you have it. I never liked the method of continuously wrapping to accelerate/obfuscate shadowsocks, always feeling that longer pathways bring more problems.</p>
<h3 id="steps">Steps</h3>
<ul>
<li>
<p>Directly download the latest release from the gost repo. Although I have a Golang environment both locally and on the VPS, downloading directly is the easiest. I downloaded version 2.9.0 here.</p>
</li>
<li>
<p>Following certbot on a bare VPS doesn&rsquo;t work&hellip; it requires:</p>
<ol>
<li>Starting an nginx server, as referenced in <a href="https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-debian-9">this guide</a>. Of course, this requires having a domain name pointing an A record to the VPS.</li>
<li>Verifying access through the domain.</li>
<li>Stopping nginx.</li>
<li>Using certbot&rsquo;s &ndash;standalone mode, which will generate the certificates upon success.</li>
</ol>
</li>
<li>
<p>Here, I didn&rsquo;t use Docker for deployment but used systemd instead, directly creating a systemd unit similar to kcptun. The difference is, because the certificate needs updating, the unit requires a reload method. <a href="http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-commands.html">This tutorial</a> teaches a lot about using systemd, and the author&rsquo;s article quality is also high, highly recommended for subscription.</p>
<ol>
<li>Create a <code>/lib/systemd/system/gost.service</code> file with the following content, replacing the domain with your own:</li>
</ol>
<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>[Unit]
</span></span><span style="display:flex;"><span>Description=gost service
</span></span><span style="display:flex;"><span>After=network.target
</span></span><span style="display:flex;"><span>StartLimitIntervalSec=0
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>[Service]
</span></span><span style="display:flex;"><span>Type=simple
</span></span><span style="display:flex;"><span>Restart=always
</span></span><span style="display:flex;"><span>RestartSec=1
</span></span><span style="display:flex;"><span>User=root
</span></span><span style="display:flex;"><span>PIDFile=/home/admin/gost.pid
</span></span><span style="display:flex;"><span>ExecStart=/home/admin/bin/gost -L &#34;http2://xxx:yyy@0.0.0.0:443?cert=/etc/letsencrypt/live/example.com/fullchain.pem&amp;key=/etc/letsencrypt/live/example.com/privkey.pem&amp;probe_resist=code:404&#34;
</span></span><span style="display:flex;"><span>ExecReload=/bin/kill -HUP $MAINPID
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>[Install]
</span></span><span style="display:flex;"><span>WantedBy=multi-user.target
</span></span></code></pre></div><p><code>ExecStart</code> is a simplified version of the Docker method in the <a href="https://haoel.github.io/">guide</a>. <code>ExecReload</code> just kills the process.</p>
<ol>
<li>
<p>Test whether it’s successful using <code>systemctl start|status|restart|enable gost</code>.</p>
</li>
<li>
<p>Configure crontab to update the certificate. I didn&rsquo;t use systemd because I&rsquo;m not familiar with it.</p>
</li>
</ol>
<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>0 0 1 * * /usr/bin/certbot renew --force-renewal
</span></span><span style="display:flex;"><span>5 0 1 * * systemctl restart gost
</span></span></code></pre></div></li>
<li>
<p>After completing the above, nginx can be directly stopped and disabled.</p>
</li>
<li>
<p>Configure the client. This is simple; just refer to the <a href="https://haoel.github.io/">guide</a>. The principle is straightforward because gost implements the shadowsocks protocol using the shadowsocks Golang version. Therefore, the following command starts a local shadowsocks server, and you configure your client to add a local server configuration that matches the password.</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>.\bin\gost-windows-amd64.exe -L=ss://aes-128-cfb:passcode@:1984 -F=https://xxx:yyy@example.com:443
</span></span></code></pre></div></li>
</ul>
<p>PS: I still don&rsquo;t know how to configure a global HTTPS proxy on Android without root, or how to set it up on iOS without a U.S. account. Also, I&rsquo;m unsure how to elegantly configure startup scripts on Windows 10. These are issues to explore further&hellip;</p>
<h2 id="continuation">Continuation</h2>
<p>Regarding the mobile problem mentioned above, I found that HTTPS proxy client support is generally poor. Gost itself seems to have problems, possibly due to my usage. In short, if not using a local gost to connect remotely, authentication errors occur.</p>
<p>During the holiday break, I tinkered a bit more. First, I deployed a gost HTTP proxy on my home NAS using the simplest nohup + ctrl-D method to maintain it. It&rsquo;s compiled with GOARCH=arm64. After a trial run for a day, Android&rsquo;s weak built-in HTTP proxy worked well, but globally routing through it wasn&rsquo;t great. Hence, I switched from HTTP to using SS to connect to HTTPS remotely. I essentially moved the local service on Windows to my NAS. Additionally, through simple double-port forwarding from NAS -&gt; internal router -&gt; optical modem router, I could also use the NAS as an SS server via the public IP.</p>
<p>The remaining issue is the DDNS. After researching, it seems Cloudflare&rsquo;s API is a more reliable option. Seeing an official flarectl, I compiled it to the NAS and wrote a small script, revisiting the various (pitfalls) wonders of bash, especially remembering special writing for string comparisons such as <code>[ $a != $b ]</code> to <code>[ $a != $b* ]</code> to handle trailing &ldquo;\r&rdquo; &ldquo;\n&rdquo; characters. However, detaching the name server still takes some time. The final effect is to be tested.</p>
<p>Additionally, on the NAS, I currently use curl to fetch my public IP from a third-party. I have a hunch that this method might not work someday or might cause issues.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Using delve to Debug Golang Programs</title>
      <link>https://blog.minifish.org/posts/using-delve-to-debug-golang-programs/</link>
      <pubDate>Mon, 16 Sep 2019 13:24:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/using-delve-to-debug-golang-programs/</guid>
      <description>When I first started writing Golang, I was always looking for a convenient debugging tool. Back then, I came across documentation about using gdb to debug and also tried delve,...</description>
      <content:encoded><![CDATA[<h2 id="background">Background</h2>
<p>When I first started writing Golang, I was always looking for a convenient debugging tool. Back then, I came across documentation about using <code>gdb</code> to debug and also tried <code>delve</code>, but neither felt easy to use. Later, on someone&rsquo;s advice, I went back to the good old <code>print</code> statements&hellip;</p>
<p>Over the past couple of days, I was debugging <code>go test</code> and found that tests would always hang when run per package. I couldn&rsquo;t think of a suitable method at first, so I thought of <code>delve</code> again. After giving it a try, I found it has become much more mature than before.</p>
<h2 id="usage">Usage</h2>
<p><code>dlv attach ${pid}</code> is the method I use most often. After attaching, you can use debugging commands similar to <code>gdb</code>. You can use <code>help</code> to view specific commands.</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>(dlv) help
</span></span><span style="display:flex;"><span>The following commands are available:
</span></span><span style="display:flex;"><span>    args ------------------------ Print function arguments.
</span></span><span style="display:flex;"><span>    break (alias: b) ------------ Sets a breakpoint.
</span></span><span style="display:flex;"><span>    breakpoints (alias: bp) ----- Print out info for active breakpoints.
</span></span><span style="display:flex;"><span>    call ------------------------ Resumes process, injecting a function call (EXPERIMENTAL!!!)
</span></span><span style="display:flex;"><span>    clear ----------------------- Deletes breakpoint.
</span></span><span style="display:flex;"><span>    clearall -------------------- Deletes multiple breakpoints.
</span></span><span style="display:flex;"><span>    condition (alias: cond) ----- Set breakpoint condition.
</span></span><span style="display:flex;"><span>    config ---------------------- Changes configuration parameters.
</span></span><span style="display:flex;"><span>    continue (alias: c) --------- Run until breakpoint or program termination.
</span></span><span style="display:flex;"><span>    deferred -------------------- Executes command in the context of a deferred call.
</span></span><span style="display:flex;"><span>    disassemble (alias: disass) - Disassembler.
</span></span><span style="display:flex;"><span>    down ------------------------ Move the current frame down.
</span></span><span style="display:flex;"><span>    edit (alias: ed) ------------ Open where you are in $DELVE_EDITOR or $EDITOR
</span></span><span style="display:flex;"><span>    exit (alias: quit | q) ------ Exit the debugger.
</span></span><span style="display:flex;"><span>    frame ----------------------- Set the current frame, or execute command on a different frame.
</span></span><span style="display:flex;"><span>    funcs ----------------------- Print list of functions.
</span></span><span style="display:flex;"><span>    goroutine (alias: gr) ------- Shows or changes current goroutine.
</span></span><span style="display:flex;"><span>    goroutines (alias: grs) ----- List program goroutines.
</span></span><span style="display:flex;"><span>    help (alias: h) ------------- Prints the help message.
</span></span><span style="display:flex;"><span>    libraries ------------------- List loaded dynamic libraries.
</span></span><span style="display:flex;"><span>    list (alias: ls | l) -------- Show source code.
</span></span><span style="display:flex;"><span>    locals ---------------------- Print local variables.
</span></span><span style="display:flex;"><span>    next (alias: n) ------------- Step over to next source line.
</span></span><span style="display:flex;"><span>    on -------------------------- Executes a command when a breakpoint is hit.
</span></span><span style="display:flex;"><span>    print (alias: p) ------------ Evaluate an expression.
</span></span><span style="display:flex;"><span>    regs ------------------------ Print contents of CPU registers.
</span></span><span style="display:flex;"><span>    restart (alias: r) ---------- Restart process.
</span></span><span style="display:flex;"><span>    set ------------------------- Changes the value of a variable.
</span></span><span style="display:flex;"><span>    source ---------------------- Executes a file containing a list of delve commands.
</span></span><span style="display:flex;"><span>    sources --------------------- Print list of source files.
</span></span><span style="display:flex;"><span>    stack (alias: bt) ----------- Print stack trace.
</span></span><span style="display:flex;"><span>    step (alias: s) ------------- Single step through program.
</span></span><span style="display:flex;"><span>    step-instruction (alias: si)  Single step a single CPU instruction.
</span></span><span style="display:flex;"><span>    stepout (alias: so) --------- Step out of the current function.
</span></span><span style="display:flex;"><span>    thread (alias: tr) ---------- Switch to the specified thread.
</span></span><span style="display:flex;"><span>    threads --------------------- Print out info for every traced thread.
</span></span><span style="display:flex;"><span>    trace (alias: t) ------------ Set tracepoint.
</span></span><span style="display:flex;"><span>    types ----------------------- Print list of types.
</span></span><span style="display:flex;"><span>    up -------------------------- Move the current frame up.
</span></span><span style="display:flex;"><span>    vars ------------------------ Print package variables.
</span></span><span style="display:flex;"><span>    whatis ---------------------- Prints type of an expression.
</span></span><span style="display:flex;"><span>Type help followed by a command for full documentation.
</span></span></code></pre></div><p>Many of these commands are the same as those in <code>gdb</code>. Another command I use frequently is <code>grs</code>, which outputs all goroutines. You can also use <code>grs -t</code>, which is equivalent to <code>gdb</code>&rsquo;s <code>t a a bt</code>. The only slight drawback is that it only outputs 10 stack frames, and any additional ones are truncated.</p>
<p>Additionally, it seems that processes forked by <code>go test</code> can&rsquo;t be attached to. If you want to test, you must first compile it into a test file and then execute it. You can refer to <a href="https://github.com/pingcap/tidb/issues/12184">this issue</a> for more details.</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>$ dlv attach 19654
</span></span><span style="display:flex;"><span>could not attach to pid 19654: decoding dwarf section info at offset 0x0: too short
</span></span></code></pre></div><p>Furthermore, by default, Go&rsquo;s <code>test</code> caches results, which can be controlled via environment variables. However, with Go modules (<code>go mod</code>), it&rsquo;s recommended to use <code>./ddl.test -test.count=1</code> to disable caching. It doesn&rsquo;t feel very elegant.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to Deploy a Shadowsocks Server</title>
      <link>https://blog.minifish.org/posts/how-to-deploy-a-shadowsocks-server/</link>
      <pubDate>Thu, 27 Sep 2018 15:48:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-deploy-a-shadowsocks-server/</guid>
      <description>There are multiple versions of the Shadowsocks server side implementation. The original version was written in Python, and later, enthusiasts implemented it in various programming...</description>
      <content:encoded><![CDATA[<p>There are multiple versions of the Shadowsocks server side implementation. The original version was written in Python, and later, enthusiasts implemented it in various programming languages of their liking.</p>
<p>Among all these implementations, I personally think the most reliable and stable one is the original Python version. The reason is simple - it has the most users. The Golang version is said to have the most features and also performs very well, making it quite powerful. This might be due to Golang’s inherent high performance and ease of implementation. There&rsquo;s also an implementation using libev, a pure C implementation, which also offers good performance and is very lightweight.</p>
<p>Additionally, updating the server is a necessary task for Shadowsocks users due to well-known reasons. The server should be updated frequently. If you’re using the Python implementation, you might be able to install updates via pip, although I haven’t confirmed this. The Golang version may require a Golang build environment, and then you can use <code>go get -u</code>. For updating libev, you can use apt on Debian-based systems, as apt includes shadowsocks-libev. I haven’t checked if it is available in the Red Hat-based yum repositories.</p>
<p>After this introduction, let&rsquo;s go over the deployment steps, which are quite straightforward:</p>
<ol>
<li>Deploy a Debian 9 or Ubuntu 17 VPS. Mainstream providers like Vultr should have these options available. Assume we are using Debian 9 here.</li>
<li>Run <code>apt install shadowsocks-libev</code> to install.</li>
<li>Edit the configuration file using <code>vim /etc/shadowsocks-libev/config.json</code>. It&rsquo;s best to set the Server IP to 0.0.0.0 to avoid IP issues similar to those on AWS Lightsail.
*. For AWS Lightsail, you need to bind a static IP and open firewall ports. Specific steps can be found on Google.</li>
<li>Restart the service using <code>systemctl restart shadowsocks-libev</code> to apply the changes.</li>
<li>Enable TCP BBR. Specific instructions can be found on Google.</li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>How Immediate is Golang&#39;s Panic</title>
      <link>https://blog.minifish.org/posts/how-immediate-is-golangs-panic/</link>
      <pubDate>Thu, 26 Jul 2018 14:24:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/how-immediate-is-golangs-panic/</guid>
      <description>Let&amp;#39;s first look at the following code snippet:</description>
      <content:encoded><![CDATA[<p>Let&rsquo;s first look at the following code snippet:</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-golang" data-lang="golang"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;os&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;runtime&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;time&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">runtime</span>.<span style="color:#a6e22e">GOMAXPROCS</span>(<span style="color:#ae81ff">2</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">a</span> <span style="color:#f92672">:=</span> make(<span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">int</span>]<span style="color:#66d9ef">int</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">go</span> <span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">for</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">a</span>[<span style="color:#ae81ff">1</span>] = <span style="color:#a6e22e">i</span>
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">i</span><span style="color:#f92672">++</span>
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Sleep</span>(<span style="color:#ae81ff">1000</span>)
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	}()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">a</span>[<span style="color:#ae81ff">1</span>] &gt; <span style="color:#ae81ff">1000000</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">a</span>[<span style="color:#ae81ff">1</span>])
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Exit</span>(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>After compiling and running it (assuming your machine has 2 or more cores), you will get the following error:</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>fatal error: concurrent map read and map write
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>goroutine 1 [running]:
</span></span><span style="display:flex;"><span>runtime.throw(0x10c3e05, 0x21)
</span></span><span style="display:flex;"><span>        /usr/local/Cellar/go/1.10.3/libexec/src/runtime/panic.go:616 +0x81 fp=0xc42004bf00 sp=0xc42004bee0 pc=0x10263f1
</span></span><span style="display:flex;"><span>runtime.mapaccess1_fast64(0x10a5b60, 0xc42007e180, 0x1, 0xc42008e048)
</span></span><span style="display:flex;"><span>        /usr/local/Cellar/go/1.10.3/libexec/src/runtime/hashmap_fast.go:101 +0x197 fp=0xc42004bf28 sp=0xc42004bf00 pc=0x1008d27
</span></span><span style="display:flex;"><span>main.main()
</span></span><span style="display:flex;"><span>        /Users/yusp/test/panic3/main.go:22 +0x7c fp=0xc42004bf88 sp=0xc42004bf28 pc=0x108e28c
</span></span><span style="display:flex;"><span>runtime.main()
</span></span><span style="display:flex;"><span>        /usr/local/Cellar/go/1.10.3/libexec/src/runtime/proc.go:198 +0x212 fp=0xc42004bfe0 sp=0xc42004bf88 pc=0x1027c62
</span></span><span style="display:flex;"><span>runtime.goexit()
</span></span><span style="display:flex;"><span>        /usr/local/Cellar/go/1.10.3/libexec/src/runtime/asm_amd64.s:2361 +0x1 fp=0xc42004bfe8 sp=0xc42004bfe0 pc=0x104e501
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>goroutine 5 [runnable]:
</span></span><span style="display:flex;"><span>time.Sleep(0x3e8)
</span></span><span style="display:flex;"><span>        /usr/local/Cellar/go/1.10.3/libexec/src/runtime/time.go:102 +0x166
</span></span><span style="display:flex;"><span>main.main.func1(0xc42007e180)
</span></span><span style="display:flex;"><span>        /Users/yusp/test/panic3/main.go:18 +0x61
</span></span><span style="display:flex;"><span>created by main.main
</span></span><span style="display:flex;"><span>        /Users/yusp/test/panic3/main.go:13 +0x59
</span></span></code></pre></div><p>It seems straightforward; Golang&rsquo;s map is not thread-safe, and concurrent read and write cause a panic. However, look at the error information on line <code>/Users/yusp/test/panic3/main.go:18 +0x61</code>, which points to line 18 of main.go where <code>Sleep</code> is called, not the actual point of concurrency issue. In a vast stack trace, it becomes even harder to locate the problem.</p>
<p>A workaround that comes to mind is if you only see the read stack and want to see the write stack, set a variable at the read position, reset it after reading, and check the value of this variable at the write position. If reading is currently happening, panic will be triggered.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How TiDB Implements the INSERT Statement</title>
      <link>https://blog.minifish.org/posts/how-tidb-implements-the-insert-statement/</link>
      <pubDate>Wed, 11 Jul 2018 14:18:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/how-tidb-implements-the-insert-statement/</guid>
      <description>In a previous article “TiDB Source Code Reading Series (4) Overview of INSERT Statement”, we introduced the general process of the INSERT statement. Why write a separate article...</description>
      <content:encoded><![CDATA[<p>In a previous article <a href="https://cn.pingcap.com/blog/tidb-source-code-reading-4">“TiDB Source Code Reading Series (4) Overview of INSERT Statement”</a>, we introduced the general process of the INSERT statement. Why write a separate article for INSERT? Because in TiDB, simply inserting a piece of data is the simplest and most common case. It becomes more complex when defining various behaviors within the INSERT statement, such as how to handle situations with Unique Key conflicts: Should we return an error? Ignore the current data insertion? Or overwrite existing data? Therefore, this article will continue to delve into the INSERT statement.</p>
<p>This article will first introduce the classification of INSERT statements in TiDB, along with the syntax and semantics of each statement, and then describe the source code implementation of the five types of INSERT statements.</p>
<h2 id="types-of-insert-statements">Types of INSERT Statements</h2>
<p>In broad terms, TiDB has the following six types of INSERT statements:</p>
<ul>
<li><code>Basic INSERT</code></li>
<li><code>INSERT IGNORE</code></li>
<li><code>INSERT ON DUPLICATE KEY UPDATE</code></li>
<li><code>INSERT IGNORE ON DUPLICATE KEY UPDATE</code></li>
<li><code>REPLACE</code></li>
<li><code>LOAD DATA</code></li>
</ul>
<p>In theory, all six statements belong to the category of INSERT statements.</p>
<p>The first one, <code>Basic INSERT</code>, is the most common INSERT statement, using the syntax <code>INSERT INTO VALUES ()</code>. It implies inserting a record, and if a unique constraint conflict occurs (such as primary key conflict, unique index conflict), it returns an execution failure.</p>
<p>The second, with the syntax <code>INSERT IGNORE INTO VALUES ()</code>, ignores the current INSERT row if a unique constraint conflict occurs and logs a warning. After the statement execution finishes, you can use <code>SHOW WARNINGS</code> to see which rows were not inserted.</p>
<p>The third one, with the syntax <code>INSERT INTO VALUES () ON DUPLICATE KEY UPDATE</code>, updates the conflicting row, then inserts data if there is a conflict. If the updated row conflicts with another row in the table, it returns an error.</p>
<p>The fourth one, similar to the previous case, if the updated row conflicts with another row, this does not insert the row and shows a warning.</p>
<p>The fifth one, with the syntax <code>REPLACE INTO VALUES ()</code>, deletes the conflicting row in the table after a conflict and continues to attempt data insertion. If another conflict occurs again, it continues to delete conflicting data on the table until there is no conflicting data left in the table, then inserts the data.</p>
<p>The last one, using the syntax <code>LOAD DATA INFILE INTO</code>, has semantics similar to <code>INSERT IGNORE</code>, both ignoring conflicts. The difference is that <code>LOAD DATA</code> imports data files into a table, meaning its data source is a CSV data file.</p>
<p>Since <code>INSERT IGNORE ON DUPLICATE KEY UPDATE</code> involves special processing on <code>INSERT ON DUPLICATE KEY UPDATE</code>, it won&rsquo;t be explained in detail separately but will be covered in the same section. Due to the unique nature of <code>LOAD DATA</code>, it will be discussed in other chapters.</p>
<h2 id="basic-insert-statement">Basic INSERT Statement</h2>
<p>The major differences among the several INSERT statements lie in the execution level. Continuing from the <a href="https://cn.pingcap.com/blog/tidb-source-code-reading-4">“TiDB Source Code Reading Series (4) Overview of INSERT Statement”</a>, here is the statement execution process. Those who do not remember the previous content can refer back to the original article.</p>
<p>INSERT&rsquo;s execution logic is located in <a href="https://github.com/pingcap/tidb/blob/ab332eba2a04bc0a996aa72e36190c779768d0f1/executor/insert.go">executor/insert.go</a>. In fact, the execution logic for all four types of INSERT statements covered previously is in this file. Here, we first discuss the most basic <code>Basic INSERT</code>.</p>
<p><code>InsertExec</code> is an implementation of the INSERT executor, conforming to the Executor interface. The most important methods are the following three interfaces:</p>
<ul>
<li>Open: Performs some initialization</li>
<li>Next: Executes the write operation</li>
<li>Close: Performs some cleanup tasks</li>
</ul>
<p>Among them, the most important and complex is the Next method. Depending on whether a SELECT statement is used to retrieve data (<code>INSERT SELECT FROM</code>), the Next process is divided into two branches: <a href="https://github.com/pingcap/tidb/blob/ab332eba2a04bc0a996aa72e36190c779768d0f1/executor/insert_common.go#L180:24">insertRows</a> and <a href="https://github.com/pingcap/tidb/blob/ab332eba2a04bc0a996aa72e36190c779768d0f1/executor/insert_common.go#L277:24">insertRowsFromSelect</a>. Both processes eventually lead to the <code>exec</code> function to execute the INSERT.</p>
<p>In the <code>exec</code> function, the first four types of INSERT statements are processed together. The standard INSERT covered in this section directly enters <a href="https://github.com/pingcap/tidb/blob/5bdf34b9bba3fc4d3e50a773fa8e14d5fca166d5/executor/insert.go#L42:22">insertOneRow</a>.</p>
<p>Before discussing <a href="https://github.com/pingcap/tidb/blob/5bdf34b9bba3fc4d3e50a773fa8e14d5fca166d5/executor/insert.go#L42:22">insertOneRow</a>, let&rsquo;s look at a segment of SQL.</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-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">CREATE</span> <span style="color:#66d9ef">TABLE</span> t (i INT <span style="color:#66d9ef">UNIQUE</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">INSERT</span> <span style="color:#66d9ef">INTO</span> t <span style="color:#66d9ef">VALUES</span> (<span style="color:#ae81ff">1</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">BEGIN</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">INSERT</span> <span style="color:#66d9ef">INTO</span> t <span style="color:#66d9ef">VALUES</span> (<span style="color:#ae81ff">1</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">COMMIT</span>;
</span></span></code></pre></div><p>Paste these lines of SQL sequentially into MySQL and TiDB to see the results.</p>
<p>MySQL:</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-sql" data-lang="sql"><span style="display:flex;"><span>mysql<span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">CREATE</span> <span style="color:#66d9ef">TABLE</span> t (i INT <span style="color:#66d9ef">UNIQUE</span>);
</span></span><span style="display:flex;"><span>Query OK, <span style="color:#ae81ff">0</span> <span style="color:#66d9ef">rows</span> affected (<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">15</span> sec)
</span></span><span style="display:flex;"><span>mysql<span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">INSERT</span> <span style="color:#66d9ef">INTO</span> t <span style="color:#66d9ef">VALUES</span> (<span style="color:#ae81ff">1</span>);
</span></span><span style="display:flex;"><span>Query OK, <span style="color:#ae81ff">1</span> <span style="color:#66d9ef">row</span> affected (<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">01</span> sec)
</span></span><span style="display:flex;"><span>mysql<span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">BEGIN</span>;
</span></span><span style="display:flex;"><span>Query OK, <span style="color:#ae81ff">0</span> <span style="color:#66d9ef">rows</span> affected (<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">00</span> sec)
</span></span><span style="display:flex;"><span>mysql<span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">INSERT</span> <span style="color:#66d9ef">INTO</span> t <span style="color:#66d9ef">VALUES</span> (<span style="color:#ae81ff">1</span>);
</span></span><span style="display:flex;"><span>ERROR <span style="color:#ae81ff">1062</span> (<span style="color:#ae81ff">23000</span>): Duplicate entry <span style="color:#e6db74">&#39;1&#39;</span> <span style="color:#66d9ef">for</span> <span style="color:#66d9ef">key</span> <span style="color:#e6db74">&#39;i&#39;</span>
</span></span><span style="display:flex;"><span>mysql<span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">COMMIT</span>;
</span></span><span style="display:flex;"><span>Query OK, <span style="color:#ae81ff">0</span> <span style="color:#66d9ef">rows</span> affected (<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">11</span> sec)
</span></span></code></pre></div><p>TiDB:</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-sql" data-lang="sql"><span style="display:flex;"><span>mysql<span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">CREATE</span> <span style="color:#66d9ef">TABLE</span> t (i INT <span style="color:#66d9ef">UNIQUE</span>);
</span></span><span style="display:flex;"><span>Query OK, <span style="color:#ae81ff">0</span> <span style="color:#66d9ef">rows</span> affected (<span style="color:#ae81ff">1</span>.<span style="color:#ae81ff">04</span> sec)
</span></span><span style="display:flex;"><span>mysql<span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">INSERT</span> <span style="color:#66d9ef">INTO</span> t <span style="color:#66d9ef">VALUES</span> (<span style="color:#ae81ff">1</span>);
</span></span><span style="display:flex;"><span>Query OK, <span style="color:#ae81ff">1</span> <span style="color:#66d9ef">row</span> affected (<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">12</span> sec)
</span></span><span style="display:flex;"><span>mysql<span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">BEGIN</span>;
</span></span><span style="display:flex;"><span>Query OK, <span style="color:#ae81ff">0</span> <span style="color:#66d9ef">rows</span> affected (<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">01</span> sec)
</span></span><span style="display:flex;"><span>mysql<span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">INSERT</span> <span style="color:#66d9ef">INTO</span> t <span style="color:#66d9ef">VALUES</span> (<span style="color:#ae81ff">1</span>);
</span></span><span style="display:flex;"><span>Query OK, <span style="color:#ae81ff">1</span> <span style="color:#66d9ef">row</span> affected (<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">00</span> sec)
</span></span><span style="display:flex;"><span>mysql<span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">COMMIT</span>;
</span></span><span style="display:flex;"><span>ERROR <span style="color:#ae81ff">1062</span> (<span style="color:#ae81ff">23000</span>): Duplicate entry <span style="color:#e6db74">&#39;1&#39;</span> <span style="color:#66d9ef">for</span> <span style="color:#66d9ef">key</span> <span style="color:#e6db74">&#39;i&#39;</span>
</span></span></code></pre></div><p>As you can see, for INSERT statements, TiDB performs conflict detection at the time of transaction commit, whereas MySQL does it when the statement is executed. The reason for this is that TiDB is designed with a layered structure with TiKV; to ensure efficient execution, only read operations within a transaction must retrieve data from the storage engine, while all write operations are initially placed within the transaction&rsquo;s own <a href="https://github.com/pingcap/tidb/blob/ab332eba2a04bc0a996aa72e36190c779768d0f1/kv/memdb_buffer.go#L31">memDbBuffer</a> in a single TiDB instance. The data is then written to TiKV as a batch during transaction commit. In the implementation, the <a href="https://github.com/pingcap/tidb/blob/e28a81813cfd290296df32056d437ccd17f321fe/kv/kv.go#L23">PresumeKeyNotExists</a> option is set within <a href="https://github.com/pingcap/tidb/blob/5bdf34b9bba3fc4d3e50a773fa8e14d5fca166d5/executor/insert.go#L42:22">insertOneRow</a>, assuming that insertions will not encounter conflicts if no conflicts are detected locally, without needing to check for conflicting data in TiKV. These data are marked as pending verification, and the <code>BatchGet</code> interface is used during the commit process to batch check the whole transaction&rsquo;s pending data.</p>
<p>After all the data goes through <a href="https://github.com/pingcap/tidb/blob/5bdf34b9bba3fc4d3e50a773fa8e14d5fca166d5/executor/insert.go#L42:22">insertOneRow</a> and completes the insertion, the INSERT statement essentially concludes. The remaining tasks involve setting the lastInsertID and other return information, and then returning the results to the client.</p>
<h2 id="insert-ignore-statement">INSERT IGNORE Statement</h2>
<p>The semantics of <code>INSERT IGNORE</code> were introduced earlier. It was mentioned how a standard INSERT checks at the time of commit, but can <code>INSERT IGNORE</code> do the same? The answer is no, because:</p>
<ol>
<li>If <code>INSERT IGNORE</code> is checked at the commit, the transaction module will need to know which rows should be ignored and which should immediately raise errors and roll back, undoubtedly increasing module coupling.</li>
<li>Users want to immediately know which rows were not inserted through <code>INSERT IGNORE</code>. In other words, they would like to see which rows were not actually inserted immediately through <code>SHOW WARNINGS</code>.</li>
</ol>
<p>This requires checking data conflicts promptly when executing <code>INSERT IGNORE</code>. One obvious approach is to try reading the data intended for insertion, logging a warning when finding a conflict, and proceeding to the next row. However, if the statement inserts multiple rows, it would require repetitive reads from TiKV for conflict detection, which would be inefficient. Therefore, TiDB implements a <a href="https://github.com/pingcap/tidb/blob/3c0bfc19b252c129f918ab645c5e7d34d0c3d154/executor/batch_checker.go#L43:6">batchChecker</a>, with the code located in <a href="https://github.com/pingcap/tidb/blob/ab332eba2a04bc0a996aa72e36190c779768d0f1/executor/batch_checker.go">executor/batch_checker.go</a>.</p>
<p>In the <a href="https://github.com/pingcap/tidb/blob/3c0bfc19b252c129f918ab645c5e7d34d0c3d154/executor/batch_checker.go#L43:6">batchChecker</a>, first, prepare the data for insertion, constructing possible conflicting unique constraints into a key within <a href="https://github.com/pingcap/tidb/blob/3c0bfc19b252c129f918ab645c5e7d34d0c3d154/executor/batch_checker.go#L85:24">getKeysNeedCheck</a>. TiDB implements unique constraints by constructing unique keys, as detailed in <a href="https://cn.pingcap.com/blog/tidb-internal-2/">“Three Articles to Understand TiDB&rsquo;s Technical Inside Story – On Computation”</a>.</p>
<p>Then, pass the constructed keys through <a href="https://github.com/pingcap/tidb/blob/c84a71d666b8732593e7a1f0ec3d9b730e50d7bf/kv/txn.go#L97:6">BatchGetValues</a> to read them all at once, resulting in a key-value map where those read are the conflicting data.</p>
<p>Finally, check the keys of the data intended for insertion against the results from <a href="https://github.com/pingcap/tidb/blob/c84a71d666b8732593e7a1f0ec3d9b730e50d7bf/kv/txn.go#L97:6">BatchGetValues</a>. If a conflicting row is found, prepare a warning message and proceed to the next row. If a conflicting row isn’t found, a safe INSERT can proceed. The implementation of this portion is found in <a href="https://github.com/pingcap/tidb/blob/ab332eba2a04bc0a996aa72e36190c779768d0f1/executor/insert_common.go#L490:24">batchCheckAndInsert</a>.</p>
<p>Similarly, after executing the insertion for all data, return information is set, and the execution results are returned to the client.</p>
<h2 id="insert-on-duplicate-key-update-statement">INSERT ON DUPLICATE KEY UPDATE Statement</h2>
<p><code>INSERT ON DUPLICATE KEY UPDATE</code> is the most complex among the INSERT statements. Its semantic essence includes both an INSERT and an UPDATE. The complexity arises since during an UPDATE, a row can be updated to any valid version.</p>
<p>In the previous section, it was discussed how TiDB uses batching to implement conflict checking for special INSERT statements. The same method is used for <code>INSERT ON DUPLICATE KEY UPDATE</code>, but the implementation process is somewhat more complex due to the semantic complexity.</p>
<p>Initially, similar to <code>INSERT IGNORE</code>, the keys constructed from the data to be inserted are read out at once using <a href="https://github.com/pingcap/tidb/blob/c84a71d666b8732593e7a1f0ec3d9b730e50d7bf/kv/txn.go#L97:6">BatchGetValues</a>, resulting in a key-value map. Then, all records corresponding to the read keys are again read using a batch <a href="https://github.com/pingcap/tidb/blob/c84a71d666b8732593e7a1f0ec3d9b730e50d7bf/kv/txn.go#L97:6">BatchGetValues</a>, prepared for possible future UPDATE operations. The specific implementation is in <a href="https://github.com/pingcap/tidb/blob/3c0bfc19b252c129f918ab645c5e7d34d0c3d154/executor/batch_checker.go#L225:24">initDupOldRowValue</a>.</p>
<p>Then, during conflict checking, if a conflict occurs, an UPDATE is performed first. As discussed in the Basic INSERT section earlier, TiDB executes INSERT in TiKV during commit. Similarly, UPDATE is also executed in TiKV during commit. In this UPDATE process, unique constraint conflicts might still occur. If so, then an error is returned. If the statement is <code>INSERT IGNORE ON DUPLICATE KEY UPDATE</code>, this error is ignored, and the next row proceeds.</p>
<p>In the UPDATE from the previous step, another scenario can occur, as in the SQL below:</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-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">CREATE</span> <span style="color:#66d9ef">TABLE</span> t (i INT <span style="color:#66d9ef">UNIQUE</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">INSERT</span> <span style="color:#66d9ef">INTO</span> t <span style="color:#66d9ef">VALUES</span> (<span style="color:#ae81ff">1</span>), (<span style="color:#ae81ff">1</span>) <span style="color:#66d9ef">ON</span> DUPLICATE <span style="color:#66d9ef">KEY</span> <span style="color:#66d9ef">UPDATE</span> i <span style="color:#f92672">=</span> i;
</span></span></code></pre></div><p>Here, it is clear that there are no original data in the table; the INSERT in the second line cannot read out possibly conflicting data, but there is a conflict between the two rows of data intended to be inserted themselves. Correct execution here should involve the first 1 being inserted normally, with the second 1 encountering conflict and updating the first 1. Thus, it is necessary to handle it as follows: remove the key-value of the data updated in the previous step from the initial step&rsquo;s key-value map, reconstruct unique constraint keys and values for the data from the UPDATE based on table information, and add this key-value pair back into the initial key-value map for subsequent data conflict checking. The detail implementation is in <a href="https://github.com/pingcap/tidb/blob/2fba9931c7ffbb6dd939d5b890508eaa21281b4f/executor/batch_checker.go#L232">fillBackKeys</a>. This scenario also arises in other INSERT statements like <code>INSERT IGNORE</code>, <code>REPLACE</code>, and <code>LOAD DATA</code>. It is introduced here because <code>INSERT ON DUPLICATE KEY UPDATE</code> showcases the full functionality of the <code>batchChecker</code>.</p>
<p>Finally, after all data completes insertion/update, return information is set, and results are returned to the client.</p>
<h2 id="replace-statement">REPLACE Statement</h2>
<p>Although the REPLACE statement appears as a separate type of DML, in examining its syntax, it is merely replacing INSERT with REPLACE compared to a standard <code>Basic INSERT</code>. The difference is that REPLACE is a one-to-many statement. Briefly, for a typical INSERT statement which needs to INSERT a row and encounters a unique constraint conflict, various treatments are available:</p>
<ul>
<li>Abandon the insert and return an error: <code>Basic INSERT</code></li>
<li>Abandon the insert without error: <code>INSERT IGNORE</code></li>
<li>Abandon the insert, turning it into updating the conflicting row. If the updated value conflicts again,</li>
<li>Return an error: <code>INSERT ON DUPLICATE KEY UPDATE</code></li>
<li>No error: <code>INSERT IGNORE ON DUPLICATE KEY UPDATE</code>They all handle conflicts when a row of data conflicts with a row in the table differently. However, the REPLACE statement is distinct; it will delete all conflicting rows it encounters until there are no more conflicts, and then insert the data. If there are 5 unique indexes in the table, there could be 5 rows conflicting with the row waiting to be inserted. The REPLACE statement will delete these 5 rows all at once and then insert its own data. See the SQL below:</li>
</ul>
<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-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">CREATE</span> <span style="color:#66d9ef">TABLE</span> t (
</span></span><span style="display:flex;"><span>i int <span style="color:#66d9ef">unique</span>,
</span></span><span style="display:flex;"><span>j int <span style="color:#66d9ef">unique</span>,
</span></span><span style="display:flex;"><span>k int <span style="color:#66d9ef">unique</span>,
</span></span><span style="display:flex;"><span>l int <span style="color:#66d9ef">unique</span>,
</span></span><span style="display:flex;"><span>m int <span style="color:#66d9ef">unique</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">INSERT</span> <span style="color:#66d9ef">INTO</span> t <span style="color:#66d9ef">VALUES</span>
</span></span><span style="display:flex;"><span>(<span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">1</span>),
</span></span><span style="display:flex;"><span>(<span style="color:#ae81ff">2</span>, <span style="color:#ae81ff">2</span>, <span style="color:#ae81ff">2</span>, <span style="color:#ae81ff">2</span>, <span style="color:#ae81ff">2</span>),
</span></span><span style="display:flex;"><span>(<span style="color:#ae81ff">3</span>, <span style="color:#ae81ff">3</span>, <span style="color:#ae81ff">3</span>, <span style="color:#ae81ff">3</span>, <span style="color:#ae81ff">3</span>),
</span></span><span style="display:flex;"><span>(<span style="color:#ae81ff">4</span>, <span style="color:#ae81ff">4</span>, <span style="color:#ae81ff">4</span>, <span style="color:#ae81ff">4</span>, <span style="color:#ae81ff">4</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">REPLACE</span> <span style="color:#66d9ef">INTO</span> t <span style="color:#66d9ef">VALUES</span> (<span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">2</span>, <span style="color:#ae81ff">3</span>, <span style="color:#ae81ff">4</span>, <span style="color:#ae81ff">5</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">SELECT</span> <span style="color:#f92672">*</span> <span style="color:#66d9ef">FROM</span> t;
</span></span><span style="display:flex;"><span>i j k l m
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">1</span> <span style="color:#ae81ff">2</span> <span style="color:#ae81ff">3</span> <span style="color:#ae81ff">4</span> <span style="color:#ae81ff">5</span>
</span></span></code></pre></div><p>After execution, it actually affects 5 rows of data.</p>
<p>Once we understand the uniqueness of the REPLACE statement, we can more easily comprehend its specific implementation.</p>
<p>Similar to the INSERT statement, the main execution part of the REPLACE statement is also in its Next method. Unlike INSERT, it passes its own <a href="https://github.com/pingcap/tidb/blob/f6dbad0f5c3cc42cafdfa00275abbd2197b8376b/executor/replace.go#L160">exec</a> method through <a href="https://github.com/pingcap/tidb/blob/ab332eba2a04bc0a996aa72e36190c779768d0f1/executor/insert_common.go#L277:24">insertRowsFromSelect</a> and <a href="https://github.com/pingcap/tidb/blob/ab332eba2a04bc0a996aa72e36190c779768d0f1/executor/insert_common.go#L180:24">insertRows</a>. In <a href="https://github.com/pingcap/tidb/blob/f6dbad0f5c3cc42cafdfa00275abbd2197b8376b/executor/replace.go#L160">exec</a>, it calls <a href="https://github.com/pingcap/tidb/blob/f6dbad0f5c3cc42cafdfa00275abbd2197b8376b/executor/replace.go#L95">replaceRow</a>, which also uses batch conflict detection in <a href="https://github.com/pingcap/tidb/blob/3c0bfc19b252c129f918ab645c5e7d34d0c3d154/executor/batch_checker.go#L43:6">batchChecker</a>. The difference from INSERT is that all detected conflicts are deleted here, and finally, the row to be inserted is written in.</p>
<h2 id="in-conclusion">In Conclusion</h2>
<p>The INSERT statement is among the most complex, versatile, and powerful of all DML statements. It includes statements like <code>INSERT ON DUPLICATE UPDATE</code>, which can perform both INSERT and UPDATE operations, and REPLACE, where a single row of data can impact many rows. The INSERT statement itself can be connected to a SELECT statement as input for the data to be inserted, thus its implementation is influenced by the planner (for more on the planner, see related source code reading articles: <a href="https://cn.pingcap.com/blog/tidb-source-code-reading-7/">Part 7: Rule-Based Optimization</a> and <a href="https://cn.pingcap.com/blog/tidb-source-code-reading-8/">Part 8: Cost-Based Optimization</a>). Familiarity with the implementation of various INSERT-related statements in TiDB can help readers use these statements more reasonably and efficiently in the future. Additionally, readers interested in contributing code to TiDB can also gain a quicker understanding of this part of the implementation through this article.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to Use HAProxy to Test CockroachDB</title>
      <link>https://blog.minifish.org/posts/how-to-use-haproxy-to-test-cockroachdb/</link>
      <pubDate>Tue, 10 Jul 2018 15:07:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-use-haproxy-to-test-cockroachdb/</guid>
      <description>yum install haproxy is effective for CentOS 7. After installation, you can start the service using systemctl start haproxy. But don&amp;#39;t rush yet.</description>
      <content:encoded><![CDATA[<h2 id="installing-haproxy">Installing HAProxy</h2>
<p><code>yum install haproxy</code> is effective for CentOS 7. After installation, you can start the service using <code>systemctl start haproxy</code>. But don&rsquo;t rush yet.</p>
<h2 id="configuring-haproxy">Configuring HAProxy</h2>
<p>Add the following content to /etc/haproxy/haproxy.cfg.</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-bash" data-lang="bash"><span style="display:flex;"><span>global <span style="color:#75715e"># The content of global is generally fixed and quite understandable.</span>
</span></span><span style="display:flex;"><span>        log 127.0.0.1   local2
</span></span><span style="display:flex;"><span>        maxconn <span style="color:#ae81ff">4096</span>
</span></span><span style="display:flex;"><span>        user haproxy
</span></span><span style="display:flex;"><span>        group haproxy
</span></span><span style="display:flex;"><span>        chroot /var/lib/haproxy
</span></span><span style="display:flex;"><span>        daemon
</span></span><span style="display:flex;"><span>        pidfile /var/run/haproxy.pid
</span></span><span style="display:flex;"><span>        stats socket /var/run/haproxy.sock         <span style="color:#75715e"># Create a socket file for haproxy</span>
</span></span><span style="display:flex;"><span>        nbproc <span style="color:#ae81ff">40</span>                                  <span style="color:#75715e"># Start 40 processes to forward concurrently, higher versions can use nbthread, a threaded approach.</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>defaults <span style="color:#75715e"># This section is mostly copied, not entirely clear on the options.</span>
</span></span><span style="display:flex;"><span>        log     global
</span></span><span style="display:flex;"><span>        mode    http
</span></span><span style="display:flex;"><span>        option  tcplog
</span></span><span style="display:flex;"><span>        option  dontlognull
</span></span><span style="display:flex;"><span>        retries <span style="color:#ae81ff">3</span>
</span></span><span style="display:flex;"><span>        option  redispatch
</span></span><span style="display:flex;"><span>        maxconn <span style="color:#ae81ff">1024</span>
</span></span><span style="display:flex;"><span>        timeout connect 5000ms
</span></span><span style="display:flex;"><span>        timeout client 50000ms
</span></span><span style="display:flex;"><span>        timeout server 50000ms
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>listen cdb_cluster 0.0.0.0:3030  <span style="color:#75715e"># The actual proxy name and address for receiving services.</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">## cdb balance leastconn - the cluster listening on port 3030.</span>
</span></span><span style="display:flex;"><span>        mode tcp
</span></span><span style="display:flex;"><span>        balance leastconn  <span style="color:#75715e"># This method is most suitable for databases; do not change.</span>
</span></span><span style="display:flex;"><span>        server cdb1 172.16.30.3:26257 check <span style="color:#75715e"># Check seems to require a port for feedback status; without it, it might not work, but it doesn&#39;t matter.</span>
</span></span><span style="display:flex;"><span>        server cdb2 172.16.30.3:26258 check
</span></span><span style="display:flex;"><span>        server cdb3 172.16.30.3:26259 check
</span></span><span style="display:flex;"><span>        server cdb4 172.16.30.3:26260 check
</span></span></code></pre></div><h2 id="start-and-connect">Start and Connect</h2>
<p><code>systemctl start haproxy</code> to start the service.</p>
<p><code>psql -Uroot -h127.0.0.1 -p3030 test</code> to connect to the database.</p>
<h2 id="cockroachdb-official-recommendation">CockroachDB Official Recommendation</h2>
<p>CockroachDB officially provided their recommended <a href="https://www.cockroachlabs.com/docs/stable/deploy-cockroachdb-on-premises.html">configuration</a>. In this configuration, they use:</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-shell" data-lang="shell"><span style="display:flex;"><span>default
</span></span><span style="display:flex;"><span><span style="color:#75715e"># TCP keep-alive on client side. Server already enables them.</span>
</span></span><span style="display:flex;"><span>    option              clitcpka
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>listen psql
</span></span><span style="display:flex;"><span>    option httpchk GET /health?ready<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>
</span></span></code></pre></div><p>These two configurations, the first is to keep the client connection alive, which seems very useful. The second is a status check port, which I understand might be an option to ensure the service is available before dispatching requests, and it also seems very useful. It is recommended to add them.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to Test CockroachDB Performance Using Benchmarksql</title>
      <link>https://blog.minifish.org/posts/how-to-test-cockroachdb-performance-using-benchmarksql/</link>
      <pubDate>Fri, 06 Jul 2018 21:21:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-test-cockroachdb-performance-using-benchmarksql/</guid>
      <description>First of all, TPC-C is the de facto OLTP benchmark standard. It is a set of specifications, and any database can publish its test results under this standard, so there&amp;#39;s no issue...</description>
      <content:encoded><![CDATA[<h2 id="why-test-tpc-c">Why Test TPC-C</h2>
<p>First of all, TPC-C is the de facto OLTP benchmark standard. It is a set of specifications, and any database can publish its test results under this standard, so there&rsquo;s no issue of quarreling over the testing tools used.</p>
<p>Secondly, TPC-C is closer to real-world scenarios as it includes a transaction model within it. In the flow of this transaction model, there are both high-frequency simple transaction statements and low-frequency inventory query statements. Therefore, it tests the database more comprehensively and practically.</p>
<h2 id="testing-tpc-c-on-cockroachdb">Testing TPC-C on CockroachDB</h2>
<p>This year, CockroachDB released its TPC-C performance results. However, unfortunately, they did not use a tool recognized by the database industry that implements the TPC-C standard for testing. Instead, they used their own implementation of a TPC-C tool. The compliance level of this tool was not recognized. In the white paper officially released by them, it is also mentioned that this TPC-C cannot be compared with the TPC-C standard.</p>
<p>Therefore, I thought of using a highly recognized tool in the industry for testing. Here, I chose Benchmarksql version 5.0.</p>
<p>Benchmarksql 5.0 supports the PostgreSQL protocol, Oracle protocol, and MySQL protocol (the MySQL protocol is supported in the code, but the author hasn&rsquo;t fully tested it, so the official documentation doesn&rsquo;t mention MySQL). Among these, the PostgreSQL protocol is supported by CockroachDB.</p>
<h3 id="test-preparation">Test Preparation</h3>
<p>After preparing the Benchmarksql code, don&rsquo;t rush into testing. There are three main pitfalls here that need to be addressed first.</p>
<ol>
<li>
<p><strong>CockroachDB does not support adding a primary key after table creation.</strong> Therefore, you need to include the primary key when creating the table. Specifically, in the <code>run</code> folder under the root directory of the Benchmarksql code, create a <code>sql.cdb</code> folder. Copy <code>tableCreates.sql</code> and <code>indexCreates.sql</code> from the <code>sql.common</code> folder at the same level into <code>sql.cdb</code>. Then move the primary keys in <code>indexCreates.sql</code> into the table creation statements in <code>tableCreates.sql</code>. For how to define indexes while creating tables, please refer to the database documentation syntax via Google.</p>
</li>
<li>
<p><strong>CockroachDB is a &ldquo;strongly typed&rdquo; database.</strong> This is my own way of describing it. It has a rather peculiar behavior: when you add different data types (e.g., int + float), it will report an error saying, &ldquo;InternalError: unsupported binary operator: &lt;int&gt; + &lt;float&gt;&rdquo;. Generally, databases don&rsquo;t behave like this; most would perform some implicit conversions, or in other words, they are very tolerant of SQL writers. But CockroachDB is unique in that if you don&rsquo;t specify the type, it reports an error. This greatly reduces the burden of type inference in its internal implementation.</p>
<p>This behavior causes Benchmarksql to fail to run the tests properly. The solution is to add the required type at the position where the error occurs. For example, change <code>update t set i = i + ?;</code> (the <code>?</code> is generally filled in using <code>prepare/execute</code>) to <code>update t set i = i + ?::DECIMAL;</code>. Yes, CockroachDB specifies types explicitly by adding <code>::&lt;type_name&gt;</code> at the end. But strangely, not all additions require type specification.</p>
</li>
<li>
<p><strong>CockroachDB does not support <code>SELECT FOR UPDATE</code>.</strong> This is the easiest to solve: comment out all <code>FOR UPDATE</code> clauses in Benchmarksql. CockroachDB itself supports the serializable isolation level; lacking <code>FOR UPDATE</code> doesn&rsquo;t affect consistency.</p>
</li>
</ol>
<h3 id="starting-the-test">Starting the Test</h3>
<p>After overcoming the pitfalls mentioned above, you can proceed with the normal testing process: creating the database, creating tables and indexes, importing data, and testing. You can refer to Benchmarksql&rsquo;s <code>HOW-TO-RUN.txt</code>.</p>
<h3 id="test-results">Test Results</h3>
<p>On my test machine with 40 cores, 128 GB of memory, and SSD, under 100 warehouses, the tpmC is approximately 5,000. This is about one-tenth of PostgreSQL 10 on the same machine. PostgreSQL can reach around 500,000 tpmC.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to Test CockroachDB Performance Using Sysbench</title>
      <link>https://blog.minifish.org/posts/how-to-test-cockroachdb-performance-using-sysbench/</link>
      <pubDate>Mon, 11 Jun 2018 13:50:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-test-cockroachdb-performance-using-sysbench/</guid>
      <description>CockroachDB uses the PostgreSQL protocol. If you want to use Sysbench for testing, you need to enable pg protocol support in Sysbench. Sysbench already supports the pg protocol,...</description>
      <content:encoded><![CDATA[<h2 id="compiling-sysbench-with-pgsql-support">Compiling Sysbench with pgsql Support</h2>
<p>CockroachDB uses the PostgreSQL protocol. If you want to use Sysbench for testing, you need to enable pg protocol support in Sysbench. Sysbench already supports the pg protocol, but it is not enabled by default during compilation. You can configure it with the following command:</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-shell" data-lang="shell"><span style="display:flex;"><span>./configure --with-pgsql
</span></span></code></pre></div><p>Of course, preliminary work involves downloading the Sysbench source code and installing the necessary PostgreSQL header files required for compilation (you can use <code>yum</code> or <code>sudo</code> to install them).</p>
<h2 id="testing">Testing</h2>
<p>The testing method is no different from testing MySQL or PostgreSQL; you can test any of the create, read, update, delete (CRUD) operations you like. The only thing to note is to set <code>auto_inc</code> to <code>off</code>.</p>
<p>This is because CockroachDB&rsquo;s auto-increment behavior is different from PostgreSQL&rsquo;s. It generates a unique <code>id</code>, but it does not guarantee that the <code>id</code>s are sequential or incremental. This is fine when inserting data. However, during delete, update, or query operations, since all SQL statements use <code>id</code> as the condition for these operations, you may encounter situations where data cannot be found.</p>
<p>That is:</p>
<p>When <code>auto_inc = on</code> (which is the default value in Sysbench)</p>
<h3 id="table-structure">Table Structure</h3>
<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-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">CREATE</span> <span style="color:#66d9ef">TABLE</span> sbtest1 (
</span></span><span style="display:flex;"><span>   id INT <span style="color:#66d9ef">NOT</span> <span style="color:#66d9ef">NULL</span> <span style="color:#66d9ef">DEFAULT</span> unique_rowid(),
</span></span><span style="display:flex;"><span>   k INTEGER <span style="color:#66d9ef">NOT</span> <span style="color:#66d9ef">NULL</span> <span style="color:#66d9ef">DEFAULT</span> <span style="color:#ae81ff">0</span>:::INT,
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">c</span> STRING(<span style="color:#ae81ff">120</span>) <span style="color:#66d9ef">NOT</span> <span style="color:#66d9ef">NULL</span> <span style="color:#66d9ef">DEFAULT</span> <span style="color:#e6db74">&#39;&#39;</span>:::STRING,
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">pad</span> STRING(<span style="color:#ae81ff">60</span>) <span style="color:#66d9ef">NOT</span> <span style="color:#66d9ef">NULL</span> <span style="color:#66d9ef">DEFAULT</span> <span style="color:#e6db74">&#39;&#39;</span>:::STRING,
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">CONSTRAINT</span> <span style="color:#e6db74">&#34;&#34;</span><span style="color:#66d9ef">primary</span><span style="color:#e6db74">&#34;&#34;</span> <span style="color:#66d9ef">PRIMARY</span> <span style="color:#66d9ef">KEY</span> (id <span style="color:#66d9ef">ASC</span>),
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">INDEX</span> k_1 (k <span style="color:#66d9ef">ASC</span>),
</span></span><span style="display:flex;"><span>   FAMILY <span style="color:#e6db74">&#34;&#34;</span><span style="color:#66d9ef">primary</span><span style="color:#e6db74">&#34;&#34;</span> (id, k, <span style="color:#66d9ef">c</span>, <span style="color:#66d9ef">pad</span>)
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><h3 id="data">Data</h3>
<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-sql" data-lang="sql"><span style="display:flex;"><span>root<span style="color:#f92672">@</span>:<span style="color:#ae81ff">26257</span><span style="color:#f92672">/</span>sbtest<span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">SELECT</span> id <span style="color:#66d9ef">FROM</span> sbtest1 <span style="color:#66d9ef">ORDER</span> <span style="color:#66d9ef">BY</span> id <span style="color:#66d9ef">LIMIT</span> <span style="color:#ae81ff">1</span>;
</span></span><span style="display:flex;"><span><span style="color:#f92672">+</span><span style="color:#75715e">--------------------+
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">|</span>         id         <span style="color:#f92672">|</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">+</span><span style="color:#75715e">--------------------+
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">|</span> <span style="color:#ae81ff">354033003848892419</span> <span style="color:#f92672">|</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">+</span><span style="color:#75715e">--------------------+
</span></span></span></code></pre></div><p>As you can see, the data does not start from <code>1</code>, nor is it sequential. Normally, the <code>id</code> in a Sysbench table should be within the range <code>[1, table_size]</code>.</p>
<h3 id="sql">SQL</h3>
<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-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">UPDATE</span> sbtest<span style="color:#f92672">%</span>u <span style="color:#66d9ef">SET</span> k <span style="color:#f92672">=</span> k <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span> <span style="color:#66d9ef">WHERE</span> id <span style="color:#f92672">=</span> <span style="color:#f92672">?</span>
</span></span></code></pre></div><p>Taking the <code>UPDATE</code> statement as an example, <code>id</code> is used as the query condition. Sysbench assumes that this <code>id</code> should be between <code>[1, table_size]</code>, but in reality, it&rsquo;s not.</p>
<h3 id="example-of-correct-testing-command-line">Example of Correct Testing Command Line</h3>
<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-shell" data-lang="shell"><span style="display:flex;"><span>sysbench --db-driver<span style="color:#f92672">=</span>pgsql --pgsql-host<span style="color:#f92672">=</span>127.0.0.1 --pgsql-port<span style="color:#f92672">=</span><span style="color:#ae81ff">26257</span> --pgsql-user<span style="color:#f92672">=</span>root --pgsql-db<span style="color:#f92672">=</span>sbtest <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>        --time<span style="color:#f92672">=</span><span style="color:#ae81ff">180</span> --threads<span style="color:#f92672">=</span><span style="color:#ae81ff">50</span> --report-interval<span style="color:#f92672">=</span><span style="color:#ae81ff">10</span> --tables<span style="color:#f92672">=</span><span style="color:#ae81ff">32</span> --table-size<span style="color:#f92672">=</span><span style="color:#ae81ff">10000000</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>        oltp_update_index <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>        --sum_ranges<span style="color:#f92672">=</span><span style="color:#ae81ff">50</span> --distinct_ranges<span style="color:#f92672">=</span><span style="color:#ae81ff">50</span> --range_size<span style="color:#f92672">=</span><span style="color:#ae81ff">100</span> --simple_ranges<span style="color:#f92672">=</span><span style="color:#ae81ff">100</span> --order_ranges<span style="color:#f92672">=</span><span style="color:#ae81ff">100</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>        --index_updates<span style="color:#f92672">=</span><span style="color:#ae81ff">100</span> --non_index_updates<span style="color:#f92672">=</span><span style="color:#ae81ff">10</span> --auto_inc<span style="color:#f92672">=</span>off prepare/run/cleanup
</span></span></code></pre></div><h3 id="insert-testing">INSERT Testing</h3>
<p>Let&rsquo;s discuss the INSERT test separately. The INSERT test refers to Sysbench&rsquo;s <code>oltp_insert</code>. The characteristic of this test is that when <code>auto_inc</code> is <code>on</code>, data is inserted during the prepare phase of the test; otherwise, only the table is created without inserting data. Because when <code>auto_inc</code> is <code>on</code>, after the prepare phase, during the run phase, the inserted data will not cause conflicts due to the guarantee of the auto-increment column. When <code>auto_inc</code> is <code>off</code>, the <code>id</code> of the data inserted during the run phase is randomly assigned, which aligns with some actual testing scenarios.</p>
<p>For CockroachDB, when testing INSERT operations with <code>auto_inc</code> set to <code>off</code>, after the prepare phase, during the run phase of data insertion, you can observe the monitoring metrics (by connecting to CockroachDB&rsquo;s HTTP port) under the &ldquo;Distribution&rdquo; section in &ldquo;KV Transactions&rdquo;. You&rsquo;ll notice a large number of &ldquo;Fast-path Committed&rdquo; transactions. This indicates that transactions are committed using one-phase commit (1PC). That is, the data involved in the transaction does not span across CockroachDB nodes, so there&rsquo;s no need to ensure consistency through two-phase commit transactions. This is an optimization in CockroachDB, which is very effective in INSERT tests and can deliver excellent performance.</p>
<p>If <code>auto_inc</code> is <code>on</code>, although for other tests that require read-before-write operations, the results in CockroachDB might be inflated, it is still fair for the INSERT test. If time permits, you can supplement the tests to see the differences.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to View CMU DB Group&#39;s OLTP-Bench</title>
      <link>https://blog.minifish.org/posts/how-to-view-cmu-db-groups-oltp-bench/</link>
      <pubDate>Fri, 23 Feb 2018 00:00:00 +0000</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-view-cmu-db-groups-oltp-bench/</guid>
      <description>OLTP-Bench is an open-source benchmarking tool platform for OLTP scenarios from CMU&amp;#39;s DB Group. It was designed to provide a simple, easy-to-use, and extensible testing platform.</description>
      <content:encoded><![CDATA[<h2 id="introduction-to-oltp-bench">Introduction to OLTP-Bench</h2>
<p>OLTP-Bench is an open-source benchmarking tool platform for OLTP scenarios from CMU&rsquo;s DB Group. It was designed to provide a simple, easy-to-use, and extensible testing platform.</p>
<p>It connects to databases via the JDBC interface, supporting the following test suites:</p>
<ul>
<li>TPC-C</li>
<li>Wikipedia</li>
<li>Synthetic Resource Stresser</li>
<li>Twitter</li>
<li>Epinions.com</li>
<li>TATP</li>
<li>AuctionMark</li>
<li>SEATS</li>
<li>YCSB</li>
<li>JPAB (Hibernate)</li>
<li>CH-benCHmark</li>
<li>Voter (Japanese &ldquo;American Idol&rdquo;)</li>
<li>SIBench (Snapshot Isolation)</li>
<li>SmallBank</li>
<li>LinkBench</li>
</ul>
<p>Detailed project information can be found <a href="http://db.cs.cmu.edu/projects/oltp-bench/">here</a>, and the GitHub page is <a href="https://github.com/oltpbenchmark/oltpbench">here</a>.</p>
<p>The project introduction page includes three papers published by the authors, with the one from 2013 being the most important, also linked on the GitHub page.</p>
<p>Based on the GitHub page, the project does not seem to have a high level of attention and has not been very active recently. Most issues and pull requests come from within CMU.</p>
<h2 id="oltp-bench-an-extensible-testbed-for-benchmarking-relational-databases">OLTP-Bench: An Extensible Testbed for Benchmarking Relational Databases</h2>
<p>The paper &ldquo;OLTP-Bench: An Extensible Testbed for Benchmarking Relational Databases&rdquo; can be regarded as the most detailed introduction to this project.</p>
<p>In the first and second chapters, the authors introduce the motivation for creating this framework, which is to integrate multiple test sets and provide features that simple benchmarking tools do not have, while offering excellent extensibility to attract developers to support more databases.</p>
<p>From the activity on GitHub, it is evident that this extensibility is more about adding database support rather than test sets. However, the number of supported test suites is already quite extensive.</p>
<p>Chapter three introduces the architectural design, with a focus on test suite management, load generators, SQL syntax conversion, multi-client scenarios (similar to multiple sysbench instances stressing a single MySQL), and result collection.</p>
<p>Chapter four discusses the supported test suites. I&rsquo;m only familiar with TPCC and YCSB. The authors classify them from three perspectives:</p>
<ol>
<li>Transaction-focused, such as TPCC and SmallBank</li>
<li>Internet applications, like LinkBench and Wikipedia</li>
<li>Specialized tests, such as YCSB and SIBench</li>
</ol>
<p>Further details can be seen in the table:
[table]</p>
<p>Chapter five describes the demo deployment environment, with subsequent sections introducing the demo&rsquo;s features.</p>
<p>Chapter six uses the demo from the previous chapter to introduce features, analyzed as follows:</p>
<ol>
<li>
<p>Rate control. It seems odd for a benchmarking tool to perform rate control, as the conventional understanding is to push performance as high as possible to gauge system limits. The paper provides an example using the Wikipedia test suite, increasing by 25 TPS every 10 seconds to observe database latency changes.</p>
</li>
<li>
<p>Tagging different transactions in the same test suite for separate statistics – using TPCC as an example to statistically categorize transactions from different stages.</p>
</li>
<li>
<p>Modifying load content, like switching from read-only to write-only loads.</p>
</li>
<li>
<p>Changing the method for load randomness.</p>
</li>
<li>
<p>Monitoring server status alongside database monitoring by deploying an OLTP-Bench monitor on the server.</p>
</li>
<li>
<p>Running multiple test suites simultaneously, such as running TPCC and YCSB concurrently.</p>
</li>
<li>
<p>Multi-client usage, mentioned in chapter three.</p>
</li>
<li>
<p>Repeatability. To prove OLTP-Bench results are genuine and reliable, the authors tested PG&rsquo;s SSI performance using SIBench from the tool on similarly configured machines, achieving results consistent with those in PG&rsquo;s SSI paper.</p>
</li>
</ol>
<p>In summary, rate control and transaction tagging stand out as novel features, while the rest are not particularly special.</p>
<p>Chapter seven is arguably the most valuable part of the article, discussing cloud environments where users might only have database access and not server control. Users may struggle to assess the cost-effectiveness of different cloud database services or configurations due to charges encompassing CPU, storage, network, and asynchronous sync in some architectures. Thus, using benchmarking tools to derive performance and subsequently calculate cost-effectiveness is particularly worthwhile. This chapter compares varying perspectives: different service providers, configurations, comparing databases on the same configuration, and presents the cost-effectiveness outcomes.</p>
<p>In chapter eight, the authors compare OLTP-Bench with other similar tools, providing a favorable self-assessment.</p>
<p>Chapter nine outlines the authors’ future plans, including support for pure NoSQL, additional databases&rsquo; proprietary SQL syntax, generating real-world load distributions from production data, and support for stored procedures.</p>
<p>In conclusion, as the authors mentioned, this is an integrative framework where ease of use and extensibility are key.</p>
<h2 id="usage-summary">Usage Summary</h2>
<p>OLTP-Bench is relatively simple to install and use, especially the deployment. Its cross-platform nature provides a better user experience compared to traditional tpcc and sysbench. Usage is relatively straightforward due to the plethora of test configuration templates provided, allowing easy initiation of tests with simple configuration file modifications. The test results are stable, although certain features mentioned in papers, like server status monitoring, still require exploration.</p>
<p>I tested all 15 test suites on MySQL 5.7 and TiDB, obtaining the following results:
[table]</p>
<p>Its usability is quite evident. As for the ease of secondary development, it should be relatively simple, considering the entire OLTP-Bench project is not particularly large, with around 40,000 lines of code.</p>
<h2 id="other">Other</h2>
<ul>
<li>tpch: While the framework&rsquo;s code appears to support tpch, it proved unusable during practical tests, likely due to incomplete implementation and thus excluded from the README.</li>
<li>Referring to future work mentioned in chapter nine of the paper, especially &ldquo;generating load to match production data distribution,&rdquo; this remains unimplemented, as seen in the codebase.</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>How to Use Monospaced Fonts in VS Code</title>
      <link>https://blog.minifish.org/posts/how-to-use-monospaced-fonts-in-vs-code/</link>
      <pubDate>Tue, 26 Dec 2017 22:21:06 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-use-monospaced-fonts-in-vs-code/</guid>
      <description>The English font set in Microsoft&amp;#39;s VS Code is monospaced, while the Chinese font is not.</description>
      <content:encoded><![CDATA[<h2 id="background">Background</h2>
<p>The English font set in Microsoft&rsquo;s VS Code is monospaced, while the Chinese font is not.</p>
<h2 id="method">Method</h2>
<ol>
<li>Download and install the fonts from <a href="https://github.com/be5invis/Sarasa-Gothic">here</a>. This font combines Source Han Sans and is said to align punctuation strictly.</li>
<li>Use the method described <a href="https://github.com/be5invis/Sarasa-Gothic/issues/8">here</a> to set up VS Code.</li>
</ol>
<p>PS: It is indeed aligned, although the English text appears a bit narrow.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to Understand F1&#39;s Schema Change</title>
      <link>https://blog.minifish.org/posts/how-to-understand-f1s-schema-change/</link>
      <pubDate>Mon, 25 Dec 2017 22:21:06 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-understand-f1s-schema-change/</guid>
      <description>The DDL paper on F1 serves as the foundation for TiDB&amp;#39;s DDL implementation. There are two main papers on F1: one provides an overview of F1&amp;#39;s DDL, and the other specifically...</description>
      <content:encoded><![CDATA[<h2 id="background">Background</h2>
<p>The DDL paper on F1 serves as the foundation for TiDB&rsquo;s DDL implementation. There are two main papers on F1: one provides an overview of F1&rsquo;s DDL, and the other specifically details the schema change method for DDL. I personally believe the second is key and more confusing to me. There is an <a href="http://www.ifi.uzh.ch/dbtg/teaching/courses/SDBS/Papaioannou.pdf">introduction to the second paper</a> here, which can help in understanding.</p>
<h2 id="understanding">Understanding</h2>
<h3 id="online-ddl-concept">Online DDL Concept</h3>
<p>The DDL discussed here refers to online DDL. The concept of online DDL originates from databases like MySQL, whereas PostgreSQL and similar databases might not support it. This concept is also quite vague; the distinction is whether you need to use exclusive locks during DDL operations to block transactions. Therefore, all databases can perform online DDL; it just depends on whether they&rsquo;re willing to put in the effort. For traditional businesses where 24/7 availability isn&rsquo;t a priority, DDL operations can be performed during maintenance times or late at night. Even if a few users are online, at most, they might experience minor delays. However, modern internet businesses are strict about maintenance windows, creating a higher demand for non-blocking DDL, which MySQL, as a quintessential internet database, was first to support. The typical implementation involves creating a copy of the schema table, with operations being sent to both the new and old tables during the transition.</p>
<p>For MySQL&rsquo;s supported online DDL, see <a href="https://dev.mysql.com/doc/refman/5.7/en/innodb-create-index-overview.html">this webpage</a>. Primarily, it&rsquo;s categorized into operations on indexes and columns. This explains my curiosity about why TiDB&rsquo;s examples for implementation often involve adding indexes.</p>
<h3 id="f1s-method">F1&rsquo;s Method</h3>
<p>Having worked on something similar to Aurora before, there were many issues with this area. If you&rsquo;re only performing offline DDL, it doesn&rsquo;t have to be this complicated. According to F1&rsquo;s paper, it uses the following series of state changes to accomplish a DDL:</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>                                    (reorganization)
</span></span><span style="display:flex;"><span>absent -&gt; delete only -&gt; write only ---------------&gt; public
</span></span></code></pre></div><p>In this sequence, each node passes through these four states, transitioning to the next state upon receiving a command. The agreed-upon rule is that each state doesn&rsquo;t persist for more than twice the lease time across all nodes. How is this ensured? Through the following rule: if a node takes too long to move to the next state after receiving the transition command (meaning a state exceeds twice the lease time), it means the node received the command too late and will stop providing services and shut down.</p>
<p>Within these four states, &lsquo;absent&rsquo; indicates a state where the node hasn&rsquo;t received instructions yet, and &lsquo;public&rsquo; signifies the completion of the DDL. What about the two middle states? The background link described them as follows:</p>
<ul>
<li>A delete-only table or column can be modified only by delete operations.</li>
<li>A delete-only index can be modified only by delete and update operations. Update operations can delete key-value pairs corresponding to updated index keys, but they cannot create any new ones.</li>
<li>A write-only column or index can have their key-value pairs modified by insert, delete, and update operations, but none of their pairs can be read by user transactions.</li>
<li>A write-only constraint is applied for all new insert, delete, and update operations, but it is not guaranteed to hold over all existing data.</li>
</ul>
<p>Summarized in a table:</p>
<table>
  <thead>
      <tr>
          <th>delete only</th>
          <th>write only</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Tables and columns can only be deleted; indexes can be updated (tables not really)</td>
          <td>Columns and indexes can be deleted, updated, and inserted</td>
      </tr>
  </tbody>
</table>
<p>The first state is rather peculiar, and the second one even more so—&ldquo;can not read&rdquo; might have been a better name for it. It&rsquo;s said that this design allows two concurrent states to behave consistently, specifically pairs like (absent and delete only), (delete only and write only), and (write only and public).</p>
<p>Examples:</p>
<p>Adding index idx to table t, the deployment environment consists of two databases, a and b.</p>
<ol>
<li>a enters delete only and completes adding the index. b has yet to receive any instructions.
<ul>
<li>Insert operations on a and b: a&rsquo;s idx is ignored, b is unaware and also ignores.</li>
<li>Read operations: a and b ignore idx.</li>
<li>Deletes and updates: a&rsquo;s idx responds, b ignores.</li>
</ul>
</li>
<li>a enters write only, b enters delete only, and indexing is completed.
<ul>
<li>Insert operations on a and b: a&rsquo;s idx responds, b ignores (b loses index data?).</li>
<li>Read operations: a and b ignore idx.</li>
<li>Deletes and updates: both a and b&rsquo;s idx respond.</li>
</ul>
</li>
<li>a enters the public state, b enters the write-only state.
<ul>
<li>Insert operations on a and b: both a and b&rsquo;s idx respond.</li>
<li>Read operations: a&rsquo;s idx responds, b ignores.</li>
<li>Deletes and updates: both a and b&rsquo;s idx respond.</li>
</ul>
</li>
</ol>
<p>Removing index idx from table t, with two databases a and b in the deployment environment.</p>
<ol>
<li>a enters delete only and completes removing the index. b has yet to receive any instructions.
<ul>
<li>Insert operations on a and b: a&rsquo;s idx is ignored, b has index idx and inserts into the index.</li>
<li>Read operations: a ignores idx, b uses it.</li>
<li>Deletes and updates: a&rsquo;s idx responds (no-op if the index is removed), b responds.</li>
</ul>
</li>
<li>a enters write only, b enters delete only, and fulfills the command.
<ul>
<li>Insert operations on a and b: a&rsquo;s idx responds (no-op), b ignores.</li>
<li>Read operations: both a and b ignore idx.</li>
<li>Deletes and updates: both a&rsquo;s and b&rsquo;s idx respond (it&rsquo;s a no-op as the index is already deleted).</li>
</ul>
</li>
<li>a enters the public state, b enters write only.
<ul>
<li>Insert operations on a and b: both a and b respond (b no-op?).</li>
<li>Read operations: a&rsquo;s idx responds, b ignores.</li>
<li>Deletes and updates: both a and b&rsquo;s idx respond (b no-op?).</li>
</ul>
</li>
</ol>
]]></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>
    <item>
      <title>How to Execute Git Push in Travis CI</title>
      <link>https://blog.minifish.org/posts/how-to-execute-git-push-in-travis-ci/</link>
      <pubDate>Mon, 16 Oct 2017 22:21:06 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-execute-git-push-in-travis-ci/</guid>
      <description>Travis CI is generally used for automating tests without needing to update the repository with the test outputs. This article explains how to automatically commit the results from...</description>
      <content:encoded><![CDATA[<h2 id="background">Background</h2>
<p>Travis CI is generally used for automating tests without needing to update the repository with the test outputs. This article explains how to automatically commit the results from Travis CI.</p>
<h2 id="process">Process</h2>
<p>The basic process references <a href="https://gist.github.com/Maumagnaguagno/84a9807ed71d233e5d3f">this gist</a>.</p>
<p>Below is the <code>.travis.yml</code> from the gist.</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-yml" data-lang="yml"><span style="display:flex;"><span><span style="color:#f92672">language</span>: <span style="color:#ae81ff">ruby</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">rvm</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">2.0.0</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">env</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">global</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">USER=&#34;username&#34;</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">EMAIL=&#34;username@mail.com&#34;</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">REPO=&#34;name of target repo&#34;</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">FILES=&#34;README.md foo.txt bar.txt&#34;</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">GH_REPO=&#34;github.com/${USER}/${REPO}.git&#34;</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">secure</span>: <span style="color:#e6db74">&#34;put travis gem output here =&gt; http://docs.travis-ci.com/user/encryption-keys/&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">script</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">ruby test.rb</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">after_success</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">MESSAGE=$(git log --format=%B -n 1 $TRAVIS_COMMIT)</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">git clone git://${GH_REPO}</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">mv -f ${FILES} ${REPO}</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">cd ${REPO}</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">git remote</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">git config user.email ${EMAIL}</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">git config user.name ${USER}</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">git add ${FILES}</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">git commit -m &#34;${MESSAGE}&#34;</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">git push &#34;https://${GH_TOKEN}@${GH_REPO}&#34; master &gt; /dev/null 2&gt;&amp;1</span>
</span></span></code></pre></div><p>Note here that MESSAGE should be quoted when committing, which the original gist did not include.</p>
<p>Original README:</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-markdown" data-lang="markdown"><span style="display:flex;"><span># Travis-CI tested push
</span></span><span style="display:flex;"><span>Sometimes we have a private repository to hold both problems and solutions as a reference for the class projects.
</span></span><span style="display:flex;"><span>The students can see only the problems in a public repository where they are able to clone/fork and develop their own solutions.
</span></span><span style="display:flex;"><span>We do not want the solution files in the public repository and each bug found/feature added in the project requires a push for each repository.
</span></span><span style="display:flex;"><span>It would be cool to work only with the reference repo and use tests to see if our modification is good enough for the public release.
</span></span><span style="display:flex;"><span>This is possible with Travis-CI following simple steps:
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">-</span> Create private and public repos
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">-</span> Download Ruby
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">-</span> Install the travis gem `<span style="color:#e6db74">`gem install travis`</span>`
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">-</span> Generate a token in the Github website to allow others to play with your repos (copy the hash)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">-</span> Log into your git account
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">-</span> Generate a secure token with the Travis gem (copy long hash)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">-</span> Fill the environment variables in the ``<span style="color:#e6db74">`.travis.yml`</span>`` file (USER, EMAIL, REPO, FILES)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">-</span> Replace the value of <span style="font-weight:bold">**secure**</span> with your long hash
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">-</span> Replace <span style="font-weight:bold">**GH_TOKEN**</span> with your Travis token name
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">-</span> Push ``<span style="color:#e6db74">`.travis.yml`</span>`` to private repo
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">-</span> Go to Travis to unlock your private repo tests
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">-</span> Push your files to the private repo to test
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Travis now has a [<span style="color:#f92672">deployment</span>](<span style="color:#a6e22e">https://docs.travis-ci.com/user/deployment/</span>) feature, which may be better for certain scenarios.
</span></span></code></pre></div><p>A simple translation:</p>
<ol>
<li>Create a GitHub project. The original seems to have created two projects, one for updating another.</li>
<li>Install Ruby. Usually, gem is installed alongside.</li>
<li>Install travis via <code>gem install travis</code>.</li>
<li>Apply for a token for your GitHub account. You can Google the details. When selecting token permissions, only tick all related to repo; others can be omitted.</li>
<li>Copy the generated token.</li>
<li>In the root directory of the local machine&rsquo;s repo (not sure if it must be the root) run <code>travis encrypt GH_TOKEN=&quot;copied token&quot;</code>. This creates an encrypted token to use as <code>${GH_TOKEN}</code>, essentially an environment variable. The command output, a string on the screen, needs to be pasted into the travis config file after secure:. Use <code>travis encrypt GH_TOKEN=&quot;copied token&quot; --add</code> to write directly into the config file.</li>
<li>Commit the modified configuration file.</li>
</ol>
<p>This translation is not strictly literal. <strong>The above content is more suited to the following personal configuration:</strong></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-yml" data-lang="yml"><span style="display:flex;"><span><span style="color:#f92672">language</span>: <span style="color:#ae81ff">ruby</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">branches</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">only</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">master</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">rvm</span>:
</span></span><span style="display:flex;"><span>- <span style="color:#ae81ff">2.4.1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">exclude</span>:
</span></span><span style="display:flex;"><span>- <span style="color:#ae81ff">vendor</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">sudo</span>: <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">env</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">global</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">secure</span>: <span style="color:#ae81ff">rxKkyttLE1L4VsVIhhDUYGoLlER33ijKbdAAJPE8vNDSHwyANYnsP1GXK/rcwQqsL/KcJa55wEjVwEBzTMCqZM4UYNVIWqrJepVYo4rL1WhO+jT5sCqVR3qxK9KbgodcSXbmySJnJs0iLGMIQ2bo8yE91OxIC/GKkLCIwr9x4EGwFd5EcE5bOqmVqoSRk1q/1/5yA0aVF+Pohc5ATCZGw9+IyprU2Dx7qbA7F/T/4FQTOQZ4CLZAgyh/Gp1P+uxf1OK4IMCc/P6jVeTmbzQIbUcX0uG09pR7F0GnlV1ZOutMjY7SF8tQ7LNv2Wf8iWdiqehcwKNe/4TFHjs6rm3lEc6F1ELB5s4Z+QXjIM70haENSwM1FI8K5biL7tndAC1TujKESm0XadxORy5yOz7TfQZDTuMXvmmH3j+NFL3vTYPyMwwFca+IQBwD67a4PKD0PWBgEFD9Kn3rAlAzhV5OYdUuxZhx5zuQjKX5szUbL166fgoRnUwDp8dsOjLgOUqQa47IRqR3CTPzbf3zZIxGuX5x6mWySezCNprnXKCpyCegJBLoxQusA+EEYkvl4AOzhnmkhxFbEbHp+DYBjcSEEgpd06l67l3KzjMkpF02vr9CHNj8r7lAtZxwBVxYmczk289D5csOVR1SZKxQLwhx7k+CuEcYds685tLjIMmB0ZU=</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#ae81ff">USER=&#34;username&#34;</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#ae81ff">FULLNAME=&#34;Your Name&#34;</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#ae81ff">EMAIL=&#34;your-email@example.com&#34;</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#ae81ff">REPO=&#34;your-username.github.io&#34;</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#ae81ff">GH_REPO=&#34;github.com/${USER}/${REPO}.git&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">before_script</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">git clone https://${GH_TOKEN}@${GH_REPO}</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">script</span>: <span style="color:#ae81ff">bundle exec jekyll b -d ${REPO}</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">after_success</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">MESSAGE=$(git log --format=%B -n 1 $TRAVIS_COMMIT)</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">cd ${REPO}</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">git config user.email ${EMAIL}</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">git config user.name ${FULLNAME}</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">git add --all</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">git commit -m &#34;${MESSAGE}&#34;</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">git push --force origin master</span>
</span></span></code></pre></div><p>This configuration is used for automatically updating a blog created with Jekyll. There are two projects, one for source files and another for compiled HTML files. The purpose of this setup is to allow updating the blog without having to set up a Jekyll environment, even allowing updates directly from the GitHub website.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to Implement MySQL X Protocol on TiDB</title>
      <link>https://blog.minifish.org/posts/how-to-implement-mysql-x-protocol-on-tidb/</link>
      <pubDate>Wed, 16 Aug 2017 00:00:00 +0000</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-implement-mysql-x-protocol-on-tidb/</guid>
      <description>1. Start a new server for TiDB. The relevant configuration parameters such as IP, port, and socket need to be set. 2. Implement the reading and writing functionality for message...</description>
      <content:encoded><![CDATA[<h2 id="some-documents-on-mysql">Some Documents on MySQL</h2>
<ul>
<li>Client Usage Guide <a href="https://dev.mysql.com/doc/refman/5.7/en/mysql-shell.html">MySQL Shell User Guide</a></li>
<li>Server Configuration Guide <a href="https://dev.mysql.com/doc/refman/5.7/en/document-store.html">Using MySQL as a Document Store</a></li>
<li>Application Development API Guide <a href="https://dev.mysql.com/doc/x-devapi-userguide/en/">X DevAPI User Guide</a></li>
<li>Introduction to Server Internal Implementation <a href="https://dev.mysql.com/doc/internals/en/x-protocol.html">X Protocol</a>.</li>
</ul>
<h2 id="implementation-principle">Implementation Principle</h2>
<ul>
<li>Communication between client and server is over TCP and the protocol uses protobuf.</li>
<li>After the server receives a message, it decodes and analyzes it. The protocol includes a concept called namespace, which specifically refers to whether the namespace is empty or &ldquo;sql&rdquo;, in which case the message content is executed as a SQL statement; if it is &ldquo;xplugin&rdquo; or &ldquo;mysqlx,&rdquo; the message is handled in another way. The other ways can be divided into:
<ul>
<li>Administrative commands</li>
<li>CRUD operations</li>
</ul>
</li>
<li>&ldquo;xplugin&rdquo; and &ldquo;mysqlx&rdquo; have the same function, with the latter being the new name for the former, retained temporarily for compatibility.</li>
<li>The content of &ldquo;mysqlx&rdquo; messages, apart from explicit command content like kill_client, are mostly transformed into SQL statements which the server processes, essentially turning most into a form where the namespace is &ldquo;sql&rdquo;.</li>
</ul>
<h2 id="implementation-steps">Implementation Steps</h2>
<ol>
<li>Start a new server for TiDB. The relevant configuration parameters such as IP, port, and socket need to be set.</li>
<li>Implement the reading and writing functionality for message communication.</li>
<li>Write a process for this new server to establish connections, including authentication, that follows the protocol. Use tcpdump to capture messages between MySQL and the client to derive protocol content, implementing the process by understanding MySQL source code.</li>
<li>The server should include contents like the Query Context from the original TiDB server, as it primarily translates into SQL for execution.</li>
<li>Implement the decoding and handling of messages. Although only a sentence, the workload included is substantial.</li>
</ol>
<p>In <code>mysqlx_all_msgs.h</code>, all messages are initialized</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-c++" data-lang="c++"><span style="display:flex;"><span>  init_message_factory()
</span></span><span style="display:flex;"><span>  {
</span></span><span style="display:flex;"><span>    server_message<span style="color:#f92672">&lt;</span>Mysqlx<span style="color:#f92672">::</span>Connection<span style="color:#f92672">::</span>Capabilities<span style="color:#f92672">&gt;</span>(Mysqlx<span style="color:#f92672">::</span>ServerMessages<span style="color:#f92672">::</span>CONN_CAPABILITIES, <span style="color:#e6db74">&#34;CONN_CAPABILITIES&#34;</span>, <span style="color:#e6db74">&#34;Mysqlx.Connection.Capabilities&#34;</span>);
</span></span><span style="display:flex;"><span>    server_message<span style="color:#f92672">&lt;</span>Mysqlx<span style="color:#f92672">::</span>Error<span style="color:#f92672">&gt;</span>(Mysqlx<span style="color:#f92672">::</span>ServerMessages<span style="color:#f92672">::</span>ERROR, <span style="color:#e6db74">&#34;ERROR&#34;</span>, <span style="color:#e6db74">&#34;Mysqlx.Error&#34;</span>);
</span></span><span style="display:flex;"><span>    server_message<span style="color:#f92672">&lt;</span>Mysqlx<span style="color:#f92672">::</span>Notice<span style="color:#f92672">::</span>Frame<span style="color:#f92672">&gt;</span>(Mysqlx<span style="color:#f92672">::</span>ServerMessages<span style="color:#f92672">::</span>NOTICE, <span style="color:#e6db74">&#34;NOTICE&#34;</span>, <span style="color:#e6db74">&#34;Mysqlx.Notice.Frame&#34;</span>);
</span></span><span style="display:flex;"><span>    server_message<span style="color:#f92672">&lt;</span>Mysqlx<span style="color:#f92672">::</span>Ok<span style="color:#f92672">&gt;</span>(Mysqlx<span style="color:#f92672">::</span>ServerMessages<span style="color:#f92672">::</span>OK, <span style="color:#e6db74">&#34;OK&#34;</span>, <span style="color:#e6db74">&#34;Mysqlx.Ok&#34;</span>);
</span></span><span style="display:flex;"><span>    server_message<span style="color:#f92672">&lt;</span>Mysqlx<span style="color:#f92672">::</span>Resultset<span style="color:#f92672">::</span>ColumnMetaData<span style="color:#f92672">&gt;</span>(Mysqlx<span style="color:#f92672">::</span>ServerMessages<span style="color:#f92672">::</span>RESULTSET_COLUMN_META_DATA, <span style="color:#e6db74">&#34;RESULTSET_COLUMN_META_DATA&#34;</span>, <span style="color:#e6db74">&#34;Mysqlx.Resultset.ColumnMetaData&#34;</span>);
</span></span><span style="display:flex;"><span>    server_message<span style="color:#f92672">&lt;</span>Mysqlx<span style="color:#f92672">::</span>Resultset<span style="color:#f92672">::</span>FetchDone<span style="color:#f92672">&gt;</span>(Mysqlx<span style="color:#f92672">::</span>ServerMessages<span style="color:#f92672">::</span>RESULTSET_FETCH_DONE, <span style="color:#e6db74">&#34;RESULTSET_FETCH_DONE&#34;</span>, <span style="color:#e6db74">&#34;Mysqlx.Resultset.FetchDone&#34;</span>);
</span></span><span style="display:flex;"><span>    server_message<span style="color:#f92672">&lt;</span>Mysqlx<span style="color:#f92672">::</span>Resultset<span style="color:#f92672">::</span>FetchDoneMoreResultsets<span style="color:#f92672">&gt;</span>(Mysqlx<span style="color:#f92672">::</span>ServerMessages<span style="color:#f92672">::</span>RESULTSET_FETCH_DONE_MORE_RESULTSETS, <span style="color:#e6db74">&#34;RESULTSET_FETCH_DONE_MORE_RESULTSETS&#34;</span>, <span style="color:#e6db74">&#34;Mysqlx.Resultset.FetchDoneMoreResultsets&#34;</span>);
</span></span><span style="display:flex;"><span>    server_message<span style="color:#f92672">&lt;</span>Mysqlx<span style="color:#f92672">::</span>Resultset<span style="color:#f92672">::</span>Row<span style="color:#f92672">&gt;</span>(Mysqlx<span style="color:#f92672">::</span>ServerMessages<span style="color:#f92672">::</span>RESULTSET_ROW, <span style="color:#e6db74">&#34;RESULTSET_ROW&#34;</span>, <span style="color:#e6db74">&#34;Mysqlx.Resultset.Row&#34;</span>);
</span></span><span style="display:flex;"><span>    server_message<span style="color:#f92672">&lt;</span>Mysqlx<span style="color:#f92672">::</span>Session<span style="color:#f92672">::</span>AuthenticateOk<span style="color:#f92672">&gt;</span>(Mysqlx<span style="color:#f92672">::</span>ServerMessages<span style="color:#f92672">::</span>SESS_AUTHENTICATE_OK, <span style="color:#e6db74">&#34;SESS_AUTHENTICATE_OK&#34;</span>, <span style="color:#e6db74">&#34;Mysqlx.Session.AuthenticateOk&#34;</span>);
</span></span><span style="display:flex;"><span>    server_message<span style="color:#f92672">&lt;</span>Mysqlx<span style="color:#f92672">::</span>Sql<span style="color:#f92672">::</span>StmtExecuteOk<span style="color:#f92672">&gt;</span>(Mysqlx<span style="color:#f92672">::</span>ServerMessages<span style="color:#f92672">::</span>SQL_STMT_EXECUTE_OK, <span style="color:#e6db74">&#34;SQL_STMT_EXECUTE_OK&#34;</span>, <span style="color:#e6db74">&#34;Mysqlx.Sql.StmtExecuteOk&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    client_message<span style="color:#f92672">&lt;</span>Mysqlx<span style="color:#f92672">::</span>Connection<span style="color:#f92672">::</span>CapabilitiesGet<span style="color:#f92672">&gt;</span>(Mysqlx<span style="color:#f92672">::</span>ClientMessages<span style="color:#f92672">::</span>CON_CAPABILITIES_GET, <span style="color:#e6db74">&#34;CON_CAPABILITIES_GET&#34;</span>, <span style="color:#e6db74">&#34;Mysqlx.Connection.CapabilitiesGet&#34;</span>);
</span></span><span style="display:flex;"><span>    client_message<span style="color:#f92672">&lt;</span>Mysqlx<span style="color:#f92672">::</span>Connection<span style="color:#f92672">::</span>CapabilitiesSet<span style="color:#f92672">&gt;</span>(Mysqlx<span style="color:#f92672">::</span>ClientMessages<span style="color:#f92672">::</span>CON_CAPABILITIES_SET, <span style="color:#e6db74">&#34;CON_CAPABILITIES_SET&#34;</span>, <span style="color:#e6db74">&#34;Mysqlx.Connection.CapabilitiesSet&#34;</span>);
</span></span><span style="display:flex;"><span>    client_message<span style="color:#f92672">&lt;</span>Mysqlx<span style="color:#f92672">::</span>Connection<span style="color:#f92672">::</span>Close<span style="color:#f92672">&gt;</span>(Mysqlx<span style="color:#f92672">::</span>ClientMessages<span style="color:#f92672">::</span>CON_CLOSE, <span style="color:#e6db74">&#34;CON_CLOSE&#34;</span>, <span style="color:#e6db74">&#34;Mysqlx.Connection.Close&#34;</span>);
</span></span><span style="display:flex;"><span>    client_message<span style="color:#f92672">&lt;</span>Mysqlx<span style="color:#f92672">::</span>Crud<span style="color:#f92672">::</span>Delete<span style="color:#f92672">&gt;</span>(Mysqlx<span style="color:#f92672">::</span>ClientMessages<span style="color:#f92672">::</span>CRUD_DELETE, <span style="color:#e6db74">&#34;CRUD_DELETE&#34;</span>, <span style="color:#e6db74">&#34;Mysqlx.Crud.Delete&#34;</span>);
</span></span><span style="display:flex;"><span>    client_message<span style="color:#f92672">&lt;</span>Mysqlx<span style="color:#f92672">::</span>Crud<span style="color:#f92672">::</span>Find<span style="color:#f92672">&gt;</span>(Mysqlx<span style="color:#f92672">::</span>ClientMessages<span style="color:#f92672">::</span>CRUD_FIND, <span style="color:#e6db74">&#34;CRUD_FIND&#34;</span>, <span style="color:#e6db74">&#34;Mysqlx.Crud.Find&#34;</span>);
</span></span><span style="display:flex;"><span>    client_message<span style="color:#f92672">&lt;</span>Mysqlx<span style="color:#f92672">::</span>Crud<span style="color:#f92672">::</span>Insert<span style="color:#f92672">&gt;</span>(Mysqlx<span style="color:#f92672">::</span>ClientMessages<span style="color:#f92672">::</span>CRUD_INSERT, <span style="color:#e6db74">&#34;CRUD_INSERT&#34;</span>, <span style="color:#e6db74">&#34;Mysqlx.Crud.Insert&#34;</span>);
</span></span><span style="display:flex;"><span>    client_message<span style="color:#f92672">&lt;</span>Mysqlx<span style="color:#f92672">::</span>Crud<span style="color:#f92672">::</span>Update<span style="color:#f92672">&gt;</span>(Mysqlx<span style="color:#f92672">::</span>ClientMessages<span style="color:#f92672">::</span>CRUD_UPDATE, <span style="color:#e6db74">&#34;CRUD_UPDATE&#34;</span>, <span style="color:#e6db74">&#34;Mysqlx.Crud.Update&#34;</span>);
</span></span><span style="display:flex;"><span>    client_message<span style="color:#f92672">&lt;</span>Mysqlx<span style="color:#f92672">::</span>Crud<span style="color:#f92672">::</span>CreateView<span style="color:#f92672">&gt;</span>(Mysqlx<span style="color:#f92672">::</span>ClientMessages<span style="color:#f92672">::</span>CRUD_CREATE_VIEW, <span style="color:#e6db74">&#34;CRUD_CREATE_VIEW&#34;</span>, <span style="color:#e6db74">&#34;Mysqlx.Crud.CreateView&#34;</span>);
</span></span><span style="display:flex;"><span>    client_message<span style="color:#f92672">&lt;</span>Mysqlx<span style="color:#f92672">::</span>Crud<span style="color:#f92672">::</span>ModifyView<span style="color:#f92672">&gt;</span>(Mysqlx<span style="color:#f92672">::</span>ClientMessages<span style="color:#f92672">::</span>CRUD_MODIFY_VIEW, <span style="color:#e6db74">&#34;CRUD_MODIFY_VIEW&#34;</span>, <span style="color:#e6db74">&#34;Mysqlx.Crud.ModifyView&#34;</span>);
</span></span><span style="display:flex;"><span>    client_message<span style="color:#f92672">&lt;</span>Mysqlx<span style="color:#f92672">::</span>Crud<span style="color:#f92672">::</span>DropView<span style="color:#f92672">&gt;</span>(Mysqlx<span style="color:#f92672">::</span>ClientMessages<span style="color:#f92672">::</span>CRUD_DROP_VIEW, <span style="color:#e6db74">&#34;CRUD_DROP_VIEW&#34;</span>, <span style="color:#e6db74">&#34;Mysqlx.Crud.DropView&#34;</span>);
</span></span><span style="display:flex;"><span>    client_message<span style="color:#f92672">&lt;</span>Mysqlx<span style="color:#f92672">::</span>Expect<span style="color:#f92672">::</span>Close<span style="color:#f92672">&gt;</span>(Mysqlx<span style="color:#f92672">::</span>ClientMessages<span style="color:#f92672">::</span>EXPECT_CLOSE, <span style="color:#e6db74">&#34;EXPECT_CLOSE&#34;</span>, <span style="color:#e6db74">&#34;Mysqlx.Expect.Close&#34;</span>);
</span></span><span style="display:flex;"><span>    client_message<span style="color:#f92672">&lt;</span>Mysqlx<span style="color:#f92672">::</span>Expect<span style="color:#f92672">::</span>Open<span style="color:#f92672">&gt;</span>(Mysqlx<span style="color:#f92672">::</span>ClientMessages<span style="color:#f92672">::</span>EXPECT_OPEN, <span style="color:#e6db74">&#34;EXPECT_OPEN&#34;</span>, <span style="color:#e6db74">&#34;Mysqlx.Expect.Open&#34;</span>);
</span></span><span style="display:flex;"><span>    client_message<span style="color:#f92672">&lt;</span>Mysqlx<span style="color:#f92672">::</span>Session<span style="color:#f92672">::</span>AuthenticateContinue<span style="color:#f92672">&gt;</span>(Mysqlx<span style="color:#f92672">::</span>ClientMessages<span style="color:#f92672">::</span>SESS_AUTHENTICATE_CONTINUE, <span style="color:#e6db74">&#34;SESS_AUTHENTICATE_CONTINUE&#34;</span>, <span style="color:#e6db74">&#34;Mysqlx.Session.AuthenticateContinue&#34;</span>);
</span></span><span style="display:flex;"><span>    client_message<span style="color:#f92672">&lt;</span>Mysqlx<span style="color:#f92672">::</span>Session<span style="color:#f92672">::</span>AuthenticateStart<span style="color:#f92672">&gt;</span>(Mysqlx<span style="color:#f92672">::</span>ClientMessages<span style="color:#f92672">::</span>SESS_AUTHENTICATE_START, <span style="color:#e6db74">&#34;SESS_AUTHENTICATE_START&#34;</span>, <span style="color:#e6db74">&#34;Mysqlx.Session.AuthenticateStart&#34;</span>);
</span></span><span style="display:flex;"><span>    client_message<span style="color:#f92672">&lt;</span>Mysqlx<span style="color:#f92672">::</span>Session<span style="color:#f92672">::</span>Close<span style="color:#f92672">&gt;</span>(Mysqlx<span style="color:#f92672">::</span>ClientMessages<span style="color:#f92672">::</span>SESS_CLOSE, <span style="color:#e6db74">&#34;SESS_CLOSE&#34;</span>, <span style="color:#e6db74">&#34;Mysqlx.Session.Close&#34;</span>);
</span></span><span style="display:flex;"><span>    client_message<span style="color:#f92672">&lt;</span>Mysqlx<span style="color:#f92672">::</span>Session<span style="color:#f92672">::</span>Reset<span style="color:#f92672">&gt;</span>(Mysqlx<span style="color:#f92672">::</span>ClientMessages<span style="color:#f92672">::</span>SESS_RESET, <span style="color:#e6db74">&#34;SESS_RESET&#34;</span>, <span style="color:#e6db74">&#34;Mysqlx.Session.Reset&#34;</span>);
</span></span><span style="display:flex;"><span>    client_message<span style="color:#f92672">&lt;</span>Mysqlx<span style="color:#f92672">::</span>Sql<span style="color:#f92672">::</span>StmtExecute<span style="color:#f92672">&gt;</span>(Mysqlx<span style="color:#f92672">::</span>ClientMessages<span style="color:#f92672">::</span>SQL_STMT_EXECUTE, <span style="color:#e6db74">&#34;SQL_STMT_EXECUTE&#34;</span>, <span style="color:#e6db74">&#34;Mysqlx.Sql.StmtExecute&#34;</span>);
</span></span><span style="display:flex;"><span>  }
</span></span></code></pre></div><p>Server and client messages are that many. Client messages are dispatched in <code>xpl_dispatcher.cc</code>.</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-c++" data-lang="c++"><span style="display:flex;"><span>ngs<span style="color:#f92672">::</span>Error_code do_dispatch_command(xpl<span style="color:#f92672">::</span>Session <span style="color:#f92672">&amp;</span>session, xpl<span style="color:#f92672">::</span>Crud_command_handler <span style="color:#f92672">&amp;</span>crudh,
</span></span><span style="display:flex;"><span>                                    xpl<span style="color:#f92672">::</span>Expectation_stack <span style="color:#f92672">&amp;</span>expect, ngs<span style="color:#f92672">::</span>Request <span style="color:#f92672">&amp;</span>command)
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">switch</span> (command.get_type())
</span></span><span style="display:flex;"><span>  {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> Mysqlx<span style="color:#f92672">::</span>ClientMessages<span style="color:#f92672">::</span>SQL_STMT_EXECUTE:
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">return</span> on_stmt_execute(session, <span style="color:#66d9ef">static_cast</span><span style="color:#f92672">&lt;</span><span style="color:#66d9ef">const</span> Mysqlx<span style="color:#f92672">::</span>Sql<span style="color:#f92672">::</span>StmtExecute<span style="color:#f92672">&amp;&gt;</span>(<span style="color:#f92672">*</span>command.message()));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> Mysqlx<span style="color:#f92672">::</span>ClientMessages<span style="color:#f92672">::</span>CRUD_FIND:
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">return</span> crudh.execute_crud_find(session, <span style="color:#66d9ef">static_cast</span><span style="color:#f92672">&lt;</span><span style="color:#66d9ef">const</span> Mysqlx<span style="color:#f92672">::</span>Crud<span style="color:#f92672">::</span>Find<span style="color:#f92672">&amp;&gt;</span>(<span style="color:#f92672">*</span>command.message()));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> Mysqlx<span style="color:#f92672">::</span>ClientMessages<span style="color:#f92672">::</span>CRUD_INSERT:
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">return</span> crudh.execute_crud_insert(session, <span style="color:#66d9ef">static_cast</span><span style="color:#f92672">&lt;</span><span style="color:#66d9ef">const</span> Mysqlx<span style="color:#f92672">::</span>Crud<span style="color:#f92672">::</span>Insert<span style="color:#f92672">&amp;&gt;</span>(<span style="color:#f92672">*</span>command.message()));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> Mysqlx<span style="color:#f92672">::</span>ClientMessages<span style="color:#f92672">::</span>CRUD_UPDATE:
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">return</span> crudh.execute_crud_update(session, <span style="color:#66d9ef">static_cast</span><span style="color:#f92672">&lt;</span><span style="color:#66d9ef">const</span> Mysqlx<span style="color:#f92672">::</span>Crud<span style="color:#f92672">::</span>Update<span style="color:#f92672">&amp;&gt;</span>(<span style="color:#f92672">*</span>command.message()));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> Mysqlx<span style="color:#f92672">::</span>ClientMessages<span style="color:#f92672">::</span>CRUD_DELETE:
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">return</span> crudh.execute_crud_delete(session, <span style="color:#66d9ef">static_cast</span><span style="color:#f92672">&lt;</span><span style="color:#66d9ef">const</span> Mysqlx<span style="color:#f92672">::</span>Crud<span style="color:#f92672">::</span>Delete<span style="color:#f92672">&amp;&gt;</span>(<span style="color:#f92672">*</span>command.message()));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> Mysqlx<span style="color:#f92672">::</span>ClientMessages<span style="color:#f92672">::</span>CRUD_CREATE_VIEW:
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">return</span> crudh.execute_create_view(session, <span style="color:#66d9ef">static_cast</span><span style="color:#f92672">&lt;</span><span style="color:#66d9ef">const</span> Mysqlx<span style="color:#f92672">::</span>Crud<span style="color:#f92672">::</span>CreateView<span style="color:#f92672">&amp;&gt;</span>(<span style="color:#f92672">*</span>command.message()));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> Mysqlx<span style="color:#f92672">::</span>ClientMessages<span style="color:#f92672">::</span>CRUD_MODIFY_VIEW:
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">return</span> crudh.execute_modify_view(session, <span style="color:#66d9ef">static_cast</span><span style="color:#f92672">&lt;</span><span style="color:#66d9ef">const</span> Mysqlx<span style="color:#f92672">::</span>Crud<span style="color:#f92672">::</span>ModifyView<span style="color:#f92672">&amp;&gt;</span>(<span style="color:#f92672">*</span>command.message()));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> Mysqlx<span style="color:#f92672">::</span>ClientMessages<span style="color:#f92672">::</span>CRUD_DROP_VIEW:
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">return</span> crudh.execute_drop_view(session, <span style="color:#66d9ef">static_cast</span><span style="color:#f92672">&lt;</span><span style="color:#66d9ef">const</span> Mysqlx<span style="color:#f92672">::</span>Crud<span style="color:#f92672">::</span>DropView<span style="color:#f92672">&amp;&gt;</span>(<span style="color:#f92672">*</span>command.message()));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> Mysqlx<span style="color:#f92672">::</span>ClientMessages<span style="color:#f92672">::</span>EXPECT_OPEN:
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">return</span> on_expect_open(session, expect, <span style="color:#66d9ef">static_cast</span><span style="color:#f92672">&lt;</span><span style="color:#66d9ef">const</span> Mysqlx<span style="color:#f92672">::</span>Expect<span style="color:#f92672">::</span>Open<span style="color:#f92672">&amp;&gt;</span>(<span style="color:#f92672">*</span>command.message()));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> Mysqlx<span style="color:#f92672">::</span>ClientMessages<span style="color:#f92672">::</span>EXPECT_CLOSE:
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">return</span> on_expect_close(session, expect, <span style="color:#66d9ef">static_cast</span><span style="color:#f92672">&lt;</span><span style="color:#66d9ef">const</span> Mysqlx<span style="color:#f92672">::</span>Expect<span style="color:#f92672">::</span>Close<span style="color:#f92672">&amp;&gt;</span>(<span style="color:#f92672">*</span>command.message()));
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  session.proto().get_protocol_monitor().on_error_unknown_msg_type();
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> ngs<span style="color:#f92672">::</span>Error(ER_UNKNOWN_COM_ERROR, <span style="color:#e6db74">&#34;Unexpected message received&#34;</span>);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The rest is filling in the gaps.</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>Client::run =&gt; Client::handle_message =&gt; Session::handle_message =&gt; Session::handle_auth_message =&gt; some auth handlers
</span></span><span style="display:flex;"><span>                                                                 =&gt; Session::handle_ready_message =&gt; xpl::dispatcher::dispatch_command =&gt; ngs::Error_code do_dispatch_command =&gt; some crud handlers
</span></span></code></pre></div><p>Mapping between MySQL type and X protocol type</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>//     ================= ============ ======= ========== ====== ========
</span></span><span style="display:flex;"><span>//     SQL Type          .type        .length .frac_dig  .flags .charset
</span></span><span style="display:flex;"><span>//     ================= ============ ======= ========== ====== ========
</span></span><span style="display:flex;"><span>//     TINY              SINT         x
</span></span><span style="display:flex;"><span>//     TINY UNSIGNED     UINT         x                  x
</span></span><span style="display:flex;"><span>//     SHORT             SINT         x
</span></span><span style="display:flex;"><span>//     SHORT UNSIGNED    UINT         x                  x
</span></span><span style="display:flex;"><span>//     INT24             SINT         x
</span></span><span style="display:flex;"><span>//     INT24 UNSIGNED    UINT         x                  x
</span></span><span style="display:flex;"><span>//     INT               SINT         x
</span></span><span style="display:flex;"><span>//     INT UNSIGNED      UINT         x                  x
</span></span><span style="display:flex;"><span>//     LONGLONG          SINT         x
</span></span><span style="display:flex;"><span>//     LONGLONG UNSIGNED UINT         x                  x
</span></span><span style="display:flex;"><span>//     DOUBLE            DOUBLE       x       x          x
</span></span><span style="display:flex;"><span>//     FLOAT             FLOAT        x       x          x
</span></span><span style="display:flex;"><span>//     DECIMAL           DECIMAL      x       x          x
</span></span><span style="display:flex;"><span>//     VARCHAR,CHAR,...  BYTES        x                  x      x
</span></span><span style="display:flex;"><span>//     GEOMETRY          BYTES
</span></span><span style="display:flex;"><span>//     TIME              TIME         x
</span></span><span style="display:flex;"><span>//     DATE              DATETIME     x
</span></span><span style="display:flex;"><span>//     DATETIME          DATETIME     x
</span></span><span style="display:flex;"><span>//     YEAR              UINT         x                  x
</span></span><span style="display:flex;"><span>//     TIMESTAMP         DATETIME     x
</span></span><span style="display:flex;"><span>//     SET               SET                                    x
</span></span><span style="display:flex;"><span>//     ENUM              ENUM                                   x
</span></span><span style="display:flex;"><span>//     NULL              BYTES
</span></span><span style="display:flex;"><span>//     BIT               BIT          x
</span></span><span style="display:flex;"><span>//     ================= ============ ======= ========== ====== ========
</span></span></code></pre></div><p>The first SQL field information of MySQL:</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>Field   1:  `@@lower_case_table_names`
</span></span><span style="display:flex;"><span>Catalog:    `def`
</span></span><span style="display:flex;"><span>Database:   ``
</span></span><span style="display:flex;"><span>Table:      ``
</span></span><span style="display:flex;"><span>Org_table:  ``
</span></span><span style="display:flex;"><span>Type:       LONGLONG
</span></span><span style="display:flex;"><span>Collation:  binary (63)
</span></span><span style="display:flex;"><span>Length:     21
</span></span><span style="display:flex;"><span>Max_length: 1
</span></span><span style="display:flex;"><span>Decimals:   0
</span></span><span style="display:flex;"><span>Flags:      UNSIGNED BINARY NUM 
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Field   2:  `connection_id()`
</span></span><span style="display:flex;"><span>Catalog:    `def`
</span></span><span style="display:flex;"><span>Database:   ``
</span></span><span style="display:flex;"><span>Table:      ``
</span></span><span style="display:flex;"><span>Org_table:  ``
</span></span><span style="display:flex;"><span>Type:       LONGLONG
</span></span><span style="display:flex;"><span>Collation:  binary (63)
</span></span><span style="display:flex;"><span>Length:     21
</span></span><span style="display:flex;"><span>Max_length: 1
</span></span><span style="display:flex;"><span>Decimals:   0
</span></span><span style="display:flex;"><span>Flags:      NOT_NULL UNSIGNED BINARY NUM 
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Field   3:  `variable_value`
</span></span><span style="display:flex;"><span>Catalog:    `def`
</span></span><span style="display:flex;"><span>Database:   `performance_schema`
</span></span><span style="display:flex;"><span>Table:      `session_status`
</span></span><span style="display:flex;"><span>Org_table:  `session_status`
</span></span><span style="display:flex;"><span>Type:       VAR_STRING
</span></span><span style="display:flex;"><span>Collation:  utf8_general_ci (33)
</span></span><span style="display:flex;"><span>Length:     3072
</span></span><span style="display:flex;"><span>Max_length: 0
</span></span><span style="display:flex;"><span>Decimals:   0
</span></span><span style="display:flex;"><span>Flags:      
</span></span></code></pre></div><p>For TiDB:</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>Field   1:  `@@lower_case_table_names`
</span></span><span style="display:flex;"><span>Catalog:    `def`
</span></span><span style="display:flex;"><span>Database:   ``
</span></span><span style="display:flex;"><span>Table:      ``
</span></span><span style="display:flex;"><span>Org_table:  ``
</span></span><span style="display:flex;"><span>Type:       STRING
</span></span><span style="display:flex;"><span>Collation:  ? (0)
</span></span><span style="display:flex;"><span>Length:     0
</span></span><span style="display:flex;"><span>Max_length: 1
</span></span><span style="display:flex;"><span>Decimals:   31
</span></span><span style="display:flex;"><span>Flags:      
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Field   2:  `connection_id()`
</span></span><span style="display:flex;"><span>Catalog:    `def`
</span></span><span style="display:flex;"><span>Database:   ``
</span></span><span style="display:flex;"><span>Table:      ``
</span></span><span style="display:flex;"><span>Org_table:  ``
</span></span><span style="display:flex;"><span>Type:       LONGLONG
</span></span><span style="display:flex;"><span>Collation:  binary (63)
</span></span><span style="display:flex;"><span>Length:     20
</span></span><span style="display:flex;"><span>Max_length: 1
</span></span><span style="display:flex;"><span>Decimals:   0
</span></span><span style="display:flex;"><span>Flags:      UNSIGNED BINARY NUM 
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Field   3:  `variable_value`
</span></span><span style="display:flex;"><span>Catalog:    `def`
</span></span><span style="display:flex;"><span>Database:   ``
</span></span><span style="display:flex;"><span>Table:      ``
</span></span><span style="display:flex;"><span>Org_table:  ``
</span></span><span style="display:flex;"><span>Type:       STRING
</span></span><span style="display:flex;"><span>Collation:  utf8_general_ci (33)
</span></span><span style="display:flex;"><span>Length:     1024
</span></span><span style="display:flex;"><span>Max_length: 0
</span></span><span style="display:flex;"><span>Decimals:   0
</span></span><span style="display:flex;"><span>Flags:      
</span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>How to Configure CentOS 6 NFS Service</title>
      <link>https://blog.minifish.org/posts/how-to-configure-centos-6-nfs-service/</link>
      <pubDate>Thu, 05 Jun 2014 22:21:06 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-configure-centos-6-nfs-service/</guid>
      <description>1. Disable SeLinux Edit the configuration file:</description>
      <content:encoded><![CDATA[<h2 id="server-side">Server Side</h2>
<ol>
<li>
<p><strong>Disable SeLinux</strong><br>
Edit the configuration file:</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-bash" data-lang="bash"><span style="display:flex;"><span>vi /etc/selinux/config
</span></span></code></pre></div><p>Modify as follows:</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>#SELINUX=enforcing    # Comment out
</span></span><span style="display:flex;"><span>#SELINUXTYPE=targeted # Comment out
</span></span><span style="display:flex;"><span>SELINUX=disabled      # Add this line
</span></span></code></pre></div><p>Then reboot the system:</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-bash" data-lang="bash"><span style="display:flex;"><span>reboot  <span style="color:#75715e"># Restart the system</span>
</span></span></code></pre></div></li>
<li>
<p><strong>Create a Directory</strong><br>
Using the root user, create a directory named <code>/nfs</code>. Note: It&rsquo;s best to check which partition has the most space by running <code>df</code>, as the root (<code>/</code>) partition may not have the most space. In some automatic partitioning setups, the <code>/home</code> partition may have the most space.</p>
</li>
<li>
<p><strong>Install NFS Utilities and RPC Bind</strong></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-bash" data-lang="bash"><span style="display:flex;"><span>yum -y install nfs-utils rpcbind
</span></span></code></pre></div></li>
<li>
<p><strong>Enable Services at Boot</strong></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-bash" data-lang="bash"><span style="display:flex;"><span>chkconfig nfs on
</span></span><span style="display:flex;"><span>chkconfig rpcbind on
</span></span><span style="display:flex;"><span>chkconfig nfslock on
</span></span></code></pre></div></li>
<li>
<p><strong>Configure Exports</strong><br>
Edit the NFS exports file:</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-bash" data-lang="bash"><span style="display:flex;"><span>vi /etc/exports
</span></span></code></pre></div><p>Add the following line:</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>/home/nfs 192.168.1.0/24(rw,sync,no_all_squash)
</span></span></code></pre></div></li>
<li>
<p><strong>Start NFS Services</strong></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-bash" data-lang="bash"><span style="display:flex;"><span>service rpcbind start
</span></span><span style="display:flex;"><span>service nfs start
</span></span><span style="display:flex;"><span>service nfslock start
</span></span><span style="display:flex;"><span>exportfs -a
</span></span></code></pre></div></li>
<li>
<p><strong>Configure NFS Ports</strong><br>
Edit the NFS configuration file:</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-bash" data-lang="bash"><span style="display:flex;"><span>vi /etc/sysconfig/nfs
</span></span></code></pre></div><p>Uncomment the following lines:</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>LOCKD_TCPPORT=32803
</span></span><span style="display:flex;"><span>LOCKD_UDPPORT=32769
</span></span><span style="display:flex;"><span>MOUNTD_PORT=892
</span></span></code></pre></div></li>
<li>
<p><strong>Restart NFS Services</strong></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-bash" data-lang="bash"><span style="display:flex;"><span>service rpcbind restart
</span></span><span style="display:flex;"><span>service nfs restart
</span></span><span style="display:flex;"><span>service nfslock restart
</span></span></code></pre></div></li>
<li>
<p><strong>Verify RPC Services</strong></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-bash" data-lang="bash"><span style="display:flex;"><span>rpcinfo -p localhost
</span></span></code></pre></div><p>Note down the ports and their types.</p>
</li>
<li>
<p><strong>Configure Firewall Rules</strong><br>
Adjust the IP range according to your network:</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-bash" data-lang="bash"><span style="display:flex;"><span>iptables -I INPUT -m state --state NEW -p tcp -m multiport --dport 111,892,2049,32803 -s 192.168.0.0/24 -j ACCEPT
</span></span><span style="display:flex;"><span>iptables -I INPUT -m state --state NEW -p udp -m multiport --dport 111,892,2049,32769 -s 192.168.0.0/24 -j ACCEPT
</span></span></code></pre></div></li>
<li>
<p><strong>Save Firewall Rules</strong><br>
Test from the client side. If successful, save the iptables configuration:</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-bash" data-lang="bash"><span style="display:flex;"><span>service iptables save
</span></span></code></pre></div></li>
</ol>
<h2 id="client-side">Client Side</h2>
<ol>
<li>
<p><strong>Create Mount Point</strong></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-bash" data-lang="bash"><span style="display:flex;"><span>mkdir /nfs
</span></span></code></pre></div></li>
<li>
<p><strong>Check RPC Services on Server</strong></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-bash" data-lang="bash"><span style="display:flex;"><span>rpcinfo -p <span style="color:#f92672">[</span>server_ip<span style="color:#f92672">]</span>
</span></span></code></pre></div></li>
<li>
<p><strong>Show NFS Exports</strong></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-bash" data-lang="bash"><span style="display:flex;"><span>showmount -e <span style="color:#f92672">[</span>server_ip<span style="color:#f92672">]</span>
</span></span></code></pre></div></li>
<li>
<p><strong>Mount NFS Share</strong></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-bash" data-lang="bash"><span style="display:flex;"><span>mount -t nfs -o soft,intr,bg,rw <span style="color:#f92672">[</span>server_ip<span style="color:#f92672">]</span>:/home/nfs /nfs
</span></span></code></pre></div></li>
<li>
<p><strong>Unmount NFS Share</strong></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-bash" data-lang="bash"><span style="display:flex;"><span>umount /nfs
</span></span></code></pre></div></li>
<li>
<p><strong>Configure Automatic Mounting</strong><br>
Edit the fstab file:</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-bash" data-lang="bash"><span style="display:flex;"><span>vi /etc/fstab
</span></span></code></pre></div><p>Add the following line:</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>[server_ip]:/home/nfs /nfs nfs soft,intr,bg,rw 0 0
</span></span></code></pre></div></li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>How to Configure CentOS KVM Network Bridging Mode</title>
      <link>https://blog.minifish.org/posts/how-to-configure-centos-kvm-network-bridging-mode/</link>
      <pubDate>Thu, 05 Jun 2014 22:21:06 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-configure-centos-kvm-network-bridging-mode/</guid>
      <description>Bridging highly simulates a network card, making the router believe that the virtual machine&amp;#39;s network card truly exists. Personally, I feel it&amp;#39;s similar to resistors connected in...</description>
      <content:encoded><![CDATA[<h2 id="what-is-bridging">What Is Bridging</h2>
<p>Bridging highly simulates a network card, making the router believe that the virtual machine&rsquo;s network card truly exists. Personally, I feel it&rsquo;s similar to resistors connected in parallel, whereas NAT (another common virtual machine network connection method) is more like parasitizing on the host&rsquo;s network card.</p>
<h2 id="why-use-bridging">Why Use Bridging</h2>
<p>It allows you to treat the virtual machine as a completely independent machine, enabling mutual access with the external network (which is not possible with NAT).</p>
<h2 id="how-to-configure-bridging">How to Configure Bridging</h2>
<p>In CentOS 6, refer to the command-line method in <a href="http://www.techotopia.com/index.php/Creating_a_CentOS_6_KVM_Networked_Bridge_Interface">this article</a>.</p>
<p>We don&rsquo;t use the GUI method because:</p>
<ul>
<li>We&rsquo;re unsure which options to fill in on the last screen.</li>
<li>We don&rsquo;t know how to reset if we make a wrong selection.</li>
</ul>
<p>Command-line steps:</p>
<ol>
<li>
<p><strong>Check if <code>bridge-utils</code> is installed:</strong></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-bash" data-lang="bash"><span style="display:flex;"><span>rpm -q bridge-utils
</span></span></code></pre></div><p>Usually, it&rsquo;s already installed. If not, install it:</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-bash" data-lang="bash"><span style="display:flex;"><span>su -
</span></span><span style="display:flex;"><span>yum install bridge-utils
</span></span></code></pre></div></li>
<li>
<p><strong>Verify your network interfaces:</strong></p>
<p>Run <code>ifconfig</code> to ensure you have at least three network interfaces:</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>eth0      Link encap:Ethernet  HWaddr 00:18:E7:16:DA:65
</span></span><span style="display:flex;"><span>          inet addr:192.168.0.117  Bcast:192.168.0.255  Mask:255.255.255.0
</span></span><span style="display:flex;"><span>          inet6 addr: fe80::218:e7ff:fe16:da65/64 Scope:Link
</span></span><span style="display:flex;"><span>          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
</span></span><span style="display:flex;"><span>          RX packets:556 errors:0 dropped:0 overruns:0 frame:0
</span></span><span style="display:flex;"><span>          TX packets:414 errors:0 dropped:0 overruns:0 carrier:0
</span></span><span style="display:flex;"><span>          collisions:0 txqueuelen:1000
</span></span><span style="display:flex;"><span>          RX bytes:222834 (217.6 KiB)  TX bytes:48430 (47.2 KiB)
</span></span><span style="display:flex;"><span>          Interrupt:16 Base address:0x4f00
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>lo        Link encap:Local Loopback
</span></span><span style="display:flex;"><span>          inet addr:127.0.0.1  Mask:255.0.0.0
</span></span><span style="display:flex;"><span>          inet6 addr: ::1/128 Scope:Host
</span></span><span style="display:flex;"><span>          UP LOOPBACK RUNNING  MTU:16436  Metric:1
</span></span><span style="display:flex;"><span>          RX packets:8 errors:0 dropped:0 overruns:0 frame:0
</span></span><span style="display:flex;"><span>          TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
</span></span><span style="display:flex;"><span>          collisions:0 txqueuelen:0
</span></span><span style="display:flex;"><span>          RX bytes:480 (480.0 b)  TX bytes:480 (480.0 b)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>virbr0    Link encap:Ethernet  HWaddr 52:54:00:2A:C1:7E
</span></span><span style="display:flex;"><span>          inet addr:192.168.122.1  Bcast:192.168.122.255  Mask:255.255.255.0
</span></span><span style="display:flex;"><span>          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
</span></span><span style="display:flex;"><span>          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
</span></span><span style="display:flex;"><span>          TX packets:13 errors:0 dropped:0 overruns:0 carrier:0
</span></span><span style="display:flex;"><span>          collisions:0 txqueuelen:0
</span></span><span style="display:flex;"><span>          RX bytes:0 (0.0 b)  TX bytes:2793 (2.7 KiB)
</span></span></code></pre></div></li>
<li>
<p><strong>Navigate to the network scripts directory:</strong></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-bash" data-lang="bash"><span style="display:flex;"><span>su –
</span></span><span style="display:flex;"><span>cd /etc/sysconfig/network-scripts
</span></span></code></pre></div></li>
<li>
<p><strong>Bring down the <code>eth0</code> interface:</strong></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-bash" data-lang="bash"><span style="display:flex;"><span>ifdown eth0
</span></span></code></pre></div><p><em>This step is crucial and must be performed locally.</em> When I first configured this, I didn&rsquo;t shut down the network (since I was working remotely). I didn&rsquo;t realize that updating the <code>ifcfg-eth0</code> configuration without restarting the network would immediately apply changes, resulting in loss of network connectivity.</p>
</li>
<li>
<p><strong>Edit <code>ifcfg-eth0</code>:</strong></p>
<p>In the <code>ifcfg-eth0</code> file, include:</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>DEVICE=eth0
</span></span><span style="display:flex;"><span>ONBOOT=yes
</span></span><span style="display:flex;"><span>BRIDGE=br0
</span></span></code></pre></div><p>Keep only these three lines in the file. There&rsquo;s no need to configure an IP address here. Bridging seems to replace the original network card with the bridge, so you can delegate the configuration to the bridge.</p>
</li>
<li>
<p><strong>Create a new file <code>ifcfg-br0</code>:</strong></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>DEVICE=br0
</span></span><span style="display:flex;"><span>ONBOOT=yes
</span></span><span style="display:flex;"><span>TYPE=Bridge
</span></span><span style="display:flex;"><span>BOOTPROTO=static
</span></span><span style="display:flex;"><span>IPADDR=xxx.xxx.xxx.xxx   # Use the IP you originally had in ifcfg-eth0
</span></span><span style="display:flex;"><span>GATEWAY=xxx.xxx.xxx.xxx  # Your gateway address
</span></span><span style="display:flex;"><span>NETMASK=255.255.255.0    # Your netmask
</span></span><span style="display:flex;"><span>DNS1=xxx.xxx.xxx.xxx     # Your primary DNS server
</span></span><span style="display:flex;"><span>DNS2=xxx.xxx.xxx.xxx     # Your secondary DNS server (if any)
</span></span><span style="display:flex;"><span>STP=on
</span></span><span style="display:flex;"><span>DELAY=0
</span></span></code></pre></div><p><em>Note:</em> Replace <code>xxx.xxx.xxx.xxx</code> with your actual network settings.</p>
</li>
<li>
<p><strong>Bring up the interfaces:</strong></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-bash" data-lang="bash"><span style="display:flex;"><span>ifup br0
</span></span><span style="display:flex;"><span>ifup eth0
</span></span></code></pre></div></li>
<li>
<p><strong>Verify the bridge interface:</strong></p>
<p>Check <code>ifconfig</code> to ensure that <code>br0</code> is now present.</p>
</li>
<li>
<p><strong>Update firewall rules:</strong></p>
<p>Edit <code>/etc/sysconfig/iptables</code> and add:</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>-A INPUT -i br0 -j ACCEPT
</span></span></code></pre></div><p><em>(This is a general example; you may need to adjust it based on your specific firewall configuration.)</em></p>
</li>
<li>
<p><strong>Restart the firewall:</strong></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-bash" data-lang="bash"><span style="display:flex;"><span>service iptables restart
</span></span></code></pre></div></li>
<li>
<p><strong>Configure bridging in <code>virt-manager</code>:</strong></p>
<p>When creating a new virtual machine using <code>virt-manager</code>, you can now select <code>br0</code> for the network interface. Without this bridge, the bridging option would not be available.</p>
</li>
</ol>
<p><strong>Note:</strong> When configuring the IP inside the virtual machine, be sure to specify the <code>GATEWAY</code>. Otherwise, the virtual machine will only be able to access the internal network and not the external network. At this point, the virtual machine won&rsquo;t automatically discover the gateway.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to Install CentOS as a Virtualization Host</title>
      <link>https://blog.minifish.org/posts/how-to-install-centos-as-a-virtualization-host/</link>
      <pubDate>Thu, 05 Jun 2014 22:21:06 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-install-centos-as-a-virtualization-host/</guid>
      <description>Installed Version: CentOS 6.3</description>
      <content:encoded><![CDATA[<h2 id="installation-process">Installation Process</h2>
<p>Installed Version: CentOS 6.3</p>
<ol>
<li>Using Win32DiskImager to create a USB flash drive image was unsuccessful; installing from an external USB optical drive was successful.</li>
<li>During the installation process, make sure to select the &ldquo;Virtual Host&rdquo; installation mode.</li>
<li>The rest can be set to default or slightly modified, such as choosing the time zone.</li>
<li>After installation, it will include the KVM suite and SSH.</li>
</ol>
<h2 id="installation-notes">Installation Notes</h2>
<ul>
<li>No internet connection is needed throughout the process, which is much better than Debian and Ubuntu.</li>
<li>You&rsquo;re not forced to set up a non-root user.</li>
<li>Before installation, be sure to check whether your CPU supports virtualization and enable the motherboard&rsquo;s virtualization setting. If the motherboard supports virtualization but doesn&rsquo;t have a virtualization option, you can still use virtualization as it&rsquo;s definitely enabled by default. There&rsquo;s a saying that Intel CPUs with a &lsquo;K&rsquo; cannot perform virtualization. &lsquo;K&rsquo; means Intel CPUs that can be overclocked. It seems that faster and newer CPUs are not necessarily better.</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>How to Practice Using SQL</title>
      <link>https://blog.minifish.org/posts/how-to-practice-using-sql/</link>
      <pubDate>Thu, 05 Jun 2014 00:00:00 +0000</pubDate>
      <guid>https://blog.minifish.org/posts/how-to-practice-using-sql/</guid>
      <description>The text provided is a detailed set of instructions and queries for practicing SQL using PostgreSQL 9.4 BETA 2, focusing on creating and querying tables related to students,...</description>
      <content:encoded><![CDATA[<p>The text provided is a detailed set of instructions and queries for practicing SQL using PostgreSQL 9.4 BETA 2, focusing on creating and querying tables related to students, courses, scores, and teachers. Here&rsquo;s a summary:</p>
<h2 id="database-structure">Database Structure</h2>
<p>The database consists of four tables:</p>
<ol>
<li><strong>STUDENT</strong>: Contains student number (SNO), name (SNAME), gender (SSEX), birthday (SBIRTHDAY), and class (CLASS).</li>
<li><strong>COURSE</strong>: Includes course number (CNO), name (CNAME), and teacher number (TNO).</li>
<li><strong>SCORE</strong>: Records student number (SNO), course number (CNO), and degree (DEGREE).</li>
<li><strong>TEACHER</strong>: Holds teacher number (TNO), name (TNAME), gender (TSEX), birthday (TBIRTHDAY), professional title (PROF), and department (DEPART).</li>
</ol>
<h2 id="sample-data">Sample Data</h2>
<ul>
<li>Students such as Zeng Hua, Kang Ming, and Wang Fang are stored with specific details, including their class and gender.</li>
<li>Courses like &ldquo;Introduction to Computers&rdquo; and &ldquo;Operating Systems&rdquo; are associated with teacher numbers.</li>
<li>Scores are recorded for students across various courses.</li>
<li>Teachers are described with their professional roles and departments.</li>
</ul>
<h2 id="query-problems">Query Problems</h2>
<p>Several SQL queries are suggested for practice, such as:</p>
<ul>
<li>Extracting specific columns like SNAME, SSEX, and CLASS from the STUDENT table.</li>
<li>Listing distinct departments for teachers.</li>
<li>Calculating and sorting grades within the SCORE table.</li>
<li>Performing database operations to find student averages, count of students per class, and comparing scores.</li>
</ul>
<h2 id="advanced-query-exercises">Advanced Query Exercises</h2>
<ul>
<li>Performing set operations and conditional joins to answer complex questions like finding students who scored more than others or comparing teachers&rsquo; scores.</li>
<li>Use of SQL functions like <code>DATE_PART</code>, subqueries, and unions to gather specific data.</li>
</ul>
<h2 id="additional-queries">Additional Queries</h2>
<ul>
<li>Techniques to refine queries for performance, like avoiding the <code>NOT IN</code> method.</li>
<li>Handling conditions like age calculations using <code>AGE(SBIRTHDAY)</code> and filtering by name patterns.</li>
</ul>
<p>Overall, these exercises provide a robust framework for practicing SQL skills on a structured set of sample data, focusing on various database manipulation and retrieval techniques.7. <strong>Query</strong>:</p>
<pre><code>- `SELECT A.TNAME, B.CNAME FROM TEACHER A JOIN COURSE B ON A.TNO = B.TNO WHERE A.TSEX='男';`
- Explanation: Joins teacher and course tables to select male teachers and their course names.
</code></pre>
<ol>
<li>
<p><strong>Query</strong>:</p>
<ul>
<li><code>SELECT A.* FROM SCORE A WHERE DEGREE=(SELECT MAX(DEGREE) FROM SCORE B);</code></li>
<li>Explanation: Selects all columns from the highest score in the score table.</li>
</ul>
</li>
<li>
<p><strong>Query</strong>:</p>
<ul>
<li><code>SELECT SNAME FROM STUDENT A WHERE SSEX=(SELECT SSEX FROM STUDENT B WHERE B.SNAME='李军');</code></li>
<li>Explanation: Selects student names who have the same gender as the student named &lsquo;Li Jun.&rsquo;</li>
</ul>
</li>
<li>
<p><strong>Query</strong>:</p>
<ul>
<li><code>SELECT SNAME FROM STUDENT A WHERE SSEX=(SELECT SSEX FROM STUDENT B WHERE B.SNAME='李军') AND CLASS=(SELECT CLASS FROM STUDENT C WHERE C.SNAME='李军');</code></li>
<li>Explanation: Selects student names who have the same gender and class as the student named &lsquo;Li Jun.&rsquo;</li>
</ul>
</li>
<li>
<p><strong>Two Answers:</strong></p>
<ul>
<li><code>SELECT A.* FROM SCORE A JOIN STUDENT B ON A.SNO = B.SNO JOIN COURSE C ON A.CNO = C.CNO WHERE B.SSEX='男' AND C.CNAME='计算机导论';</code></li>
<li><code>SELECT * FROM SCORE WHERE SNO IN(SELECT SNO FROM STUDENT WHERE SSEX='男') AND CNO=(SELECT CNO FROM COURSE WHERE CNAME='计算机导论');</code></li>
<li>Explanation: Both queries select scores of male students for the course &lsquo;Introduction to Computer Science.&rsquo;</li>
</ul>
</li>
</ol>
]]></content:encoded>
    </item>
  </channel>
</rss>
