<?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.154.5</generator>
    <language>en-US</language>
    <copyright>Mini Fish 2014-present. Licensed under CC-BY-NC</copyright>
    <lastBuildDate>Mon, 19 Jan 2026 10:00:00 +0800</lastBuildDate>
    <atom:link href="https://blog.minifish.org/index.xml" rel="self" type="application/rss+xml" />
    <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>&lt;h2 id=&#34;the-problem-lost-context-lost-thoughts&#34;&gt;The Problem: Lost Context, Lost Thoughts&lt;/h2&gt;
&lt;p&gt;We&amp;rsquo;ve all been there. You&amp;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: &amp;ldquo;This would make a great blog post.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;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&amp;rsquo;re back, the original context is gone, and the momentum is lost.&lt;/p&gt;</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/github-action">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/[new-post].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;"><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/github-action">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/github-action">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/</code></li>
<li><strong>Format</strong>: YAML front matter + Markdown content</li>
<li><strong>Images</strong>: Stored in <code>content/posts/images/</code></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/github-action">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>&lt;h2 id=&#34;motivation-why-this-is-an-engineering-problem&#34;&gt;Motivation: Why This Is an Engineering Problem&lt;/h2&gt;
&lt;p&gt;Most Moka pot guides treat brewing as a &lt;em&gt;recipe problem&lt;/em&gt;:
grind size, water temperature, and heat level.&lt;/p&gt;
&lt;p&gt;That framing breaks down completely when you introduce:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;Brikka&lt;/strong&gt; (pressure valve + burst extraction)&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;induction hob&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;steel induction adapter plate&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At this point, you are no longer “brewing coffee”.
You are operating a &lt;strong&gt;multi-stage thermal + pressure system with delayed feedback&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This article reframes Brikka-on-induction as a &lt;strong&gt;control problem&lt;/strong&gt;, not a recipe.&lt;/p&gt;</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>&lt;h2 id=&#34;why-these-paths-matter&#34;&gt;Why These Paths Matter&lt;/h2&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Write transactions must be durable, replicated, and efficiently committed.&lt;/li&gt;
&lt;li&gt;Tablet replay must recover state quickly and safely.&lt;/li&gt;
&lt;li&gt;SQL parse to execute must optimize well while respecting multi-tenant constraints.&lt;/li&gt;
&lt;li&gt;Unit placement must map tenants to physical resources without fragmentation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This article focuses on motivation, design, implementation highlights, and tradeoffs, using concrete code entry points from the OceanBase codebase.&lt;/p&gt;</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>&lt;h2 id=&#34;what-is-webvm&#34;&gt;What is WebVM?&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/leaningtech/webvm&#34;&gt;WebVM&lt;/a&gt; is a virtual machine (VM) that executes entirely within a web browser. It&amp;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.&lt;/p&gt;
&lt;h2 id=&#34;understanding-webvm&#34;&gt;Understanding WebVM&lt;/h2&gt;
&lt;p&gt;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:&lt;/p&gt;</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/images/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;"><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>Brompton Clone Bike Modification</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>&lt;p&gt;I&amp;rsquo;m excited to share the final version of my &lt;strong&gt;Aceoffix 01&lt;/strong&gt;, a Brompton clone bike I&amp;rsquo;ve been customizing:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;20250110_181217_IMG_6530&#34; loading=&#34;lazy&#34; src=&#34;https://blog.minifish.org/posts/images/20250110_181217_IMG_6530.jpg&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;what-modifications-did-i-make&#34;&gt;What Modifications Did I Make?&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Handlebar Grips&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The original grips were glued on and became loose over time, so I replaced them with new, more secure ones.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Bell&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Safety first! I added a bell to alert pedestrians and other cyclists.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CNC Mount Base for Action Cameras&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Installed at the center of the handlebar, it&amp;rsquo;s perfect for mounting my DJI OSMO Pocket 1 or a phone holder.&lt;/p&gt;</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/images/20250110_181217_IMG_6530.jpg"></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/images/IMG_6529.jpg"></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/images/IMG_6531.jpg"></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>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;As a long-time podcaster, I&amp;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 &lt;a href=&#34;https://creators.spotify.com/&#34;&gt;&lt;strong&gt;Spotify Creator&lt;/strong&gt;&lt;/a&gt; (formerly &lt;strong&gt;Anchor&lt;/strong&gt;) 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.&lt;/p&gt;</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>&lt;p&gt;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.&lt;/p&gt;
&lt;h2 id=&#34;key-features&#34;&gt;Key Features&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Compact Size&lt;/strong&gt;: The trifold design results in a very small folded size.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lightweight&lt;/strong&gt;: Weighing between &lt;strong&gt;10 to 11 kg&lt;/strong&gt;, making them easy to carry.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easy Transportation&lt;/strong&gt;: Suitable for air travel, and can be taken on public transport like MRT and buses.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Thin Tires&lt;/strong&gt;: Facilitates easy movement and efficient riding.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Front Bracket&lt;/strong&gt;: Allows for quick changes of front bags.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Popularity&lt;/strong&gt;: Highly popular in Singapore.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Non-standard Accessories&lt;/strong&gt;: May require specific accessories due to unique design.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My bike is a Brompton clone, and I&amp;rsquo;ll share some insights on the various brands and models available.&lt;/p&gt;</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/images/20241210_093935_BAD44C59-7EF9-428A-8AA3-C81C46B01A9A.JPG"></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/images/20241209_213208_IMG_6425.jpg"></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/images/20241209_213223_IMG_6424.jpg"></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>&lt;p&gt;Recently, I decided to expand my collection of folding knives and purchased three models from &lt;strong&gt;&lt;a href=&#34;#&#34;&gt;Sanrenmu&lt;/a&gt;&lt;/strong&gt;. 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.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;sanrenmu-9201&#34;&gt;Sanrenmu 9201&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Price:&lt;/strong&gt; 198 Yuan&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lock Type:&lt;/strong&gt; Axis Lock&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Handle Material:&lt;/strong&gt; Plastic&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Blade Material:&lt;/strong&gt; D2 Steel&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;first-impressions&#34;&gt;First Impressions&lt;/h3&gt;
&lt;p&gt;The 9201 features an axis lock mechanism, which I found to be &lt;strong&gt;very smooth and easy to open and close&lt;/strong&gt;. The D2 steel blade offers good edge retention, and the plastic handle keeps the knife lightweight.&lt;/p&gt;</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>&lt;p&gt;When working with Go, it&amp;rsquo;s important to know the proper way to compile your programs to avoid common errors. Here are some tips on using the &lt;code&gt;go build&lt;/code&gt; command effectively.&lt;/p&gt;
&lt;h2 id=&#34;recommended-usage&#34;&gt;Recommended Usage&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Compile all Go files in the current directory:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;go build
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Compile all Go files explicitly:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;go build *.go
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;common-pitfalls&#34;&gt;Common Pitfalls&lt;/h2&gt;
&lt;h3 id=&#34;compiling-a-single-file&#34;&gt;Compiling a Single File&lt;/h3&gt;
&lt;p&gt;Running:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;go build main.go
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;will only build &lt;code&gt;main.go&lt;/code&gt;. This can lead to errors if &lt;code&gt;main.go&lt;/code&gt; depends on other Go files in the same package, as those files won&amp;rsquo;t be included in the build process.&lt;/p&gt;</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;"><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;"><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;"><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;"><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>&lt;p&gt;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&amp;rsquo;ll share my experiences with &lt;strong&gt;Ollama&lt;/strong&gt;, a remarkable tool for running local LLMs, along with other tools like &lt;strong&gt;llamaindex&lt;/strong&gt; and &lt;strong&gt;Candle&lt;/strong&gt;. I&amp;rsquo;ll also discuss various user interfaces (UI) that enhance the local LLM experience.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;table-of-contents&#34;&gt;Table of Contents&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#introduction-to-ollama&#34;&gt;Introduction to Ollama&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#a-popular-choice&#34;&gt;A Popular Choice&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#ease-of-use&#34;&gt;Ease of Use&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#built-with-golang&#34;&gt;Built with Golang&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#my-practices-with-ollama&#34;&gt;My Practices with Ollama&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#preferred-models&#34;&gt;Preferred Models&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#llama-31&#34;&gt;Llama 3.1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#mistral&#34;&gt;Mistral&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#phi-3&#34;&gt;Phi-3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#qwen-2&#34;&gt;Qwen-2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#hardware-constraints&#34;&gt;Hardware Constraints&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#exploring-uis-for-ollama&#34;&gt;Exploring UIs for Ollama&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#openwebui&#34;&gt;OpenWebUI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#page-assist&#34;&gt;Page Assist&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#enchanted&#34;&gt;Enchanted&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#anythingllm&#34;&gt;AnythingLLM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#dify&#34;&gt;Dify&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#diving-into-llamaindex&#34;&gt;Diving into llamaindex&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#experimenting-with-candle&#34;&gt;Experimenting with Candle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#conclusion&#34;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;introduction-to-ollama&#34;&gt;Introduction to Ollama&lt;/h2&gt;
&lt;h3 id=&#34;a-popular-choice&#34;&gt;A Popular Choice&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/jmorganca/ollama&#34;&gt;Ollama&lt;/a&gt; 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.&lt;/p&gt;</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>&lt;p&gt;I recently started experimenting with &lt;strong&gt;Tailscale&lt;/strong&gt;, a tool that has significantly simplified the way I manage my personal network across devices. In this blog post, I&amp;rsquo;ll share how I discovered Tailscale, its core features, and my personal setup that leverages this powerful tool.&lt;/p&gt;
&lt;h2 id=&#34;discovering-tailscale-through-webvm&#34;&gt;Discovering Tailscale Through WebVM&lt;/h2&gt;
&lt;p&gt;My journey with Tailscale began when I came across &lt;a href=&#34;https://github.com/leaningtech/webvm&#34;&gt;WebVM&lt;/a&gt;, 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.&lt;/p&gt;</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>&lt;p&gt;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.&lt;/p&gt;
&lt;h2 id=&#34;internet-access-and-blocked-websites&#34;&gt;Internet Access and Blocked Websites&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Slow Access to Twitter&lt;/strong&gt;: If you&amp;rsquo;re an avid Twitter user, be prepared for slower connection speeds. The platform doesn&amp;rsquo;t perform as well in Vietnam.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inaccessible Websites&lt;/strong&gt;: Certain websites are blocked in Vietnam, including:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Medium&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Some porn websites&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tip&lt;/strong&gt;: If you rely on any of these platforms, consider downloading content beforehand or using a VPN to access them during your stay.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;visa-process-for-chinese-citizens&#34;&gt;Visa Process for Chinese Citizens&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;E-Visa Conversion&lt;/strong&gt;: Travelers from China holding an E-Visa need to convert it into a paper visa upon arrival.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bring Two Photos&lt;/strong&gt;: Ensure you have two passport-sized photos ready for the visa conversion process.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Carry Cash for Tips&lt;/strong&gt;: It&amp;rsquo;s customary to offer tips during the visa processing, so have some cash (preferably small denominations) on hand.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;exploring-saigon-ho-chi-minh-city&#34;&gt;Exploring Saigon (Ho Chi Minh City)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;City Vibes&lt;/strong&gt;: Saigon reminded me of a blend between &lt;strong&gt;Shanghai&lt;/strong&gt; and &lt;strong&gt;Qingdao&lt;/strong&gt;.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cleanliness&lt;/strong&gt;: While the city is bustling with energy, it&amp;rsquo;s not as clean as Shanghai or Qingdao.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Abundance of Coffee Shops&lt;/strong&gt;: Vietnam is famous for its coffee culture. You&amp;rsquo;ll find numerous coffee shops on almost every street corner.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Must-Try&lt;/strong&gt;: Don&amp;rsquo;t miss out on traditional Vietnamese iced coffee (&lt;strong&gt;Cà Phê Sữa Đá&lt;/strong&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;affordability&#34;&gt;Affordability&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cheaper Than Bangkok&lt;/strong&gt;: Compared to cities like Bangkok, Saigon is significantly more affordable.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hotels&lt;/strong&gt;: Accommodation can be about &lt;strong&gt;half the price&lt;/strong&gt; of equivalent hotels in Bangkok.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;General Expenses&lt;/strong&gt;: Food, transportation, and entertainment are also reasonably priced.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;currency-and-payments&#34;&gt;Currency and Payments&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Accepted Currencies&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Vietnamese Dong (VND)&lt;/strong&gt; is the official currency.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;US Dollars (USD)&lt;/strong&gt; are widely accepted and sometimes preferred.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Chinese RMB&lt;/strong&gt; can be used in some places, but USD is more commonly accepted.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tip&lt;/strong&gt;: Carrying USD can be convenient, but always have some local currency for small purchases.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;local-customs-and-observations&#34;&gt;Local Customs and Observations&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Clothing Choices&lt;/strong&gt;: You&amp;rsquo;ll notice many locals wearing long-sleeved clothing and pants, even in hot weather.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reason&lt;/strong&gt;: This is to &lt;strong&gt;prevent sunburn&lt;/strong&gt; and protect their skin from the sun.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hardworking People&lt;/strong&gt;: The Vietnamese are known for their strong work ethic. Streets are lively with activity from early morning until late at night.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Affordable Goods&lt;/strong&gt;: Many items, from street food to souvenirs, are priced affordably.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Non-Spicy Cuisine&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Similar to Cantonese Food&lt;/strong&gt;: The flavors are mild and focus on the freshness of ingredients.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Must-Try Dishes&lt;/strong&gt;: Pho (noodle soup), Banh Mi (baguette sandwich), and fresh spring rolls.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;final-thoughts&#34;&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;Vietnam offers a captivating blend of cultural experiences, historical sites, and modern attractions. Whether you&amp;rsquo;re sipping coffee in a cozy café, exploring bustling markets, or simply taking in the sights and sounds of the city, there&amp;rsquo;s something for every traveler.&lt;/p&gt;</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>&lt;p&gt;&lt;em&gt;Note: All prices mentioned are in RMB.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Over the years, I&amp;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.&lt;/p&gt;
&lt;h2 id=&#34;rod-selection&#34;&gt;Rod Selection&lt;/h2&gt;
&lt;h3 id=&#34;understanding-rod-actions&#34;&gt;Understanding Rod Actions&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;L (Light) Action Rods&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Best for casting lures weighing &lt;strong&gt;4-5g&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Ideal for using lures within half of the rod&amp;rsquo;s lure weight range (e.g., for a rod rated &lt;strong&gt;2-7g&lt;/strong&gt;, the optimal lure weight is around &lt;strong&gt;4-5g&lt;/strong&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UL (Ultra-Light) Action Rods&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Suitable for casting lures weighing &lt;strong&gt;3g&lt;/strong&gt; or less.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;one-piece-vs-multi-piece-rods&#34;&gt;One-Piece vs. Multi-Piece Rods&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;One-Piece Rods&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Offer better sensitivity and casting performance.&lt;/li&gt;
&lt;li&gt;However, they are less portable and harder to transport.&lt;/li&gt;
&lt;li&gt;Not as easy to sell if you decide to upgrade.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multi-Piece Rods&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;More convenient for travel.&lt;/li&gt;
&lt;li&gt;Slight trade-off in sensitivity compared to one-piece rods.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;recommended-action&#34;&gt;Recommended Action&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;best action&lt;/strong&gt; for spinning cast rods is &lt;strong&gt;Light (L)&lt;/strong&gt; action.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;reel-selection&#34;&gt;Reel Selection&lt;/h2&gt;
&lt;h3 id=&#34;spinning-reels-vs-baitcasting-reels&#34;&gt;Spinning Reels vs. Baitcasting Reels&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Spinning Reels&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Easier for casting, especially for beginners.&lt;/li&gt;
&lt;li&gt;Better suited for saltwater fishing than baitcasting reels.&lt;/li&gt;
&lt;li&gt;Can use heavier action rods to cast small lures effectively.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Baitcasting Reels&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Look cool and offer precision, but have a steeper learning curve.&lt;/li&gt;
&lt;li&gt;Not as user-friendly for casting light lures.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;reel-size-and-line-compatibility&#34;&gt;Reel Size and Line Compatibility&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;For a &lt;strong&gt;2500S&lt;/strong&gt; reel:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;0.8 PE&lt;/strong&gt; line is sufficient.&lt;/li&gt;
&lt;li&gt;Using &lt;strong&gt;0.6 PE&lt;/strong&gt; line on a 2500S reel may require too much line to fill the spool.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;For a &lt;strong&gt;2000S&lt;/strong&gt; reel:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;0.6 PE&lt;/strong&gt; line is appropriate.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s not common to use PE lines smaller than &lt;strong&gt;0.6&lt;/strong&gt;, as they are less widely available.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;light-weight-reels&#34;&gt;Light Weight Reels&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Always choose &lt;strong&gt;lightweight reels&lt;/strong&gt; for better balance and less fatigue during fishing.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;line-selection&#34;&gt;Line Selection&lt;/h2&gt;
&lt;h3 id=&#34;pe-line-vs-fluorocarbon&#34;&gt;PE Line vs. Fluorocarbon&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Casting Difficulty&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1.5 PE&lt;/strong&gt; and fluorocarbon lines offer similar casting challenges.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PE Line Advantages&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Thinner diameter for the same strength compared to fluorocarbon.&lt;/li&gt;
&lt;li&gt;Essential for longer casting distances.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Avoiding Line Connections&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Use a &lt;strong&gt;Carolina rig&lt;/strong&gt; to eliminate the need to connect PE line and fluorocarbon leader.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;recommended-lines&#34;&gt;Recommended Lines&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;YGK PE Line&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Highly recommended for its quality.&lt;/li&gt;
&lt;li&gt;Performs much better than Sufix lines.&lt;/li&gt;
&lt;li&gt;Consider using &lt;strong&gt;0.6 or 0.8 PE&lt;/strong&gt; from YGK.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fluorocarbon Line&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1.5 Fluorocarbon&lt;/strong&gt; can be used as a leader if necessary.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;rigs-and-lures&#34;&gt;Rigs and Lures&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Carolina Rig&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Allows you to use PE line without needing to connect it to a fluorocarbon leader.&lt;/li&gt;
&lt;li&gt;Effective for various fishing conditions.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;recommendations&#34;&gt;Recommendations&lt;/h2&gt;
&lt;h3 id=&#34;final-gear-choices&#34;&gt;Final Gear Choices&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Rod&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;L Action Spinning Rod&lt;/strong&gt; with multiple pieces for portability.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reel&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;2000S or 2500S&lt;/strong&gt; spinning reel, prioritize lightweight models.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Line&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;0.6 or 0.8 YGK PE line&lt;/strong&gt; (lean towards 0.6 for more capacity).&lt;/li&gt;
&lt;li&gt;Optional &lt;strong&gt;1.5 Fluorocarbon&lt;/strong&gt; leader.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rig&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Use a &lt;strong&gt;Carolina rig&lt;/strong&gt; setup.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;brands-and-models&#34;&gt;Brands and Models&lt;/h2&gt;
&lt;h3 id=&#34;chinese-domestic-market-cdm-rods&#34;&gt;Chinese Domestic Market (CDM) Rods&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Few high-quality CDM spinning rods are available.&lt;/li&gt;
&lt;li&gt;The best CDM spinning rod is the &lt;strong&gt;翠鸟 (Kingfisher)&lt;/strong&gt; from &lt;strong&gt;钓之屋 (Fishing House)&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;游侠&lt;/strong&gt; (the larger version of 翠鸟) is not as good.
&lt;ul&gt;
&lt;li&gt;Example: A 2500S reel on a 游侠 lacks proper rod skewness, affecting casting performance.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;shimano-rods-and-reels&#34;&gt;Shimano Rods and Reels&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Shimano&lt;/strong&gt; spinning rods and reels are superior in performance.&lt;/li&gt;
&lt;li&gt;The reels have features that prevent accidental rolling after opening the bail, allowing you to stop the line with your finger for better control.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;reels-ive-used&#34;&gt;Reels I&amp;rsquo;ve Used&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Shimano Vanquish 2500S (2023 model)&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Extremely lightweight and smooth.&lt;/li&gt;
&lt;li&gt;Feels almost too slim, raising concerns about durability.&lt;/li&gt;
&lt;li&gt;The infinite loop system is excellent.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shimano Stradic 2500S (2023 model)&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Heavier compared to Vanquish.&lt;/li&gt;
&lt;li&gt;Handle design is not as comfortable.&lt;/li&gt;
&lt;li&gt;Appearance gives a sense of reliability.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shimano Vanford C2000SHG (2020 model)&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Great performance but similar concerns about durability due to plastic components.&lt;/li&gt;
&lt;li&gt;Feels less robust than the Stradic but is lighter.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;price-comparison-in-rmb&#34;&gt;Price Comparison (in RMB)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;钓之屋 翠鸟 (Kingfisher)&lt;/strong&gt;: &lt;strong&gt;360&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shimano Stradic&lt;/strong&gt;: &lt;strong&gt;900&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shimano Vanford&lt;/strong&gt;: &lt;strong&gt;1,100&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shimano Vanquish&lt;/strong&gt;: &lt;strong&gt;2,600&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shimano Stella&lt;/strong&gt;: &lt;strong&gt;3,800&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;</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>Encounter with a Major Issue with Cloudflare Warp: A Life and Death Rescue for VPS</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>&lt;p&gt;Today, I planned to use Warp to select an IP exit for my VPS, following the &lt;a href=&#34;https://developers.cloudflare.com/warp-client/get-started/linux/&#34;&gt;Cloudflare official documentation&lt;/a&gt;. When executing the &lt;code&gt;warp-cli connect&lt;/code&gt; step, the server immediately lost connection, and the problem persisted even after rebooting.&lt;/p&gt;
&lt;p&gt;After researching, I found that this problem is not unique. For instance, in a &lt;a href=&#34;https://www.v2ex.com/t/933725&#34;&gt;discussion on V2EX&lt;/a&gt;, many users encountered similar issues. The solution is to run &lt;code&gt;warp-cli set-mode proxy&lt;/code&gt; before executing &lt;code&gt;warp-cli connect&lt;/code&gt; to bypass the local address. Surprisingly, Cloudflare&amp;rsquo;s official documentation does not mention this crucial step, undoubtedly increasing the complexity and risk of configuration.&lt;/p&gt;</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>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;Many customers have TLS enabled, which is different from the lab environment.&lt;/p&gt;
&lt;h2 id=&#34;curl&#34;&gt;Curl&lt;/h2&gt;
&lt;p&gt;Curl requires a specified CA certificate, otherwise it will report an error.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;curl --cacert ca.crt https://127.0.0.1:10080/status
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;wget&#34;&gt;Wget&lt;/h2&gt;
&lt;p&gt;Many containers do not have curl, so wget is used instead. Wget is better as it does not require a CA certificate.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;wget --no-check-certificate http://127.0.0.1:10080/status
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</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;"><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;"><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>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;After moving house, there are many more devices at home that need internet access. However, I don&amp;rsquo;t want to configure a proxy on each device, so I thought of using a transparent gateway.&lt;/p&gt;
&lt;h2 id=&#34;transparent-gateway&#34;&gt;Transparent Gateway&lt;/h2&gt;
&lt;p&gt;After some research, I found that the easiest way is to use the premium version of Clash, although I didn&amp;rsquo;t know when Clash released a premium version. I mainly referred to &lt;a href=&#34;https://www.cfmem.com/2022/05/clash.html&#34;&gt;this article&lt;/a&gt;. It&amp;rsquo;s much simpler than setting up iptables.&lt;/p&gt;</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;"><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;"><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;"><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>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;</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;"><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>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;One day, I impulsively turned on GitHub&amp;rsquo;s Vigilant mode.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;test&#34; loading=&#34;lazy&#34; src=&#34;https://blog.minifish.org/posts/images/2022-02-12-12.02.07.webp&#34;&gt;&lt;/p&gt;
&lt;p&gt;As a result, all my commits started looking like this.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;test&#34; loading=&#34;lazy&#34; src=&#34;https://blog.minifish.org/posts/images/2022-02-12-12.11.01.webp&#34;&gt;&lt;/p&gt;
&lt;p&gt;To figure out how to make them Verified, I found the following method.&lt;/p&gt;
&lt;h2 id=&#34;method&#34;&gt;Method&lt;/h2&gt;
&lt;p&gt;I actually referred to this &lt;a href=&#34;https://zhuanlan.zhihu.com/p/76861431&#34;&gt;link&lt;/a&gt;. However, it wasn&amp;rsquo;t quite enough, as there might be authentication-related issues on MacBooks that lead to commit errors. So, I found this &lt;a href=&#34;https://stackoverflow.com/a/40066889&#34;&gt;solution&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;</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/images/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/images/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;"><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>&lt;h3 id=&#34;background&#34;&gt;Background&lt;/h3&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h3 id=&#34;preparation&#34;&gt;Preparation&lt;/h3&gt;
&lt;p&gt;You need two Microsoft accounts, so I dug out my wife&amp;rsquo;s long-unused Hotmail account.&lt;/p&gt;</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>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;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&amp;rsquo;t dealt with this kind of issue before, so I did a bit of research.&lt;/p&gt;
&lt;p&gt;Typically, to verify data consistency, you compute a checksum on both sides and compare them.&lt;/p&gt;
&lt;h2 id=&#34;tidb-mysql-side&#34;&gt;TiDB (MySQL) Side&lt;/h2&gt;
&lt;p&gt;For the verification of a specific table, the following SQL is used:&lt;/p&gt;</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;"><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;"><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;"><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;"><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;"><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;"><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;"><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;"><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>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;Initially, I didn&amp;rsquo;t think this was something worth writing about, because CTex was previously working smoothly for everyone. However, it turns out that CTex hasn&amp;rsquo;t been updated since 2016. So, I wanted to find a replacement for Chinese LaTeX on Windows in 2021.&lt;/p&gt;
&lt;h2 id=&#34;configuration-method&#34;&gt;Configuration Method&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Install MiKTeX (TexLive should work as well). MiKTeX can automatically download dependency packages and you can also proactively install the ctex package.&lt;/li&gt;
&lt;li&gt;Install the VSCode LaTeX extension.&lt;/li&gt;
&lt;li&gt;Configure the LaTeX extension. I found a powerful configuration on Zhihu, but it’s quite complex, so I simplified it a bit.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;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&amp;rsquo;ve put XeLaTex first here because it&amp;rsquo;s the most compatible for compiling Chinese. If you use pdflatex to compile Chinese documents, you&amp;rsquo;re likely to encounter issues.&lt;/p&gt;</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;"><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;"><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/images/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>&lt;p&gt;In &lt;a href=&#34;https://blog.minifish.org/posts/travis-git-push&#34;&gt;this post&lt;/a&gt;, 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.&lt;/p&gt;
&lt;h2 id=&#34;go-action&#34;&gt;Go Action&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;</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/images/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;"><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>&lt;p&gt;&lt;a href=&#34;https://github.com/akopytov/sysbench&#34;&gt;Sysbench&lt;/a&gt; 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&amp;rsquo;s custom load usage. Secondly, because I tried the mysqlslap tool provided by MySQL&amp;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.&lt;/p&gt;</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;"><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;"><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;"><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;"><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;"><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>&lt;p&gt;When using TiDB, you may occasionally encounter some exceptions, such as the &amp;ldquo;Lost connection to MySQL server during query&amp;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.&lt;/p&gt;</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/images/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/images/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/images/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/images/20200908141537.webp"></p>
<p><img alt="lost" loading="lazy" src="/posts/images/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/images/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/images/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/images/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/images/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>&lt;p&gt;This article will introduce some key functions and the interpretation of logs in TiDB.&lt;/p&gt;
&lt;h2 id=&#34;key-functions&#34;&gt;Key Functions&lt;/h2&gt;
&lt;p&gt;The definition of key functions varies from person to person, so the content of this section is subjective.&lt;/p&gt;
&lt;h3 id=&#34;execute&#34;&gt;execute&lt;/h3&gt;
&lt;p&gt;&lt;img alt=&#34;func&#34; loading=&#34;lazy&#34; src=&#34;https://blog.minifish.org/posts/images/20200812152326.webp&#34;&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;execute&lt;/code&gt; function is the necessary pathway for text protocol execution. It also nicely demonstrates the various processes of SQL handling.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;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.&lt;/p&gt;</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/images/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/images/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/images/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;"><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;"><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;"><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/images/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>&lt;p&gt;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.&lt;/p&gt;
&lt;h2 id=&#34;system-variables&#34;&gt;System Variables&lt;/h2&gt;
&lt;p&gt;The system variable names in TiDB are defined in &lt;a href=&#34;https://github.com/pingcap/tidb/blob/db0310b17901b1a59f7f728294455ed9667f88ac/sessionctx/variable/tidb_vars.go&#34;&gt;tidb_vars.go&lt;/a&gt;. This file also includes some default values for variables, but the place where they are actually assembled is &lt;a href=&#34;https://github.com/pingcap/tidb/blob/12aac547a9068c404ad18093ae4d0ea4d060a465/sessionctx/variable/sysvar.go#L96&#34;&gt;defaultSysVars&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;defaultSysVars&#34; loading=&#34;lazy&#34; src=&#34;https://blog.minifish.org/posts/images/20200728151254.webp&#34;&gt;&lt;/p&gt;
&lt;p&gt;This large struct array defines the scope, variable names, and default values for all variables in TiDB. Besides TiDB&amp;rsquo;s own system variables, it also includes compatibility with MySQL&amp;rsquo;s system variables.&lt;/p&gt;</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/images/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/images/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;"><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;"><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/images/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/images/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;"><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;"><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;"><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/images/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;"><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;"><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/images/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/images/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/images/20200728174207.webp">
<img alt="defaultSysVars" loading="lazy" src="/posts/images/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/images/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/images/20200729205545.webp"></p>
<p>Click EDIT and you will see the monitoring formula is:</p>
<p><img alt="tps2" loading="lazy" src="/posts/images/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/images/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/images/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/images/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/images/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>&lt;p&gt;Continuing from &lt;a href=&#34;https://blog.minifish.org/posts/tidb1&#34;&gt;the previous article&lt;/a&gt;, we learned how to set up the environment for reading code and where to start reading the code. In this part, we&amp;rsquo;ll introduce methods for viewing code based on some common needs.&lt;/p&gt;
&lt;h2 id=&#34;how-to-check-the-support-level-of-a-syntax&#34;&gt;How to Check the Support Level of a Syntax&lt;/h2&gt;
&lt;p&gt;There are usually two methods:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Check through the parser repo&lt;/li&gt;
&lt;li&gt;Directly check within the TiDB repo&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Both of these methods require the &lt;a href=&#34;https://blog.minifish.org/posts/tidb1#%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA&#34;&gt;environment setup from the previous article&lt;/a&gt;. If you haven&amp;rsquo;t tried that yet, give it a go.&lt;/p&gt;</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/images/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/images/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/images/20200712183658.webp"></p>
<p>Alternatively, you can find it in the file list,</p>
<p><img alt="parser.y2" loading="lazy" src="/posts/images/20200712184101.webp"></p>
<p><img alt="parser.y3" loading="lazy" src="/posts/images/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/images/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/images/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/images/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/images/20200712190752.webp"></p>
<p>One is the actual implementation,</p>
<p><img alt="parser.y7" loading="lazy" src="/posts/images/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/images/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/images/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/images/20200712191711.webp"></p>
<p>It&rsquo;s an option.</p>
<p><img alt="parser.y9" loading="lazy" src="/posts/images/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/images/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/images/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/images/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/images/20200715164756.webp"></p>
<p>In the code, it is represented as:</p>
<p><img alt="conf5" loading="lazy" src="/posts/images/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/images/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/images/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>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;There are many articles on reading the source code of TiDB, often referred to as &lt;a href=&#34;https://pingcap.com/blog-cn/#TiDB-%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB&#34;&gt;the &amp;ldquo;Twenty-Four Chapters Scriptures&amp;rdquo;&lt;/a&gt;. However, these introductions typically proceed from a macro to a micro perspective. This series attempts to introduce how to read TiDB&amp;rsquo;s source code from an easier angle. The goals we aim to achieve are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Enable readers to start reading TiDB&amp;rsquo;s code themselves, rather than understanding it passively through pre-written articles.&lt;/li&gt;
&lt;li&gt;Provide some common examples of looking into the details of the code, such as examining the scope of a variable.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After all, teaching people to fish is better than giving them fish. While the code changes often, the methods remain mostly unchanged.&lt;/p&gt;</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/images/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/images/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/images/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/images/20200706222247.webp"></p>
<p><img alt="run2" loading="lazy" src="/posts/images/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/images/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/images/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/images/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/images/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>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;I had to install Docker on Windows to reproduce a bug.&lt;/p&gt;
&lt;h2 id=&#34;process&#34;&gt;Process&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Using Windows 10 as an example, if you have the Home Basic version, you&amp;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.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;li&gt;If you encounter permission issues with shared folders, follow the instructions at &lt;a href=&#34;https://github.com/docker/for-win/issues/3174&#34;&gt;link&lt;/a&gt;. 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.&lt;/li&gt;
&lt;li&gt;When you encounter errors during use, just try a few more times. It might work; if not, reset it.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;impressions&#34;&gt;Impressions&lt;/h2&gt;
&lt;p&gt;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&amp;rsquo;t have high expectations. The results were still quite surprising. In summary, here are a few points:&lt;/p&gt;</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>&lt;p&gt;Initially, I thought it would be a simple setting adjustment, so I casually Googled it. Sure enough, there was a unanimous solution: modify &lt;code&gt;/etc/systemd/logind.conf&lt;/code&gt;, change &lt;code&gt;HandleLidSwitch&lt;/code&gt; to &lt;code&gt;ignore&lt;/code&gt; or &lt;code&gt;lock&lt;/code&gt;, and then restart &lt;code&gt;logind&lt;/code&gt; or reboot.&lt;/p&gt;
&lt;p&gt;I tried this, but it didn&amp;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, &lt;code&gt;Ubuntu&lt;/code&gt; even reported errors.&lt;/p&gt;
&lt;p&gt;So, I reinstalled the more preferred &lt;code&gt;Debian&lt;/code&gt;. Tried again, and it still didn&amp;rsquo;t work. Finally, I found a more brutal method.&lt;/p&gt;</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;"><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;"><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;"><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>&lt;h2 id=&#34;dynamically-updating-dns-records&#34;&gt;Dynamically Updating DNS Records&lt;/h2&gt;
&lt;p&gt;The purpose of dynamic updates is pretty simple. As a long-term user of China Unicom&amp;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).&lt;/p&gt;
&lt;p&gt;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&amp;rsquo;s better to write your own script for updating.&lt;/p&gt;</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>&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Create a script and place it in&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;C:\Users\name\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\`  
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fill the script with:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Start-Process -FilePath &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;C:\Users\name\bin\gost-windows-amd64.exe&amp;#34;&lt;/span&gt; -ArgumentList &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;-L=&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;-F=&amp;#34;&lt;/span&gt; -RedirectStandardOutput &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;C:\Users\name\bin\gost-windows-amd64.log&amp;#34;&lt;/span&gt; -RedirectStandardError &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;C:\Users\name\bin\gost-windows-amd64.err&amp;#34;&lt;/span&gt; -WindowStyle Hidden
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note: &lt;code&gt;Start-Process&lt;/code&gt; seems to perform a fork-like action, and by default, it opens a new PowerShell window to execute. That&amp;rsquo;s why &lt;code&gt;-WindowStyle Hidden&lt;/code&gt; is added at the end. You can&amp;rsquo;t use &lt;code&gt;-NoNewWindow&lt;/code&gt; here because it only prevents the creation of a new window for executing &lt;code&gt;Start-Process&lt;/code&gt;, but the old window will not exit.&lt;br&gt;
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.&lt;/p&gt;</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;"><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;"><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>&lt;h2 id=&#34;preface&#34;&gt;Preface&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2 id=&#34;why-use-an-https-proxy&#34;&gt;Why Use an HTTPS Proxy&lt;/h2&gt;
&lt;p&gt;In the &lt;a href=&#34;https://haoel.github.io/&#34;&gt;guide&lt;/a&gt;, 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.&lt;/p&gt;</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;"><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;"><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;"><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>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;When I first started writing Golang, I was always looking for a convenient debugging tool. Back then, I came across documentation about using &lt;code&gt;gdb&lt;/code&gt; to debug and also tried &lt;code&gt;delve&lt;/code&gt;, but neither felt easy to use. Later, on someone&amp;rsquo;s advice, I went back to the good old &lt;code&gt;print&lt;/code&gt; statements&amp;hellip;&lt;/p&gt;
&lt;p&gt;Over the past couple of days, I was debugging &lt;code&gt;go test&lt;/code&gt; and found that tests would always hang when run per package. I couldn&amp;rsquo;t think of a suitable method at first, so I thought of &lt;code&gt;delve&lt;/code&gt; again. After giving it a try, I found it has become much more mature than before.&lt;/p&gt;</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;"><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;"><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>&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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&amp;rsquo;s also an implementation using libev, a pure C implementation, which also offers good performance and is very lightweight.&lt;/p&gt;</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>&lt;p&gt;Let&amp;rsquo;s first look at the following code snippet:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-golang&#34; data-lang=&#34;golang&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;package&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;os&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;runtime&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;time&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;runtime&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;GOMAXPROCS&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; make(&lt;span style=&#34;color:#66d9ef&#34;&gt;map&lt;/span&gt;[&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;]&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;go&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#a6e22e&#34;&gt;a&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;] = &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Sleep&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;1000&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;a&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;] &amp;gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1000000&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#a6e22e&#34;&gt;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Println&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;a&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#a6e22e&#34;&gt;os&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Exit&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After compiling and running it (assuming your machine has 2 or more cores), you will get the following error:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;fatal error: concurrent map read and map write
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;goroutine 1 [running]:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;runtime.throw(0x10c3e05, 0x21)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        /usr/local/Cellar/go/1.10.3/libexec/src/runtime/panic.go:616 +0x81 fp=0xc42004bf00 sp=0xc42004bee0 pc=0x10263f1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;runtime.mapaccess1_fast64(0x10a5b60, 0xc42007e180, 0x1, 0xc42008e048)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        /usr/local/Cellar/go/1.10.3/libexec/src/runtime/hashmap_fast.go:101 +0x197 fp=0xc42004bf28 sp=0xc42004bf00 pc=0x1008d27
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;main.main()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        /Users/yusp/test/panic3/main.go:22 +0x7c fp=0xc42004bf88 sp=0xc42004bf28 pc=0x108e28c
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;runtime.main()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        /usr/local/Cellar/go/1.10.3/libexec/src/runtime/proc.go:198 +0x212 fp=0xc42004bfe0 sp=0xc42004bf88 pc=0x1027c62
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;runtime.goexit()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        /usr/local/Cellar/go/1.10.3/libexec/src/runtime/asm_amd64.s:2361 +0x1 fp=0xc42004bfe8 sp=0xc42004bfe0 pc=0x104e501
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;goroutine 5 [runnable]:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;time.Sleep(0x3e8)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        /usr/local/Cellar/go/1.10.3/libexec/src/runtime/time.go:102 +0x166
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;main.main.func1(0xc42007e180)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        /Users/yusp/test/panic3/main.go:18 +0x61
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;created by main.main
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        /Users/yusp/test/panic3/main.go:13 +0x59
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It seems straightforward; Golang&amp;rsquo;s map is not thread-safe, and concurrent read and write cause a panic. However, look at the error information on line &lt;code&gt;/Users/yusp/test/panic3/main.go:18 +0x61&lt;/code&gt;, which points to line 18 of main.go where &lt;code&gt;Sleep&lt;/code&gt; is called, not the actual point of concurrency issue. In a vast stack trace, it becomes even harder to locate the problem.&lt;/p&gt;</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;"><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;"><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>&lt;p&gt;In a previous article &lt;a href=&#34;https://cn.pingcap.com/blog/tidb-source-code-reading-4&#34;&gt;“TiDB Source Code Reading Series (4) Overview of INSERT Statement”&lt;/a&gt;, 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.&lt;/p&gt;</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;"><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;"><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;"><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;"><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;"><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>&lt;h2 id=&#34;installing-haproxy&#34;&gt;Installing HAProxy&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;yum install haproxy&lt;/code&gt; is effective for CentOS 7. After installation, you can start the service using &lt;code&gt;systemctl start haproxy&lt;/code&gt;. But don&amp;rsquo;t rush yet.&lt;/p&gt;
&lt;h2 id=&#34;configuring-haproxy&#34;&gt;Configuring HAProxy&lt;/h2&gt;
&lt;p&gt;Add the following content to /etc/haproxy/haproxy.cfg.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;global &lt;span style=&#34;color:#75715e&#34;&gt;# The content of global is generally fixed and quite understandable.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        log 127.0.0.1   local2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        maxconn &lt;span style=&#34;color:#ae81ff&#34;&gt;4096&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        user haproxy
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        group haproxy
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        chroot /var/lib/haproxy
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        daemon
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        pidfile /var/run/haproxy.pid
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        stats socket /var/run/haproxy.sock         &lt;span style=&#34;color:#75715e&#34;&gt;# Create a socket file for haproxy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        nbproc &lt;span style=&#34;color:#ae81ff&#34;&gt;40&lt;/span&gt;                                  &lt;span style=&#34;color:#75715e&#34;&gt;# Start 40 processes to forward concurrently, higher versions can use nbthread, a threaded approach.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;defaults &lt;span style=&#34;color:#75715e&#34;&gt;# This section is mostly copied, not entirely clear on the options.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        log     global
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        mode    http
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        option  tcplog
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        option  dontlognull
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        retries &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        option  redispatch
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        maxconn &lt;span style=&#34;color:#ae81ff&#34;&gt;1024&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        timeout connect 5000ms
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        timeout client 50000ms
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        timeout server 50000ms
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;listen cdb_cluster 0.0.0.0:3030  &lt;span style=&#34;color:#75715e&#34;&gt;# The actual proxy name and address for receiving services.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;## cdb balance leastconn - the cluster listening on port 3030.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        mode tcp
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        balance leastconn  &lt;span style=&#34;color:#75715e&#34;&gt;# This method is most suitable for databases; do not change.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        server cdb1 172.16.30.3:26257 check &lt;span style=&#34;color:#75715e&#34;&gt;# Check seems to require a port for feedback status; without it, it might not work, but it doesn&amp;#39;t matter.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        server cdb2 172.16.30.3:26258 check
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        server cdb3 172.16.30.3:26259 check
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        server cdb4 172.16.30.3:26260 check
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;start-and-connect&#34;&gt;Start and Connect&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;systemctl start haproxy&lt;/code&gt; to start the service.&lt;/p&gt;</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;"><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;"><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>&lt;h2 id=&#34;why-test-tpc-c&#34;&gt;Why Test TPC-C&lt;/h2&gt;
&lt;p&gt;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;rsquo;s no issue of quarreling over the testing tools used.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;</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>&lt;h2 id=&#34;compiling-sysbench-with-pgsql-support&#34;&gt;Compiling Sysbench with pgsql Support&lt;/h2&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;./configure --with-pgsql
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Of course, preliminary work involves downloading the Sysbench source code and installing the necessary PostgreSQL header files required for compilation (you can use &lt;code&gt;yum&lt;/code&gt; or &lt;code&gt;sudo&lt;/code&gt; to install them).&lt;/p&gt;</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;"><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;"><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;"><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;"><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;"><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>&lt;h2 id=&#34;introduction-to-oltp-bench&#34;&gt;Introduction to OLTP-Bench&lt;/h2&gt;
&lt;p&gt;OLTP-Bench is an open-source benchmarking tool platform for OLTP scenarios from CMU&amp;rsquo;s DB Group. It was designed to provide a simple, easy-to-use, and extensible testing platform.&lt;/p&gt;
&lt;p&gt;It connects to databases via the JDBC interface, supporting the following test suites:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TPC-C&lt;/li&gt;
&lt;li&gt;Wikipedia&lt;/li&gt;
&lt;li&gt;Synthetic Resource Stresser&lt;/li&gt;
&lt;li&gt;Twitter&lt;/li&gt;
&lt;li&gt;Epinions.com&lt;/li&gt;
&lt;li&gt;TATP&lt;/li&gt;
&lt;li&gt;AuctionMark&lt;/li&gt;
&lt;li&gt;SEATS&lt;/li&gt;
&lt;li&gt;YCSB&lt;/li&gt;
&lt;li&gt;JPAB (Hibernate)&lt;/li&gt;
&lt;li&gt;CH-benCHmark&lt;/li&gt;
&lt;li&gt;Voter (Japanese &amp;ldquo;American Idol&amp;rdquo;)&lt;/li&gt;
&lt;li&gt;SIBench (Snapshot Isolation)&lt;/li&gt;
&lt;li&gt;SmallBank&lt;/li&gt;
&lt;li&gt;LinkBench&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Detailed project information can be found &lt;a href=&#34;http://db.cs.cmu.edu/projects/oltp-bench/&#34;&gt;here&lt;/a&gt;, and the GitHub page is &lt;a href=&#34;https://github.com/oltpbenchmark/oltpbench&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;</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>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;The English font set in Microsoft&amp;rsquo;s VS Code is monospaced, while the Chinese font is not.&lt;/p&gt;
&lt;h2 id=&#34;method&#34;&gt;Method&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Download and install the fonts from &lt;a href=&#34;https://github.com/be5invis/Sarasa-Gothic&#34;&gt;here&lt;/a&gt;. This font combines Source Han Sans and is said to align punctuation strictly.&lt;/li&gt;
&lt;li&gt;Use the method described &lt;a href=&#34;https://github.com/be5invis/Sarasa-Gothic/issues/8&#34;&gt;here&lt;/a&gt; to set up VS Code.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;PS: It is indeed aligned, although the English text appears a bit narrow.&lt;/p&gt;</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>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;The DDL paper on F1 serves as the foundation for TiDB&amp;rsquo;s DDL implementation. There are two main papers on F1: one provides an overview of F1&amp;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 &lt;a href=&#34;http://www.ifi.uzh.ch/dbtg/teaching/courses/SDBS/Papaioannou.pdf&#34;&gt;introduction to the second paper&lt;/a&gt; here, which can help in understanding.&lt;/p&gt;
&lt;h2 id=&#34;understanding&#34;&gt;Understanding&lt;/h2&gt;
&lt;h3 id=&#34;online-ddl-concept&#34;&gt;Online DDL Concept&lt;/h3&gt;
&lt;p&gt;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&amp;rsquo;re willing to put in the effort. For traditional businesses where 24/7 availability isn&amp;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.&lt;/p&gt;</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;"><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>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;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;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.&lt;/p&gt;
&lt;h2 id=&#34;understanding&#34;&gt;Understanding&lt;/h2&gt;
&lt;p&gt;I read the first part of this &lt;a href=&#34;https://static.googleusercontent.com/media/research.google.com/zh-CN//pubs/archive/45855.pdf&#34;&gt;paper&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The CAP theorem [Bre12] says that you can only have two of the three desirable properties of:&lt;/p&gt;</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>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2 id=&#34;process&#34;&gt;Process&lt;/h2&gt;
&lt;p&gt;The basic process references &lt;a href=&#34;https://gist.github.com/Maumagnaguagno/84a9807ed71d233e5d3f&#34;&gt;this gist&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Below is the &lt;code&gt;.travis.yml&lt;/code&gt; from the gist.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;language&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;ruby&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;rvm&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ae81ff&#34;&gt;2.0.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;env&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;global&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ae81ff&#34;&gt;USER=&amp;#34;username&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ae81ff&#34;&gt;EMAIL=&amp;#34;username@mail.com&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ae81ff&#34;&gt;REPO=&amp;#34;name of target repo&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ae81ff&#34;&gt;FILES=&amp;#34;README.md foo.txt bar.txt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ae81ff&#34;&gt;GH_REPO=&amp;#34;github.com/${USER}/${REPO}.git&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#f92672&#34;&gt;secure&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;put travis gem output here =&amp;gt; http://docs.travis-ci.com/user/encryption-keys/&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;script&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ae81ff&#34;&gt;ruby test.rb&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;after_success&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ae81ff&#34;&gt;MESSAGE=$(git log --format=%B -n 1 $TRAVIS_COMMIT)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ae81ff&#34;&gt;git clone git://${GH_REPO}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ae81ff&#34;&gt;mv -f ${FILES} ${REPO}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ae81ff&#34;&gt;cd ${REPO}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ae81ff&#34;&gt;git remote&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ae81ff&#34;&gt;git config user.email ${EMAIL}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ae81ff&#34;&gt;git config user.name ${USER}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ae81ff&#34;&gt;git add ${FILES}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ae81ff&#34;&gt;git commit -m &amp;#34;${MESSAGE}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ae81ff&#34;&gt;git push &amp;#34;https://${GH_TOKEN}@${GH_REPO}&amp;#34; master &amp;gt; /dev/null 2&amp;gt;&amp;amp;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note here that MESSAGE should be quoted when committing, which the original gist did not include.&lt;/p&gt;</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;"><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;"><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;"><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>&lt;h2 id=&#34;some-documents-on-mysql&#34;&gt;Some Documents on MySQL&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Client Usage Guide &lt;a href=&#34;https://dev.mysql.com/doc/refman/5.7/en/mysql-shell.html&#34;&gt;MySQL Shell User Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Server Configuration Guide &lt;a href=&#34;https://dev.mysql.com/doc/refman/5.7/en/document-store.html&#34;&gt;Using MySQL as a Document Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Application Development API Guide &lt;a href=&#34;https://dev.mysql.com/doc/x-devapi-userguide/en/&#34;&gt;X DevAPI User Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Introduction to Server Internal Implementation &lt;a href=&#34;https://dev.mysql.com/doc/internals/en/x-protocol.html&#34;&gt;X Protocol&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;implementation-principle&#34;&gt;Implementation Principle&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Communication between client and server is over TCP and the protocol uses protobuf.&lt;/li&gt;
&lt;li&gt;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 &amp;ldquo;sql&amp;rdquo;, in which case the message content is executed as a SQL statement; if it is &amp;ldquo;xplugin&amp;rdquo; or &amp;ldquo;mysqlx,&amp;rdquo; the message is handled in another way. The other ways can be divided into:
&lt;ul&gt;
&lt;li&gt;Administrative commands&lt;/li&gt;
&lt;li&gt;CRUD operations&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&amp;ldquo;xplugin&amp;rdquo; and &amp;ldquo;mysqlx&amp;rdquo; have the same function, with the latter being the new name for the former, retained temporarily for compatibility.&lt;/li&gt;
&lt;li&gt;The content of &amp;ldquo;mysqlx&amp;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 &amp;ldquo;sql&amp;rdquo;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;implementation-steps&#34;&gt;Implementation Steps&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Start a new server for TiDB. The relevant configuration parameters such as IP, port, and socket need to be set.&lt;/li&gt;
&lt;li&gt;Implement the reading and writing functionality for message communication.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;li&gt;The server should include contents like the Query Context from the original TiDB server, as it primarily translates into SQL for execution.&lt;/li&gt;
&lt;li&gt;Implement the decoding and handling of messages. Although only a sentence, the workload included is substantial.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In &lt;code&gt;mysqlx_all_msgs.h&lt;/code&gt;, all messages are initialized&lt;/p&gt;</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;"><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;"><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;"><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;"><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;"><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;"><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>&lt;h2 id=&#34;server-side&#34;&gt;Server Side&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Disable SeLinux&lt;/strong&gt;&lt;br&gt;
Edit the configuration file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;vi /etc/selinux/config
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Modify as follows:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;#SELINUX=enforcing    # Comment out
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;#SELINUXTYPE=targeted # Comment out
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;SELINUX=disabled      # Add this line
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then reboot the system:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;reboot  &lt;span style=&#34;color:#75715e&#34;&gt;# Restart the system&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Create a Directory&lt;/strong&gt;&lt;br&gt;
Using the root user, create a directory named &lt;code&gt;/nfs&lt;/code&gt;. Note: It&amp;rsquo;s best to check which partition has the most space by running &lt;code&gt;df&lt;/code&gt;, as the root (&lt;code&gt;/&lt;/code&gt;) partition may not have the most space. In some automatic partitioning setups, the &lt;code&gt;/home&lt;/code&gt; partition may have the most space.&lt;/p&gt;</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;"><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;"><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;"><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;"><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;"><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;"><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;"><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;"><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;"><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;"><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;"><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;"><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;"><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;"><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;"><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;"><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;"><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;"><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;"><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;"><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;"><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>&lt;h2 id=&#34;what-is-bridging&#34;&gt;What Is Bridging&lt;/h2&gt;
&lt;p&gt;Bridging highly simulates a network card, making the router believe that the virtual machine&amp;rsquo;s network card truly exists. Personally, I feel it&amp;rsquo;s similar to resistors connected in parallel, whereas NAT (another common virtual machine network connection method) is more like parasitizing on the host&amp;rsquo;s network card.&lt;/p&gt;
&lt;h2 id=&#34;why-use-bridging&#34;&gt;Why Use Bridging&lt;/h2&gt;
&lt;p&gt;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).&lt;/p&gt;</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;"><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;"><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;"><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;"><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;"><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;"><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;"><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;"><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;"><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;"><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>&lt;h2 id=&#34;installation-process&#34;&gt;Installation Process&lt;/h2&gt;
&lt;p&gt;Installed Version: CentOS 6.3&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Using Win32DiskImager to create a USB flash drive image was unsuccessful; installing from an external USB optical drive was successful.&lt;/li&gt;
&lt;li&gt;During the installation process, make sure to select the &amp;ldquo;Virtual Host&amp;rdquo; installation mode.&lt;/li&gt;
&lt;li&gt;The rest can be set to default or slightly modified, such as choosing the time zone.&lt;/li&gt;
&lt;li&gt;After installation, it will include the KVM suite and SSH.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;installation-notes&#34;&gt;Installation Notes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;No internet connection is needed throughout the process, which is much better than Debian and Ubuntu.&lt;/li&gt;
&lt;li&gt;You&amp;rsquo;re not forced to set up a non-root user.&lt;/li&gt;
&lt;li&gt;Before installation, be sure to check whether your CPU supports virtualization and enable the motherboard&amp;rsquo;s virtualization setting. If the motherboard supports virtualization but doesn&amp;rsquo;t have a virtualization option, you can still use virtualization as it&amp;rsquo;s definitely enabled by default. There&amp;rsquo;s a saying that Intel CPUs with a &amp;lsquo;K&amp;rsquo; cannot perform virtualization. &amp;lsquo;K&amp;rsquo; means Intel CPUs that can be overclocked. It seems that faster and newer CPUs are not necessarily better.&lt;/li&gt;
&lt;/ul&gt;</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>&lt;p&gt;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&amp;rsquo;s a summary:&lt;/p&gt;
&lt;h2 id=&#34;database-structure&#34;&gt;Database Structure&lt;/h2&gt;
&lt;p&gt;The database consists of four tables:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;STUDENT&lt;/strong&gt;: Contains student number (SNO), name (SNAME), gender (SSEX), birthday (SBIRTHDAY), and class (CLASS).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;COURSE&lt;/strong&gt;: Includes course number (CNO), name (CNAME), and teacher number (TNO).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SCORE&lt;/strong&gt;: Records student number (SNO), course number (CNO), and degree (DEGREE).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TEACHER&lt;/strong&gt;: Holds teacher number (TNO), name (TNAME), gender (TSEX), birthday (TBIRTHDAY), professional title (PROF), and department (DEPART).&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;sample-data&#34;&gt;Sample Data&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Students such as Zeng Hua, Kang Ming, and Wang Fang are stored with specific details, including their class and gender.&lt;/li&gt;
&lt;li&gt;Courses like &amp;ldquo;Introduction to Computers&amp;rdquo; and &amp;ldquo;Operating Systems&amp;rdquo; are associated with teacher numbers.&lt;/li&gt;
&lt;li&gt;Scores are recorded for students across various courses.&lt;/li&gt;
&lt;li&gt;Teachers are described with their professional roles and departments.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;query-problems&#34;&gt;Query Problems&lt;/h2&gt;
&lt;p&gt;Several SQL queries are suggested for practice, such as:&lt;/p&gt;</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>
