<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>SandGrid</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://lichuanyang.top/en/</id>
  <link href="https://lichuanyang.top/en/" rel="alternate"/>
  <link href="https://lichuanyang.top/en/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, SandGrid</rights>
  <subtitle>Sand accumulates into pagodas, making every step a choice</subtitle>
  <title>Mobility</title>
  <updated>2026-07-03T07:42:21.746Z</updated>
  <entry>
    <author>
      <name>SandGrid</name>
    </author>
    <category term="AI Practice" scheme="https://lichuanyang.top/en/categories/AI-Practice/"/>
    <category term="ai-agent" scheme="https://lichuanyang.top/en/tags/ai-agent/"/>
    <category term="skill-management" scheme="https://lichuanyang.top/en/tags/skill-management/"/>
    <category term="pks" scheme="https://lichuanyang.top/en/tags/pks/"/>
    <category term="workflow" scheme="https://lichuanyang.top/en/tags/workflow/"/>
    <category term="multi-agent-collaboration" scheme="https://lichuanyang.top/en/tags/multi-agent-collaboration/"/>
    <content>
      <![CDATA[<p>I wrote an article before: <a href="https://lichuanyang.top/en/posts/26060/">https://lichuanyang.top/en/posts/26060/</a> . It taught people how to build Agent-independent workflows, so you can spin up different Agents anytime and smoothly use the free&#x2F;trial plans from various providers. After practicing this way, one thing that obviously becomes tedious is skill management. So I wrote another tool to manage skills across different Agents and projects.</p><span id="more"></span><p>Given the current Agent ecosystem is still quite chaotic, everyone does their own thing, and skill storage directories are defined by each provider.</p><p>For example, Cursor puts them in <code>~/.cursor/skills</code>, Claude Code in <code>~/.claude/skills</code>, Trae in <code>~/.trae/skills</code>, OpenCode in <code>~/.config/opencode/skills</code>, and there are also Windsurf, Qoder, Hermes, etc., each with their own path conventions.</p><p>The community is also trying to define a universal <code>.agents/skill</code> directory, which helps a bit, but not much.</p><p>Of course, the problem of skill management isn’t entirely caused by using multiple Agents. Because skills naturally have very different scopes of application. Some skills are suitable for company projects, some for personal projects, some have an even narrower scope, only applicable to a few related projects, and some skills, like news aggregation, I only want to configure in a specific agent.</p><p>If you ask, is it okay not to do such fine-grained management of skills? Well, there’s no major problem, it’s just that the agent will spend a bit more tokens retrieving skills each time. But as a technical person with OCD, I still want to manage these things more carefully, and just don’t let the agent see these skills in places where they’re not needed.</p><p>Based on this, I designed the pks tool. The core idea is to manage all skills in a centralized place, and then, according to needs, write skills to the agent’s skill directory or project directory.</p><p>Specifically, it supports the following features:</p><p><strong>Global Management</strong>: All skills are centrally stored under <code>~/.local/share/pks/skills/</code>, use <code>pks list</code> to view them, and <code>pks new</code> to create new skills.</p><p><strong>Project-level Installation</strong>: After running <code>pks init</code> in a project directory, you can use <code>pks install</code> to install global skills into the project’s <code>.skills/</code> directory.</p><p><strong>Agent-level Installation</strong>: Use <code>pks install-to &lt;agent&gt; &lt;skill&gt;</code> to directly install skills into a specific agent’s skill directory (like <code>~/.cursor/skills/</code>).</p><p><strong>Bidirectional Sync</strong>: If you modify skill files in a project, use <code>pks push</code> to push the changes back to the global repository.</p><p>In practice, I have about these use cases:</p><p>Some skills have limited scope, like a few related projects. In this case, I don’t put the skill in the agent’s configuration, but instead put it in the project. Using the <code>pks install</code> command, you can put skills from the local skill repository into the project directory. Then you can guide the agent to find skills in the skill directory in the AGENTS.md file. For agents that support project-level skills, you can also use the <code>pks link</code> command to soft-link the agent’s corresponding project-level skill directory, like <code>.opencode/skills</code>, to the skills directory;</p><p>Some skills, like news collection skills, I only want to appear in some agents, so I run the <code>pks install-to</code> command to install it in the agent’s skill directory.</p><p>Sometimes skill files need to be modified. I’ll first modify them in a project, then run <code>pks push</code> to push the changes back to the skill repository.</p><p>This way, there’s a relatively proper and reasonable handling process for skills.</p><p>The project is at: <a href="https://github.com/lcy362/personal-skills-manager">https://github.com/lcy362/personal-skills-manager</a> , welcome to try it out. If you have other experiences with skill management, feel free to share.</p>]]>
    </content>
    <id>https://lichuanyang.top/en/posts/34689/</id>
    <link href="https://lichuanyang.top/en/posts/34689/"/>
    <published>2026-07-01T09:16:24.000Z</published>
    <summary>
      <![CDATA[<p>I wrote an article before: <a href="https://lichuanyang.top/en/posts/26060/">https://lichuanyang.top/en/posts/26060/</a> . It taught people how to build Agent-independent workflows, so you can spin up different Agents anytime and smoothly use the free&#x2F;trial plans from various providers. After practicing this way, one thing that obviously becomes tedious is skill management. So I wrote another tool to manage skills across different Agents and projects.</p>]]>
    </summary>
    <title>From Token Farming to Skill Management: My pks Tool in Practice</title>
    <updated>2026-07-03T07:42:21.746Z</updated>
  </entry>
  <entry>
    <author>
      <name>SandGrid</name>
    </author>
    <category term="Tech Talk" scheme="https://lichuanyang.top/en/categories/Tech-Talk/"/>
    <category term="Obsidian" scheme="https://lichuanyang.top/en/tags/Obsidian/"/>
    <category term="llm-wiki" scheme="https://lichuanyang.top/en/tags/llm-wiki/"/>
    <category term="knowledge-management" scheme="https://lichuanyang.top/en/tags/knowledge-management/"/>
    <category term="WeChat-Reading" scheme="https://lichuanyang.top/en/tags/WeChat-Reading/"/>
    <category term="Zhihu" scheme="https://lichuanyang.top/en/tags/Zhihu/"/>
    <category term="AI" scheme="https://lichuanyang.top/en/tags/AI/"/>
    <category term="Notion" scheme="https://lichuanyang.top/en/tags/Notion/"/>
    <content>
      <![CDATA[<p>A while back, I came across Andrej Karpathy’s llm-wiki concept and felt an instant sense of resonance. I’ve always enjoyed writing things down, but the problem was that everything ended up scattered across different places, and I never had the energy to manage it properly. When I discovered llm-wiki, I realized — all that stuff I’d been writing over the years was finally going to pay off.</p><span id="more"></span><h2 id="Why-a-Knowledge-Hub"><a href="#Why-a-Knowledge-Hub" class="headerlink" title="Why a Knowledge Hub"></a>Why a Knowledge Hub</h2><p>First, what is llm-wiki?</p><p>llm-wiki is a concept recently proposed by Andrej Karpathy: take all the written material you’ve accumulated over the years — notes, blog posts, reading highlights, work logs — treat it as a “corpus,” and let an LLM automatically extract concepts, create pages, and weave cross-references into a structured, continuously evolving personal wiki.</p><p>The core premise is simple: everyone produces a substantial amount of structured, insightful writing in their daily work and learning — it’s just scattered everywhere with no connections. llm-wiki uses an LLM-driven process to string these scattered pearls together. You keep producing and collecting content; the LLM handles the organization and management.</p><p>Unlike traditional manual wiki maintenance — creating pages, writing summaries, adding links, tedious and hard to sustain — llm-wiki brings the organizational cost down to nearly zero. You just tell the LLM the structure and rules of your knowledge base (an AGENTS.md file), and it can repeatedly execute ingestion, updating, and auditing operations. My personal experience: watching an AI turn scattered notes into a structured network of cross-references feels like clearing out a long-overdue debt.</p><h2 id="Data-Ingestion"><a href="#Data-Ingestion" class="headerlink" title="Data Ingestion"></a>Data Ingestion</h2><p>The first thing I did was export all my Notion notes — development knowledge, investing insights, and countless miscellaneous records — and move them into Obsidian.</p><p><strong>How to Import Notion Content into Obsidian</strong></p><p>The process is straightforward:</p><ol><li><p><strong>Export your Notion data</strong>: Go to “Settings &amp; members → Settings” in Notion, select “Export all workspace content,” and choose <strong>Markdown &amp; CSV</strong> as the format. You’ll get a ZIP file; after unzipping, each Notion page becomes a <code>.md</code> file, and databases come with additional CSV files. Note: Notion’s free plan exports one workspace at a time — if you have multiple workspaces, export them separately.</p></li><li><p><strong>Install the Obsidian Importer plugin</strong>: Search for “Importer” in the Obsidian community plugin marketplace. It supports one-click import from Notion, Bear, Evernote, OneNote, and more, handling image attachments and internal links automatically. After enabling the plugin, press <code>Cmd+P</code>, search for “Importer: Open Importer,” select the Notion format, and point it to the unzipped folder.</p></li><li><p><strong>Manual import (fallback)</strong>: If you prefer not to use the Importer plugin, just drop the unzipped folder directly into your Obsidian vault. Obsidian natively supports <code>[[wiki-link]]</code> internal links, and Notion’s exported Markdown typically already converts links to this format.</p></li><li><p><strong>Post-import handling</strong>: I recommend placing the original files in a dedicated subdirectory (e.g., <code>raw/notion-export/</code>) and marking them as “read-only.” This preserves the integrity of the original data — a key tenet of the llm-wiki methodology: raw materials are never modified; the LLM builds structured knowledge on top of them. If your Notion had databases, keep the CSV files for reference; embedded Notion-specific blocks (calendars, kanban boards) will lose interactivity after export, but the text content remains.</p></li></ol><p>After the whole process, thousands of scattered notes were consolidated into Obsidian, becoming the first batch of “raw materials” for my knowledge hub.</p><p>Next, I fed Karpathy’s gist to an AI and had it generate the project’s AGENTS.md document. The AI naturally figured out the ingestion and auditing operations required for llm-wiki.</p><p>Then it was time to execute. Watching the AI continuously generate wiki content, categorizing years of accumulated material — it was genuinely satisfying.</p><p>After that, I did a few more things: bringing in my Zhihu writings and WeChat Reading notes. I’ve written over a thousand answers on Zhihu, and over the years I’ve read more than a hundred books on WeChat Reading. Beyond just highlights, these are significant components of my knowledge system. Coincidentally, around that time, WeChat Reading released their official skill, so I put it to use.</p><h2 id="Importing-Zhihu-Content"><a href="#Importing-Zhihu-Content" class="headerlink" title="Importing Zhihu Content"></a>Importing Zhihu Content</h2><p><strong>How to Sync Zhihu Writings to Obsidian</strong></p><p>Zhihu doesn’t provide an official data export API, so I used Playwright for browser automation.</p><p><strong>Steps</strong>:</p><ol><li>Install Playwright: <code>pip install playwright &amp;&amp; playwright install chromium</code></li><li>Run the script for the first time, log in by scanning a QR code or entering your password in the opened Chromium browser</li><li>Once logged in, the script automatically crawls your profile to capture all answers, articles, and status updates</li><li>Login state is persisted locally; subsequent runs use <code>--reuse</code> for silent execution without re-login</li></ol><p><strong>Features</strong>: Incremental sync — only fetches new content, existing files are never reprocessed. Files are organized by content type (answers&#x2F;articles&#x2F;pins).</p><h2 id="Syncing-WeChat-Reading-Notes"><a href="#Syncing-WeChat-Reading-Notes" class="headerlink" title="Syncing WeChat Reading Notes"></a>Syncing WeChat Reading Notes</h2><p><strong>How to Sync WeChat Reading Notes to Obsidian</strong></p><p>WeChat Reading provides an Agent API Gateway — apply for an API key and you’re good to go.</p><p><strong>Steps</strong>:</p><ol><li>Call the <code>/user/notebooks</code> endpoint to get the list of books with notes</li><li>For each new book, fetch highlights and annotations separately</li><li>Group content by chapter and output as well-formatted Markdown files</li></ol><p><strong>Output format</strong>: Book title and author as the heading, each chapter’s highlights in blockquote format (with dates), personal annotations placed below the corresponding highlights.</p><p><strong>Features</strong>: Fully incremental — the script maintains a state file of synced book IDs, only processing new additions on each run. Over 150 books’ worth of notes silently flowed into Obsidian, becoming one of the richest sources of raw material for my knowledge hub.</p><h2 id="Intelligent-Retrieval-with-LLM-Wiki"><a href="#Intelligent-Retrieval-with-LLM-Wiki" class="headerlink" title="Intelligent Retrieval with LLM Wiki"></a>Intelligent Retrieval with LLM Wiki</h2><p>At this point, the content layer was essentially ready. Then I started thinking: since most of my knowledge and creative output is here, could I start distilling… myself?</p><p>I built a simple first version: a “personal” pipeline parallel to the wiki pipeline, with similar ingestion and linting operations. The key difference: wiki focuses on knowledge, while personal focuses on who I am as an individual.</p><p><strong>Knowledge Base vs. Personality Distillation: Two Different AI Processing Approaches</strong></p><p>Here it’s worth explaining the difference — they share the same set of raw materials but have entirely different goals and outputs.</p><p><strong>Knowledge Base (Wiki): Answers “What Do I Know?”</strong></p><p>Extracts objective knowledge from notes, blogs, and reading highlights, generating concept pages (e.g., “distributed consensus”), entity pages (e.g., “Raft algorithm”), and source summary pages (e.g., “Designing Data-Intensive Applications — reading notes”), with dense cross-references between them. The goal: make knowledge queryable and reusable — an externalized second brain.</p><p><strong>Personality Distillation (Personal Model): Answers “Who Am I?”</strong></p><p>Reverse-engineers cognitive patterns, expressive styles, and value orientations from your writing and reading. For example, analyzing technical blog posts might reveal a “thesis-first, case-driven” style; analyzing Zhihu answers might uncover recurring traits like “first-principles reduction” and “quantitative thinking.” The output isn’t knowledge entries — it’s a cognitive map of a person: what you’re good at, how you approach problems, what you value.</p><p><strong>Comparison</strong></p><table><thead><tr><th>Dimension</th><th>Knowledge Base</th><th>Personality Distillation</th></tr></thead><tbody><tr><td>Core question</td><td>What do I know?</td><td>Who am I?</td></tr><tr><td>Input</td><td>Notes, blogs, reading highlights</td><td>All personal writing and reading records</td></tr><tr><td>Output</td><td>Concept&#x2F;entity&#x2F;source pages + cross-references</td><td>Domain depth, cognitive traits, expressive style, values</td></tr><tr><td>Direction</td><td>Outward: structuring external knowledge</td><td>Inward: modeling personal cognition</td></tr><tr><td>Workflow</td><td>Ingest → Query → Lint → Audit</td><td>Ingest → Query → Lint → Audit (isomorphic)</td></tr></tbody></table><p>Both processes are structurally similar, but one looks outward, structuring and organizing the knowledge you possess; the other looks inward, distilling and modeling your cognitive traits as an individual. This “two sides of the same coin” design is, I think, the most fascinating part of the entire system.</p><p>Lately I’ve been looking at projects like Nüwa online to see if there are better approaches to personality distillation.</p><h2 id="Results-and-Reflections"><a href="#Results-and-Reflections" class="headerlink" title="Results and Reflections"></a>Results and Reflections</h2><p>That’s the recent story of my knowledge hub. If you have thoughts or ideas, I’d love to hear them.</p><h2 id="Frequently-Asked-Questions"><a href="#Frequently-Asked-Questions" class="headerlink" title="Frequently Asked Questions"></a>Frequently Asked Questions</h2><h3 id="Q-Is-Obsidian-suitable-for-programmers-doing-knowledge-management"><a href="#Q-Is-Obsidian-suitable-for-programmers-doing-knowledge-management" class="headerlink" title="Q: Is Obsidian suitable for programmers doing knowledge management?"></a>Q: Is Obsidian suitable for programmers doing knowledge management?</h3><p>Absolutely. Obsidian’s core philosophy — local Markdown files, bidirectional links, graph visualization — naturally aligns with how programmers work. You already know Markdown syntax. Local file storage means full data control and Git versioning. Bidirectional links let you manage knowledge references just like code dependencies. Layer on the llm-wiki approach, and AI can automatically extract concepts from scattered notes, create pages with cross-references, transforming loose documents into a structured knowledge network.</p><h3 id="Q-Does-LLM-Wiki-require-a-GPU"><a href="#Q-Does-LLM-Wiki-require-a-GPU" class="headerlink" title="Q: Does LLM Wiki require a GPU?"></a>Q: Does LLM Wiki require a GPU?</h3><p>No local GPU deployment is needed. The core idea of LLM Wiki is <strong>letting an LLM process your text</strong>, not running a model yourself. You simply call a cloud LLM API, feed it your Markdown files, and let it extract concepts, generate pages, and build cross-references. The entire “hardware” requirement is just Obsidian plus any tool that can call an LLM API (like WorkBuddy or another Agent).</p><h3 id="Q-What’s-the-difference-between-llm-wiki-and-a-traditional-wiki"><a href="#Q-What’s-the-difference-between-llm-wiki-and-a-traditional-wiki" class="headerlink" title="Q: What’s the difference between llm-wiki and a traditional wiki?"></a>Q: What’s the difference between llm-wiki and a traditional wiki?</h3><p>A traditional wiki requires you to manually create pages, write summaries, and add internal links — high maintenance cost that’s hard to sustain. llm-wiki reduces the organizational cost to nearly zero — you just keep producing and collecting written content, and the LLM reads your AGENTS.md rules, repeatedly running ingestion, updating, and auditing operations to generate a structured cross-reference network. In short: a traditional wiki means “you organize knowledge”; llm-wiki means “AI organizes knowledge for you.”</p><h3 id="Q-What’s-the-difference-between-knowledge-distillation-and-personality-distillation"><a href="#Q-What’s-the-difference-between-knowledge-distillation-and-personality-distillation" class="headerlink" title="Q: What’s the difference between knowledge distillation and personality distillation?"></a>Q: What’s the difference between knowledge distillation and personality distillation?</h3><p>Both share the same set of raw materials, but their goals and outputs are entirely different. <strong>Knowledge Base (Wiki)</strong> answers “What do I know?” — extracting objective knowledge from notes and reading highlights, generating concept pages and cross-references. <strong>Personality Distillation (Personal Model)</strong> answers “Who am I?” — reverse-engineering your cognitive patterns, expressive style, and value orientations from your writing and reading records. One looks outward (structuring knowledge), the other looks inward (modeling personal cognition). The workflows are similar, but the direction is opposite.</p><h2 id="Quick-Start-Guide"><a href="#Quick-Start-Guide" class="headerlink" title="Quick Start Guide"></a>Quick Start Guide</h2><ol><li><strong>Install Obsidian</strong>: Download the client from <a href="https://obsidian.md/">obsidian.md</a> and create a local Vault — just a local folder.</li><li><strong>Configure LLM Wiki</strong>: Create <code>AGENTS.md</code> in the Vault root, following Karpathy’s llm-wiki approach to define maintenance rules, including ingestion, updating, and auditing workflows. Let AI tools read this file to automatically extract concepts and build cross-references from raw notes.</li><li><strong>Import Notion notes</strong>: Export from Notion settings as Markdown + CSV, use the Obsidian Importer plugin for one-click import, or drop the extracted Markdown folder directly into the Vault.</li><li><strong>Connect WeChat Reading</strong>: Apply for a WeChat Reading API key, call the <code>/user/notebooks</code> endpoint to fetch books, pull highlights and notes, group by chapter, and output as Markdown files into the Vault.</li><li><strong>Import Zhihu and blog</strong>: Use a Playwright script to auto-scrape Zhihu answers and articles; copy blog Markdown source files into the Vault. Once done, run a full wiki ingestion via AI to generate the complete cross-reference network.</li></ol><p>Source: <a href="https://lichuanyang.top/en/posts/18804/">https://lichuanyang.top/en/posts/18804/</a></p>]]>
    </content>
    <id>https://lichuanyang.top/en/posts/18804/</id>
    <link href="https://lichuanyang.top/en/posts/18804/"/>
    <published>2026-06-26T08:18:13.000Z</published>
    <summary>
      <![CDATA[<p>A while back, I came across Andrej Karpathy’s llm-wiki concept and felt an instant sense of resonance. I’ve always enjoyed writing things down, but the problem was that everything ended up scattered across different places, and I never had the energy to manage it properly. When I discovered llm-wiki, I realized — all that stuff I’d been writing over the years was finally going to pay off.</p>]]>
    </summary>
    <title>Bringing Notes, WeChat Reading, and Zhihu into Obsidian: My LLM-Wiki Knowledge Hub</title>
    <updated>2026-06-27T03:23:24.826Z</updated>
  </entry>
  <entry>
    <author>
      <name>SandGrid</name>
    </author>
    <category term="AI Practice" scheme="https://lichuanyang.top/en/categories/AI-Practice/"/>
    <category term="ai-video" scheme="https://lichuanyang.top/en/tags/ai-video/"/>
    <category term="open-source" scheme="https://lichuanyang.top/en/tags/open-source/"/>
    <category term="free-tools" scheme="https://lichuanyang.top/en/tags/free-tools/"/>
    <category term="text-to-video" scheme="https://lichuanyang.top/en/tags/text-to-video/"/>
    <category term="agnes-ai" scheme="https://lichuanyang.top/en/tags/agnes-ai/"/>
    <content>
      <![CDATA[<blockquote><p>“The solution is not to suppress AI, but to make it a more equitable capability, so that everyone knows how to create more with AI. This is a very important vision for our company — to make world-class AI belong to everyone.”</p></blockquote><p>This is something Bruce Yang, the founder of Agnes AI, said in an interview.</p><p>Many Chinese AI companies today — DeepSeek, Zhipu, and others — are driving down the price of AI. To be fair, the cost of text and code processing has already been pushed remarkably low. But video is different. Making AI videos today has an absurdly high barrier — overseas services like Runway and Pika charge tens of dollars monthly, domestic platforms like Jimeng and Keling charge by the second once free quotas run out, and running open-source models locally requires a GPU costing over ten thousand RMB.</p><p>Objectively speaking, video generation is genuinely expensive right now. Making industrial-grade video generation available to everyone isn’t realistic. But ordinary people should still have ways to experiment and create. Thanks to Agnes for opening up their video model and giving us this opportunity. This project is just a small contribution toward that goal. <a href="https://github.com/lcy362/agnes-video-generator">Agnes Video Generator</a> (<a href="https://video.lichuanyang.top/">official website</a>) — it’s a free AI video generator. Not “free trial” or “free for 3 generations,” but the whole thing: script writing, image synthesis, video rendering, voiceover, subtitles, all at zero cost. You just need a free API key from <a href="https://platform.agnes-ai.com/">Agnes AI</a>.</p><p>Agnes’s video model isn’t perfect yet, to be honest. But I want to use this project to grow alongside Agnes, and contribute in my own small way toward AI equity.</p><span id="more"></span><h2 id="Multiple-Ways-to-Use-It"><a href="#Multiple-Ways-to-Use-It" class="headerlink" title="Multiple Ways to Use It"></a>Multiple Ways to Use It</h2><p>Give it a text prompt, get a video back. A few different modes:</p><p><strong>Simple Video.</strong> A straightforward API wrapper — good for testing. Most API parameters are exposed as config options.</p><p><strong>Creative Video.</strong> You write a story idea, like “dark version of The Frog Prince,” and the AI handles everything: expand story → generate character references → split into scenes → write shot prompts → generate per-scene video → narration → subtitles → final output. Ten steps, all automatic. By pre-generating end frames, it ensures the best possible visual continuity between scenes.</p><p><strong>Manuscript Video &amp; Digital Anchor.</strong> Paste a long article or script — it auto-splits by speech duration and generates video per segment, or puts a digital anchor there to read it. Everything stitched with a unified TTS narration + subtitle track. Great for explainers and course content.</p><p>For detailed parameters and usage guides for each mode, check the <a href="https://video.lichuanyang.top/">official website</a>.</p><h2 id="Getting-It-Running"><a href="#Getting-It-Running" class="headerlink" title="Getting It Running"></a>Getting It Running</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/lcy362/agnes-video-generator.git</span><br><span class="line"><span class="built_in">cd</span> agnes-video-generator</span><br><span class="line">./start.sh</span><br></pre></td></tr></table></figure><p>That’s it. <code>start.sh</code> creates a virtual environment, installs dependencies, and starts the server.</p><p>Once it’s running, open <code>http://localhost:8765</code>, paste in your Agnes AI API key at the top, pick a mode, write your idea, and wait patiently for the results.</p><p>If you’re using an AI coding assistant like Cursor or Claude, I’ve included a dedicated guide for AI Agents. Just tell your agent to read the <code>Agents.md</code> file in the project — it’ll handle the whole setup on its own.</p><h2 id="Demos"><a href="#Demos" class="headerlink" title="Demos"></a>Demos</h2><p>I made a few demos — check them out:</p><ul><li><a href="https://v.douyin.com/L4F6KdGnD6U/">The Frog Prince — no narration</a> — 5 scenes, keyframes chaining, fully auto-generated</li><li><a href="https://v.douyin.com/l2FlbF1Jdz0/">Same story, with voiceover and subtitles</a> — AI narration + auto subtitles, see the subtitle effect</li><li><a href="https://v.douyin.com/eSGE9KENWVU/">Manuscript video</a> — pasted a long article, auto-split with different visuals per segment</li></ul><h2 id="That’s-About-It"><a href="#That’s-About-It" class="headerlink" title="That’s About It"></a>That’s About It</h2><p>Going back to Bruce Yang’s words — “making world-class AI belong to everyone.”</p><p>This project isn’t some grand mission. It’s just about keeping the door to AI video creation open. No subscription, no fancy GPU, no cost at all — just a free API key and a machine that can run Python.</p><p>Code on <a href="https://github.com/lcy362/agnes-video-generator">GitHub</a>, official website at <a href="https://video.lichuanyang.top/">video.lichuanyang.top</a>. Bug reports welcome.</p><p>Source: <a href="https://lichuanyang.top/en/posts/22470/">https://lichuanyang.top/en/posts/22470/</a></p>]]>
    </content>
    <id>https://lichuanyang.top/en/posts/22470/</id>
    <link href="https://lichuanyang.top/en/posts/22470/"/>
    <published>2026-06-16T05:21:16.000Z</published>
    <summary>Tired of paying per second for AI video? I open-sourced a 100% free AI video generator — text, images, video, and voiceover all at zero cost. One click to produce narrated, subtitled multi-scene AI videos.</summary>
    <title>Free AI Video Generator: How I Built a Zero-Cost Tool for Narrated Multi-Scene Videos</title>
    <updated>2026-06-27T03:23:24.827Z</updated>
  </entry>
  <entry>
    <author>
      <name>SandGrid</name>
    </author>
    <category term="AI Practice" scheme="https://lichuanyang.top/en/categories/AI-Practice/"/>
    <category term="agnes" scheme="https://lichuanyang.top/en/tags/agnes/"/>
    <category term="agnes free model" scheme="https://lichuanyang.top/en/tags/agnes-free-model/"/>
    <category term="agnes video" scheme="https://lichuanyang.top/en/tags/agnes-video/"/>
    <category term="Agnes-Video-V2.0" scheme="https://lichuanyang.top/en/tags/Agnes-Video-V2-0/"/>
    <category term="AI video generation" scheme="https://lichuanyang.top/en/tags/AI-video-generation/"/>
    <category term="free AI models" scheme="https://lichuanyang.top/en/tags/free-AI-models/"/>
    <category term="open source" scheme="https://lichuanyang.top/en/tags/open-source/"/>
    <category term="ViMax" scheme="https://lichuanyang.top/en/tags/ViMax/"/>
    <content>
      <![CDATA[<p>A while back, I wrote about “token hunting” — the practice of bouncing between free AI models instead of paying for subscriptions. At the time, free models mostly meant text and maybe some image generation. Then Agnes AI quietly dropped something unexpected: <strong>free video generation models.</strong></p><p>Not text. Not images. Video. Three models — <code>agnes-video-v2.0</code>, <code>agnes-image-2.1-flash</code>, and <code>agnes-2.0-flash</code> — all free, no credit card, no GPU, just an API key. Among free agnes video generation solutions, Agnes-Video-V2.0 stands out as the most capable, supporting multiple generation modes with impressive output quality.</p><p>Naturally, I couldn’t resist. I took the open-source ViMax framework and rebuilt it as a full Agnes-powered pipeline, with a bunch of usability improvements along the way.</p><span id="more"></span><h2 id="What-Agnes-Free-Models-Actually-Give-You"><a href="#What-Agnes-Free-Models-Actually-Give-You" class="headerlink" title="What Agnes Free Models Actually Give You"></a>What Agnes Free Models Actually Give You</h2><p>Let’s start with what’s on the table. After <a href="https://platform.agnes-ai.com/">signing up at Agnes AI</a>, you get an API key that unlocks three models:</p><ul><li><strong>agnes-2.0-flash</strong> (Chat): Writes stories, scripts, and visual prompts from a single idea</li><li><strong>agnes-image-2.1-flash</strong> (Image): Text-to-image for character references and keyframes</li><li><strong>agnes-video-v2.0</strong> (Video): Supports text-to-video, image-to-video, and keyframes modes</li></ul><p>The API follows the OpenAI-compatible format at <code>https://apihub.agnes-ai.com/v1</code>, making integration straightforward. The video model is async — submit a task, get a task_id, poll for results. Standard stuff for cloud video generation.</p><p>The real kicker: <strong>these three models cover the entire pipeline from idea to finished video.</strong> No mixing providers, no juggling multiple API keys. One key, one base URL, done.</p><h2 id="The-Rewrite-From-Multi-Provider-to-Single-Agnes-Key"><a href="#The-Rewrite-From-Multi-Provider-to-Single-Agnes-Key" class="headerlink" title="The Rewrite: From Multi-Provider to Single Agnes Key"></a>The Rewrite: From Multi-Provider to Single Agnes Key</h2><p>The original ViMax, open-sourced by HKU, is a well-designed agentic video generation framework. It’s intentionally provider-agnostic — one service for LLM, another for images, yet another for video. Flexible, yes, but you end up managing three sets of credentials, three error handlers, three rate limit strategies.</p><p>My approach was simple: <strong>since all three Agnes models are free, just use Agnes for everything.</strong> One API key, one base URL, minimal headache.</p><p>Here’s what changed:</p><p><strong>Screenwriter module</strong>: All LLM calls now hit <code>agnes-2.0-flash</code>. It takes a one-line idea and produces a full story, breaks it into scenes, writes visual prompts for each scene, and generates end-frame descriptions. Standard chat&#x2F;completions endpoint, temperature 0.7.</p><p><strong>Image generator</strong>: Switched to <code>agnes-image-2.1-flash</code> for text-to-image and <code>agnes-image-2.0-flash</code> for image-to-image. The newer 2.1 model generates character reference images; the older 2.0 handles scene transition frames.</p><p><strong>Video generator</strong>: The core — <code>agnes-video-v2.0</code>. Supports three modes: pure text-to-video (t2v), image-guided video (ti2vid), and keyframe interpolation (keyframes). Each scene supports 5 to 20 seconds at 24fps.</p><p>After the rewrite, the entire project depends on a single API provider. Write one key in <code>.api_key</code>, and you’re done.</p><h2 id="Usability-Improvements-Don’t-Make-Users-Think"><a href="#Usability-Improvements-Don’t-Make-Users-Think" class="headerlink" title="Usability Improvements: Don’t Make Users Think"></a>Usability Improvements: Don’t Make Users Think</h2><p>The original ViMax had a very “research project” feel — parameters hardcoded in Python, changing a creative idea meant editing source code. Not great. So I did a bunch of usability work.</p><h3 id="YAML-Creative-Configs"><a href="#YAML-Creative-Configs" class="headerlink" title="YAML Creative Configs"></a>YAML Creative Configs</h3><p>The biggest change: YAML-based creative files. Each video idea lives in its own <code>.yaml</code> file under <code>creatives/</code>:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">&quot;frog&quot;</span></span><br><span class="line"><span class="attr">idea:</span> <span class="string">|</span></span><br><span class="line"><span class="string">  A dark twist on The Frog Prince — the princess kisses</span></span><br><span class="line"><span class="string">  the frog, but instead of a handsome prince, the frog</span></span><br><span class="line"><span class="string">  transforms into an even more terrifying creature</span></span><br><span class="line"><span class="string"></span><span class="attr">user_requirement:</span> <span class="string">|</span></span><br><span class="line"><span class="string">  5 scenes, 10 seconds each, gothic dark fairytale</span></span><br><span class="line"><span class="string"></span><span class="attr">style:</span> <span class="string">&quot;Gothic dark fairytale, cinematic quality&quot;</span></span><br><span class="line"><span class="attr">chaining_mode:</span> <span class="string">keyframes</span></span><br><span class="line"><span class="attr">video_width:</span> <span class="number">768</span></span><br><span class="line"><span class="attr">video_height:</span> <span class="number">1152</span></span><br></pre></td></tr></table></figure><p>New video idea? Write a YAML, run one command, done. No touching Python source code.</p><h3 id="One-Click-Launcher"><a href="#One-Click-Launcher" class="headerlink" title="One-Click Launcher"></a>One-Click Launcher</h3><p><code>start.sh</code> wraps everything: auto-loads the API key from <code>.api_key</code>, activates the virtual environment, lists available creatives, and runs the pipeline. No arguments lists all creatives; pass a name and it just runs:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">./start.sh          <span class="comment"># list all creatives</span></span><br><span class="line">./start.sh frog     <span class="comment"># generate &quot;The Frog Prince&quot;</span></span><br></pre></td></tr></table></figure><h3 id="Smart-Caching-and-Resume"><a href="#Smart-Caching-and-Resume" class="headerlink" title="Smart Caching and Resume"></a>Smart Caching and Resume</h3><p>This one matters because video generation is genuinely slow.</p><p>Every intermediate result — story text, scene scripts, character reference images, per-scene videos — is persisted to disk. If the pipeline crashes halfway, re-running the same creative skips completed steps and only generates what’s missing.</p><p>The caching granularity goes down to individual scene videos: if you had 5 scenes and crashed after 3, re-running only generates the remaining 2. No restart from scratch.</p><h3 id="Multimodal-Image-Analysis"><a href="#Multimodal-Image-Analysis" class="headerlink" title="Multimodal Image Analysis"></a>Multimodal Image Analysis</h3><p>You can provide your own character reference images or custom end-frame images per scene. The system analyzes them via the multimodal LLM and weaves the visual descriptions into the story and prompt generation.</p><p>For example, provide a hand-drawn cartoon character, and the system will identify its visual features and maintain consistency across all generated scenes.</p><h2 id="Character-Consistency-Two-Stage-Lockdown"><a href="#Character-Consistency-Two-Stage-Lockdown" class="headerlink" title="Character Consistency: Two-Stage Lockdown"></a>Character Consistency: Two-Stage Lockdown</h2><p>The biggest pitfall in AI video generation is character consistency — your protagonist has black hair in scene one and suddenly goes blonde in scene two.</p><p>ViMax-Agnes uses a two-stage approach:</p><p><strong>Stage one</strong>: Generate (or accept from the user) a character reference image. The screenwriter module extracts a detailed appearance description from the story — body type, hairstyle, clothing, color palette, distinguishing features — and feeds it to the image model to produce a full-body reference.</p><p><strong>Stage two</strong>: Every scene video starts from this reference image as its first frame, generated via <code>ti2vid</code> mode. The video model animates from the same visual anchor, naturally preserving character consistency.</p><p>In practice, cartoon and stylized art styles get the best consistency. For photorealistic output, providing your own reference image works better than relying on AI generation.</p><h2 id="Three-Scene-Chaining-Modes"><a href="#Three-Scene-Chaining-Modes" class="headerlink" title="Three Scene Chaining Modes"></a>Three Scene Chaining Modes</h2><p>This was the most interesting part of the rewrite. I adapted three chaining modes on top of the Agnes API:</p><p><strong><code>none</code> (Independent)</strong>: Each scene is generated independently, sharing only the character reference image. Fastest, but hard cuts between scenes — no transitions.</p><p><strong><code>ti2vid</code> (Transition Frames)</strong>: Sequential generation. After each scene, the last frame is extracted, then img2img generates a “transition frame” blending the end of one scene into the start of the next. Smoother transitions, but error accumulates — artifacts in earlier scenes propagate forward.</p><p><strong><code>keyframes</code> (Keyframe Interpolation)</strong>: The recommended mode. Each scene specifies both a first frame and a last frame, and the video model interpolates motion between them. End frames are AI-generated from scene descriptions (or manually provided). With both endpoints determined, transitions are the smoothest of the three modes.</p><p>Switching between modes is a single YAML field — no code changes needed.</p><h2 id="Results"><a href="#Results" class="headerlink" title="Results"></a>Results</h2><p>I tested several creative ideas:</p><ul><li><strong>The Frog Prince</strong>: 5-scene dark fairytale, keyframes mode, fully auto-generated</li><li><strong>Girl Dunk</strong>: 3-scene sports theme, character consistency held up well</li><li><strong>Beach Dance</strong>: 4-scene MV style, smooth scene transitions</li><li><strong>Hot Spring Robot</strong>: 3-scene cozy vibe, cartoon style had the best consistency</li></ul><p>The main bottleneck is video generation itself — each scene takes a few minutes. But with the caching system, the debugging cost stays manageable.</p><h2 id="Agnes-Video-V2-0-Technical-Details"><a href="#Agnes-Video-V2-0-Technical-Details" class="headerlink" title="Agnes-Video-V2.0 Technical Details"></a>Agnes-Video-V2.0 Technical Details</h2><p>For those who want to dig deeper, a few implementation notes:</p><p><strong>Video parameters</strong>: Frame counts follow the <code>8n+1</code> rule with a 441-frame cap. 5 seconds &#x3D; 121 frames, 10s &#x3D; 241, 15s &#x3D; 361, 18s and 20s &#x3D; 441 (at 24 and 22 fps respectively).</p><p><strong>Image upload workaround</strong>: The video API needs image URLs, not base64. Local images get “uploaded” through the image API — a no-op i2i call with <code>agnes-image-2.1-flash</code> (prompt: “keep the image exactly as it is”) that returns a hosted URL. Falls back to inline base64 if the upload fails.</p><p><strong>Retry mechanism</strong>: Video submissions auto-retry with exponential backoff on 429 (rate limit) and 5xx (server errors), up to 5 attempts. Polling has no timeout — video generation is just slow, and you have to wait.</p><p><strong>Minimal dependencies</strong>: The entire project uses only 5 Python packages: requests, pydantic, PyYAML, moviepy, and tenacity. No PyTorch, no CUDA — it’s purely an API orchestration layer.</p><h2 id="Wrapping-Up"><a href="#Wrapping-Up" class="headerlink" title="Wrapping Up"></a>Wrapping Up</h2><p>Agnes’s free model lineup is genuinely generous. The agnes free models cover text, image, and video, with an OpenAI-compatible API format and zero signup friction. For anyone curious about AI video generation without burning money, it’s a solid starting point.</p><p>The ViMax-Agnes rewrite confirmed something I’d been thinking: <strong>when free models are good enough, the “hunt for free tokens” strategy scales seamlessly from text to video.</strong> One API key, one command, one YAML file — from a sentence to a complete multi-scene video.</p><p>The project is open source: <a href="https://github.com/lcy362/vimax-agnes">github.com&#x2F;lcy362&#x2F;vimax-agnes</a>. Stars and issues welcome.</p><blockquote><p><strong>Updated June 2026</strong>: This tool has evolved into <a href="https://github.com/lcy362/agnes-video-generator">Agnes Video Generator</a> with Web UI and multilingual support. The new version is more feature-complete — check it out.</p></blockquote><h2 id="Quick-Start-Guide"><a href="#Quick-Start-Guide" class="headerlink" title="Quick Start Guide"></a>Quick Start Guide</h2><ol><li><strong>Setup</strong>: Register an <a href="https://platform.agnes-ai.com/">Agnes AI</a> account and get your API Key. Ensure Python 3.8+ and Git are installed locally.</li><li><strong>Clone the project</strong>: <code>git clone https://github.com/lcy362/vimax-agnes &amp;&amp; cd vimax-agnes</code>, write your API Key to <code>.api_key</code>.</li><li><strong>Create creative config</strong>: Create a YAML file under <code>creatives/</code> defining <code>name</code>, <code>idea</code>, <code>style</code>, <code>chaining_mode</code>, etc.</li><li><strong>Generate video</strong>: Run <code>./start.sh &lt;creative_name&gt;</code>. The system auto-executes script generation, image generation, and video generation in one pipeline, with intermediate results cached for resume support.</li><li><strong>Review output</strong>: Videos are saved under <code>output/</code>. Check character consistency, scene transitions, and overall quality. Tweak the YAML config and rerun if needed.</li></ol><p>Source: <a href="https://lichuanyang.top/en/posts/65500/">https://lichuanyang.top/en/posts/65500/</a></p>]]>
    </content>
    <id>https://lichuanyang.top/en/posts/65500/</id>
    <link href="https://lichuanyang.top/en/posts/65500/"/>
    <published>2026-06-11T14:00:00.000Z</published>
    <summary>Agnes AI released free video models including Agnes-Video-V2.0 — no GPU, no credit card needed. I rewrote the open-source ViMax framework into a full Agnes pipeline with major usability improvements.</summary>
    <title>Agnes Free Models: I Rewrote ViMax to Generate AI Videos for $0</title>
    <updated>2026-06-27T03:53:05.199Z</updated>
  </entry>
  <entry>
    <author>
      <name>SandGrid</name>
    </author>
    <category term="AI Practice" scheme="https://lichuanyang.top/en/categories/AI-Practice/"/>
    <category term="ai-agent" scheme="https://lichuanyang.top/en/tags/ai-agent/"/>
    <category term="workflow" scheme="https://lichuanyang.top/en/tags/workflow/"/>
    <category term="llm" scheme="https://lichuanyang.top/en/tags/llm/"/>
    <category term="productivity" scheme="https://lichuanyang.top/en/tags/productivity/"/>
    <category term="cli" scheme="https://lichuanyang.top/en/tags/cli/"/>
    <content>
      <![CDATA[<p>Last post ended with this: <strong>Agents are just tools. Documentation is the core.</strong></p><p>After that went up, someone asked: sure, but how do you actually manage all those docs? You can’t just copy-paste them into every project, can you?</p><p>Nope, you can’t.</p><p>So I built a tool to solve exactly that — <a href="https://github.com/lcy362/personal-skills-manager">pks</a> (Personal Skills Manager). 408 lines of pure bash, zero dependencies. It does one thing: packages your AI workflow docs as skills, installs them into projects on demand, and locks you into no Agent platform.</p><span id="more"></span><h2 id="What-Is-a-Skill"><a href="#What-Is-a-Skill" class="headerlink" title="What Is a Skill"></a>What Is a Skill</h2><p>If you’ve used OpenCode, Cursor, or any other Agent platform, you’ve probably encountered the concept of a “skill.” Platforms call them different things — skills, rules, custom instructions — but they all mean the same thing: <strong>structured instructions you give an Agent so it knows how to handle specific types of tasks.</strong></p><p>“When calling our API gateway, parameters must be flat in the request body — no nesting under <code>params</code>.” That’s a skill. “Commit messages must follow <code>type(scope): description</code> format.” Also a skill. “Table names use snake_case, every column must have a comment.” Still a skill.</p><p>The underlying model provides general-purpose reasoning — it knows how to write code, how to call APIs. But the quirks of your API gateway, your team’s naming conventions, why your project chose this architecture — the model doesn’t know any of that. You have to tell it. Skills are how.</p><p>Each platform manages skills differently. OpenCode uses <code>.opencode/skills/</code> directories, Cursor uses <code>.cursorrules</code>, Claude Code reads <code>CLAUDE.md</code>. Different formats, different mechanisms, same idea: a document the Agent reads and follows.</p><p>Skills themselves don’t depend on any platform. A skill folder written in Markdown, dropped into any project — any Agent that finds it will read it and use it. <strong>Plain Markdown. Anyone can read it.</strong></p><h2 id="Where’s-the-Problem"><a href="#Where’s-the-Problem" class="headerlink" title="Where’s the Problem"></a>Where’s the Problem</h2><p>Skills are great. But here’s the catch: you have multiple projects.</p><p>A team coding standard — project A needs it, project B needs it, project C needs it too. How do you manage that?</p><p>The common approach is to configure it at the Agent level — a standard “install skill” operation. For example, you install a WeRead skill into OpenCode, and boom, every project you open with OpenCode can use it.</p><p>That works fine… unless you read my last post.</p><p>If you’re trying to build agent-independent workflows, this approach falls apart fast. The day you spot a new Agent giving away free tokens, download it, and realize you have to manually migrate all your skills? Kinda defeats the purpose.</p><p>That’s exactly why I built pks — to decouple skill management from any specific Agent or project, and make it a standalone system of its own.</p><h2 id="How-pks-Solves-It"><a href="#How-pks-Solves-It" class="headerlink" title="How pks Solves It"></a>How pks Solves It</h2><p>pks is best suited for <strong>non-general, project- or team-specific</strong> instructions: your coding style preferences and commit conventions, your team’s code standards and CI&#x2F;CD workflows, your project’s architecture decision records and API design constraints.</p><p>pks does one thing: <strong>manage all your skills in one place, install them into any project on demand.</strong></p><p>All skills live in a single global repo. Each project installs only what it needs.</p><p>Three core global commands:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">pks new my-skill       <span class="comment"># Create a new skill skeleton from template</span></span><br><span class="line">pks list               <span class="comment"># List all your skills</span></span><br><span class="line">pks delete my-skill    <span class="comment"># Delete one you don&#x27;t need</span></span><br></pre></td></tr></table></figure><p>Project-level operations are just as simple:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> your-project</span><br><span class="line">pks init                        <span class="comment"># Initialize (one-time only)</span></span><br><span class="line">pks install my-skill            <span class="comment"># Install a skill</span></span><br><span class="line">pks status                      <span class="comment"># See what&#x27;s installed</span></span><br><span class="line">pks uninstall my-skill          <span class="comment"># Remove it</span></span><br></pre></td></tr></table></figure><p>Once installed, the skill gets copied into the project’s <code>.skills/</code> directory:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">your-project/</span><br><span class="line">├── .skills/</span><br><span class="line">│   ├── INDEX.md              # Auto-generated index</span><br><span class="line">│   └── my-skill/</span><br><span class="line">│       └── SKILL.md           # Your skill content</span><br><span class="line">└── ...</span><br></pre></td></tr></table></figure><h2 id="How-Agents-Discover-Skills"><a href="#How-Agents-Discover-Skills" class="headerlink" title="How Agents Discover Skills"></a>How Agents Discover Skills</h2><p>It’s actually a very natural flow.</p><p>Every time you install or uninstall, pks automatically rebuilds <code>.skills/INDEX.md</code>. This index lists all installed skills with descriptions and versions, and includes one line:</p><blockquote><p>Agents: read the <code>SKILL.md</code> file in each skill directory below for relevant instructions.</p></blockquote><p>Any Agent entering the project will see this directory, read INDEX.md, and load the specific SKILL.md files as needed.</p><p>At most, you might add a line in your project’s AGENTS.md pointing to the skills directory. In my experience, most Agents don’t even need that — they find the skills naturally on their own.</p><h2 id="When-Would-You-Use-This"><a href="#When-Would-You-Use-This" class="headerlink" title="When Would You Use This"></a>When Would You Use This</h2><p>Here’s the most common scenario: you’re leading a project and a new developer is joining the team.</p><p>The old way? Hand over a README, verbally explain “we write commits like this,” “that internal library works like that,” “don’t mess with database fields.” The rest? Figure it out yourself.</p><p>With an Agent, it’s about the same — you stuff the rules into CLAUDE.md or AGENTS.md, and the Agent consumes them every single conversation, regardless of whether the current task has anything to do with those rules.</p><p>Try a different approach. Break this knowledge into a few skills:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">skills/</span><br><span class="line">├── team-coding-style/     # Team coding standards, naming conventions, commit format</span><br><span class="line">│   └── SKILL.md</span><br><span class="line">├── internal-api-gateway/  # Internal API gateway: flat params, auth, pitfall notes</span><br><span class="line">│   ├── SKILL.md</span><br><span class="line">│   ├── pitfalls.md        # Common gotchas (e.g., don&#x27;t nest params)</span><br><span class="line">│   └── fields.md          # Response field reference</span><br><span class="line">├── project-architecture/  # Architecture decisions: module layout, directory structure, rationale</span><br><span class="line">│   └── SKILL.md</span><br><span class="line">└── db-conventions/        # Database conventions: naming, indexing strategy, migration process</span><br><span class="line">    └── SKILL.md</span><br></pre></td></tr></table></figure><p>New hire? <code>pks init</code>, <code>pks install team-coding-style</code>, <code>pks install project-architecture</code> — done in minutes. The Agent reads these skills and immediately knows how this project works. Different person, different machine, different Agent platform — same skills, same results.</p><p>And <strong>only installed skills consume tokens.</strong> A pure frontend project doesn’t need the database conventions skill. A legacy project doesn’t need the onboarding skill. Compared to stuffing all rules into one massive config file that the Agent reads every single conversation, installing on demand saves a lot of wasted tokens — which is basically farming your own tokens.</p><p>These <strong>non-general</strong> instructions are pks’s sweet spot. General capabilities? The Agent already has those. But your team standards, your project conventions, the pitfalls you’ve stepped in — those are only useful to you, and should only show up when needed.</p><h2 id="Why-Pure-Bash"><a href="#Why-Pure-Bash" class="headerlink" title="Why Pure Bash"></a>Why Pure Bash</h2><p>The entire pks CLI is 408 lines of bash. No Python, no Node.js, no runtime. <code>git clone</code> and it runs.</p><p>Why so extreme? Because a skill management tool shouldn’t introduce any overhead. Your workflow is already complex enough — the tool managing it should be as simple as possible. Simple enough that it won’t break because some runtime had a version update.</p><p>Bash works out of the box on macOS and Linux. A script from ten years ago still runs today. That’s the beauty of zero dependencies: <strong>your workflow docs outlive any framework.</strong></p><h2 id="A-Few-Design-Details-Worth-Mentioning"><a href="#A-Few-Design-Details-Worth-Mentioning" class="headerlink" title="A Few Design Details Worth Mentioning"></a>A Few Design Details Worth Mentioning</h2><p>pks has a few design choices worth calling out.</p><p><strong>Semantic versioning</strong>: Each skill’s YAML front matter includes a <code>version</code> field. When a skill evolves, the version bumps. You always know which version a project has installed.</p><p><strong>Global management, per-project installation</strong>: All skills maintained in one repo. Projects install only what they need.</p><p><strong>Template protection</strong>: Skills starting with <code>_</code> (like <code>_template</code>) are hidden from listings and can’t be deleted. <code>pks new</code> auto-generates a skeleton from the template — no starting from scratch.</p><p><strong>Path-independent</strong>: pks resolves symlinks and uses relative paths. No matter where you invoke it, it always finds the skills directory. It always knows where home is.</p><h2 id="Back-to-That-Line"><a href="#Back-to-That-Line" class="headerlink" title="Back to That Line"></a>Back to That Line</h2><p>Last post said: “Agents are just tools. Documentation is the core.” This post takes it a step further: <strong>documented workflows can be managed.</strong></p><p>pks isn’t anything fancy — just 408 lines of bash. But it represents an attitude: your workflow is your asset, not any platform’s appendage.</p><p>Farm those tokens. Manage those skills. Saving money and doing good work — those two things aren’t mutually exclusive.</p><p>Source: <a href="https://lichuanyang.top/en/posts/26061/">https://lichuanyang.top/en/posts/26061/</a></p><h2 id="Frequently-Asked-Questions"><a href="#Frequently-Asked-Questions" class="headerlink" title="Frequently Asked Questions"></a>Frequently Asked Questions</h2><h3 id="Q-What’s-the-difference-between-Skills-management-and-Agent-management"><a href="#Q-What’s-the-difference-between-Skills-management-and-Agent-management" class="headerlink" title="Q: What’s the difference between Skills management and Agent management?"></a>Q: What’s the difference between Skills management and Agent management?</h3><p>Skills management is <strong>independent of any Agent platform</strong> — you store all skills in a single global repository and install only what each project needs. Agent management means configuring skills inside a specific Agent’s internal settings (e.g., OpenCode’s <code>.opencode/skills/</code> or Cursor’s <code>.cursorrules</code>), tying them to that platform. The benefit of the former is zero migration cost when switching platforms; the latter offers tight integration but requires reconfiguration every time you switch Agents.</p><h3 id="Q-How-is-cross-platform-compatibility-maintained"><a href="#Q-How-is-cross-platform-compatibility-maintained" class="headerlink" title="Q: How is cross-platform compatibility maintained?"></a>Q: How is cross-platform compatibility maintained?</h3><p>The key is <strong>plain Markdown</strong>. When pks installs a skill, it generates <code>.md</code> files placed in the project’s <code>.skills/</code> directory, along with an auto-maintained <code>INDEX.md</code> index file. Any Agent scanning the project directory will discover this structure, read INDEX.md to learn what skills are available, then load specific SKILL.md files as needed. No special formats, no platform SDKs required — plain Markdown is the greatest common divisor.</p><h3 id="Q-How-does-pks-differ-from-configuring-skills-directly-in-an-Agent"><a href="#Q-How-does-pks-differ-from-configuring-skills-directly-in-an-Agent" class="headerlink" title="Q: How does pks differ from configuring skills directly in an Agent?"></a>Q: How does pks differ from configuring skills directly in an Agent?</h3><p>Configuring directly in an Agent is convenient, but skills become “bound” to that Agent. pks’s approach is to <strong>decouple skill management from the Agent</strong> — skills live in a global repo and are installed into any project on demand. The biggest practical benefit: on-demand installation means <strong>only installed skills consume tokens</strong>. A pure frontend project doesn’t need the database conventions skill. A legacy project doesn’t need the onboarding skill. Compared to stuffing all rules into one giant config file the Agent reads every conversation, pks saves a significant amount of wasted tokens.</p><h3 id="Q-Why-use-bash-instead-of-Python-or-Node-js"><a href="#Q-Why-use-bash-instead-of-Python-or-Node-js" class="headerlink" title="Q: Why use bash instead of Python or Node.js?"></a>Q: Why use bash instead of Python or Node.js?</h3><p>Zero dependencies. A skill management tool itself shouldn’t introduce any runtime overhead — your workflow is already complex enough. A 408-line bash script works out of the box on macOS and Linux, requires no interpreter installation, no virtual environment management, and won’t break because of a runtime version upgrade. A bash script from ten years ago still runs today, ensuring your workflow documentation outlives any framework.</p>]]>
    </content>
    <id>https://lichuanyang.top/en/posts/26061/</id>
    <link href="https://lichuanyang.top/en/posts/26061/"/>
    <published>2026-06-04T14:00:00.000Z</published>
    <summary>Last post argued &quot;documentation is the core.&quot; So how do you manage it? I wrote a 400-line bash tool that packages AI workflows into portable skill bundles, installable into any project on demand — no Agent platform lock-in.</summary>
    <title>How to Farm Free Tokens (Part 2): Building an Agent-Independent Skills Management Workflow</title>
    <updated>2026-06-27T03:24:01.608Z</updated>
  </entry>
  <entry>
    <author>
      <name>SandGrid</name>
    </author>
    <category term="AI Practice" scheme="https://lichuanyang.top/en/categories/AI-Practice/"/>
    <category term="ai-agent" scheme="https://lichuanyang.top/en/tags/ai-agent/"/>
    <category term="workflow" scheme="https://lichuanyang.top/en/tags/workflow/"/>
    <category term="llm" scheme="https://lichuanyang.top/en/tags/llm/"/>
    <category term="prompt" scheme="https://lichuanyang.top/en/tags/prompt/"/>
    <category term="productivity" scheme="https://lichuanyang.top/en/tags/productivity/"/>
    <content>
      <![CDATA[<p>What’s the biggest—or perhaps only—pain point of using AI right now? Probably the bill.</p><p>Claude, Codex, Cursor, Qoder, MiMo, MiniMax—their basic plans all start around $20&#x2F;month, and you still have to watch your usage. If you want unlimited access, it gets even more expensive.</p><p>Meanwhile, lots of platforms offer free trials or permanently free models. For instance, OpenCode’s DeepSeek Flash has been free for a while.</p><p>So how do we actually make the most of these free models?</p><p>I’ve been using AI Agents across a wide range of projects—code, personal blogs, knowledge bases, fiction writing, even little games I build with AI. Over time I noticed something: <strong>every Agent operation boils down to: read your docs → assemble a prompt → send it to a model → modify files based on the result.</strong></p><p>The intelligence lives in your docs, not the platform. Whether you assemble the prompt yourself or let a platform do it—there’s really not that much difference.</p><p>So I stopped worrying about which Agent platform to use. I maintain good docs, and wherever there are free tokens, that’s where I go.</p><span id="more"></span><h2 id="What-Agents-Actually-Do"><a href="#What-Agents-Actually-Do" class="headerlink" title="What Agents Actually Do"></a>What Agents Actually Do</h2><p>Regardless of which Agent you use, the workflow is basically the same:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">You state a need → Agent reads project docs → reads relevant files → assembles a prompt → asks the model → processes the response → modifies files → loops</span><br></pre></td></tr></table></figure><p>Say you ask it to fix a bug. It will:</p><ol><li>First read your <code>AGENTS.md</code> and <code>README.md</code> to understand what the project is and where things are</li><li>Then find the files related to the bug and read the source code</li><li>Assemble “project context + relevant code + bug description + fix requirements” into a single prompt</li><li>Send it to the LLM</li><li>Turn the response into actual code changes</li></ol><p>How much difference can there really be between one Agent and another, at this level?</p><p>Don’t get me wrong—I’m not dismissing genuinely good Agents. The best ones really do handle context management, flow control, and tool integration smoothly. That’s why they’re popular. But “human intelligence” can easily bridge those gaps.</p><p>Take context management: an Agent can figure out “this bug involves the auth module, I need to read these three files”—but only because it already read your <code>AGENTS.md</code>. Those file paths, module responsibilities, and conventions? You wrote them in there yourself.</p><p>When your docs are messy, a good Agent can find information more efficiently. But with well-organized docs, the differences between Agents become negligible.</p><p>Bottom line: good Agents are genuinely convenient in some scenarios, but they’re not worth the price. Saving a few minutes on doc maintenance and window-switching in exchange for tens or hundreds of dollars a month? Terrible ROI.</p><p>For me personally, maintaining a solid Agent guide is far more cost-effective. Besides, you can totally have AI write those guide documents for you.</p><h2 id="Documentation-Is-the-Key"><a href="#Documentation-Is-the-Key" class="headerlink" title="Documentation Is the Key"></a>Documentation Is the Key</h2><p>Once you accept the above, you have to face a counterintuitive fact: <strong>what’s truly valuable isn’t the Agent platform—it’s your own workflow.</strong></p><p>Anyone can call an LLM. Most Agent platforms are just orchestration layers with similar features. But your project docs and process management are unique—they record your project structure, design decisions, pitfalls you’ve hit, and your preferred coding style.</p><p>Same docs on a different platform? The results are basically the same. But garbage docs? No platform can save you.</p><p>Here’s how I maintain docs for code projects:</p><ul><li>Project overview doc: tech stack, directory structure, key module descriptions</li><li>Workflow docs: “how to add a feature”, “bug fix steps”, “release process”</li><li>Rule docs: naming conventions, comment requirements, lessons learned</li></ul><p>Same project directory, opened with different Agents—barely any difference in results.</p><p>Same goes for fiction projects. World-building, character profiles, chapter outlines, writing guidelines—keep these docs consistent, and you’ll get consistent style across different models. <strong>The docs set the shape; the content quality follows.</strong></p><p>Even at the model layer, the differences aren’t that big. DeepSeek V4 Flash handles the vast majority of tasks just fine.</p><p>If you really want to spend money to save time, invest in the documentation itself—not in choosing the “right” platform.</p><h2 id="Token-Farming-in-Practice"><a href="#Token-Farming-in-Practice" class="headerlink" title="Token Farming in Practice"></a>Token Farming in Practice</h2><p>Once your workflow lives in docs, the biggest benefit kicks in: you can switch platforms freely.</p><p>Whichever platform has free tokens, use it. When it runs out, move to the next.</p><p>Here are some I use:</p><ul><li><strong>OpenCode</strong>: Free DeepSeek Flash daily</li><li><strong>Qoder</strong>: Some trial credits for new users, plus free model quotas</li><li><strong>Codex</strong>: Trial bundle—honestly, I still haven’t gotten around to using it. I keep thinking I’ll save the free quota for a genuinely complex task, but those tasks never actually come up.</li><li><strong>Hermes</strong>: Occasionally releases free models. Sometimes you luck into a great one—like when they had free DeepSeek Flash a while back.</li><li><strong>Trae</strong>: Completely free, just need to queue. When a task really feels too much for the free models, I hop over to Trae temporarily. Works perfectly fine.</li></ul><p>Other avenues worth exploring:</p><ul><li><strong>LLM API new-user credits</strong>: OpenAI, Anthropic, DeepSeek, etc. New accounts usually come with a few dollars. Calling the API directly is cheaper than going through an Agent platform.</li><li><strong>Free hosting for open-source models</strong>: HuggingFace, Together AI offer free inference quotas. More than enough for small tasks.</li><li><strong>Student discounts</strong>: GitHub Student Pack and the like come with a bunch of platform credits.</li><li><strong>Community events</strong>: AI platforms often give away credits through events.</li></ul><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>The takeaway is simple: <strong>Agents are just tools. Documentation is the core.</strong> Maintain your docs well, and you can switch platforms at will, farming free tokens wherever they pop up. Saving tens of dollars a month—that’s a nice hotpot dinner right there.</p><p>As for those burning through hundreds of dollars in tokens every month—hey, your money is well spent. But my tokens cost nothing, and the results are about the same.</p><p>Source: <a href="https://lichuanyang.top/en/posts/26060/">https://lichuanyang.top/en/posts/26060/</a></p><h2 id="Frequently-Asked-Questions"><a href="#Frequently-Asked-Questions" class="headerlink" title="Frequently Asked Questions"></a>Frequently Asked Questions</h2><h3 id="Q-What’s-the-relationship-between-Agents-and-Skills"><a href="#Q-What’s-the-relationship-between-Agents-and-Skills" class="headerlink" title="Q: What’s the relationship between Agents and Skills?"></a>Q: What’s the relationship between Agents and Skills?</h3><p>An Agent is the “worker” executing tasks; a Skill is the “instruction manual” telling it how. The Agent’s underlying model provides general reasoning ability, but it doesn’t know your project structure, coding conventions, or API design constraints — that knowledge must be communicated through Skills (structured instruction documents). The relationship can be understood as: Agent &#x3D; General Reasoning Engine + On-Demand Skill Loading. If you maintain your Skills well, you can switch between different Agent platforms and still get consistent output quality.</p><h3 id="Q-What’s-the-most-effective-way-to-optimize-token-usage"><a href="#Q-What’s-the-most-effective-way-to-optimize-token-usage" class="headerlink" title="Q: What’s the most effective way to optimize token usage?"></a>Q: What’s the most effective way to optimize token usage?</h3><p>Not switching to cheaper models — it’s <strong>writing better docs</strong>. A well-crafted AGENTS.md or SKILL.md allows the Agent to precisely locate the files it needs, avoiding a full project scan for every task. In comparison, tweaking model choices or prompt phrasing on top of poor documentation saves far fewer tokens than good documentation would. Invest time in clear docs first, then farm free tokens — that’s the highest-ROI path.</p><h3 id="Q-Which-is-a-better-investment-—-maintaining-docs-or-subscribing-to-an-Agent"><a href="#Q-Which-is-a-better-investment-—-maintaining-docs-or-subscribing-to-an-Agent" class="headerlink" title="Q: Which is a better investment — maintaining docs or subscribing to an Agent?"></a>Q: Which is a better investment — maintaining docs or subscribing to an Agent?</h3><p>Maintaining docs is the better investment. An Agent subscription costs tens to hundreds of dollars a month, essentially paying for the platform’s convenience of “finding files, assembling prompts.” But if your project docs are already comprehensive — tech stack, directory structure, design decisions, coding conventions all spelled out — switching to a free Agent yields almost identical results. Docs are your asset, reusable across platforms. An Agent subscription is a consumable — switch platforms and you start over.</p><h3 id="Q-Are-free-models-really-sufficient"><a href="#Q-Are-free-models-really-sufficient" class="headerlink" title="Q: Are free models really sufficient?"></a>Q: Are free models really sufficient?</h3><p>For the vast majority of daily tasks, yes. The author’s experience shows that DeepSeek Flash-level free models can handle most code modifications, document generation, and knowledge organization tasks. When you encounter genuinely complex scenarios beyond their reach, you can temporarily switch to platforms with free trial credits (like Trae, Codex trial bundles). The real bottleneck is rarely model capability — it’s whether the context (docs) you feed it is good enough.</p>]]>
    </content>
    <id>https://lichuanyang.top/en/posts/26060/</id>
    <link href="https://lichuanyang.top/en/posts/26060/"/>
    <published>2026-06-03T14:00:00.000Z</published>
    <summary>After tinkering with AI Agents, I realized they're mostly just shuffling documents and prompts around. Instead of paying for subscriptions, maintain your own project docs and use whichever platform has free tokens.</summary>
    <title>How to Farm Free Tokens: Building Agent-Independent AI Workflows</title>
    <updated>2026-06-27T03:24:01.609Z</updated>
  </entry>
  <entry>
    <author>
      <name>SandGrid</name>
    </author>
    <category term="AI Practice" scheme="https://lichuanyang.top/en/categories/AI-Practice/"/>
    <category term="hexo" scheme="https://lichuanyang.top/en/tags/hexo/"/>
    <category term="blog" scheme="https://lichuanyang.top/en/tags/blog/"/>
    <category term="ai" scheme="https://lichuanyang.top/en/tags/ai/"/>
    <category term="ai Agent" scheme="https://lichuanyang.top/en/tags/ai-Agent/"/>
    <category term="automation" scheme="https://lichuanyang.top/en/tags/automation/"/>
    <category term="llm" scheme="https://lichuanyang.top/en/tags/llm/"/>
    <category term="developer-tools" scheme="https://lichuanyang.top/en/tags/developer-tools/"/>
    <category term="prompt-engineering" scheme="https://lichuanyang.top/en/tags/prompt-engineering/"/>
    <content>
      <![CDATA[<h2 id="The-Problem-A-Tedious-Task-No-One-Wants-to-Do"><a href="#The-Problem-A-Tedious-Task-No-One-Wants-to-Do" class="headerlink" title="The Problem: A Tedious Task No One Wants to Do"></a>The Problem: A Tedious Task No One Wants to Do</h2><p>Recently, I decided to migrate my Hexo blog from the Next theme to Butterfly. Sounds simple, right?</p><p>If you’ve ever done a Hexo theme migration, you know it’s anything but simple:</p><ol><li><strong>Config files are 1000+ lines long</strong>, requiring line-by-line comparison between old and new theme formats</li><li><strong>Feature mapping is complex</strong>: Next’s <code>leancloud_visitors</code> maps to Butterfly’s <code>busuanzi</code>, Next’s <code>reading_progress</code> maps to Butterfly’s <code>preloader</code></li><li><strong>Two sites need syncing</strong>: Both Chinese and English sites need updates, with slightly different configs</li><li><strong>Font Awesome version conflicts</strong>: Butterfly defaults to FA 7.1.0, which doesn’t exist on cdnjs</li><li><strong>YAML format sensitivity</strong>: Indentation and line endings can break everything</li></ol><p>Manually, this would take an entire day and be error-prone. So I tried something different: <strong>letting an AI Agent handle the entire task</strong>.</p><h2 id="The-Solution-AI-Agent-Driven-Migration"><a href="#The-Solution-AI-Agent-Driven-Migration" class="headerlink" title="The Solution: AI Agent-Driven Migration"></a>The Solution: AI Agent-Driven Migration</h2><p>I used <strong>Hermes Agent</strong>, an AI-powered development assistant, to automate the entire migration process.</p><h3 id="What-the-AI-Agent-Did"><a href="#What-the-AI-Agent-Did" class="headerlink" title="What the AI Agent Did"></a>What the AI Agent Did</h3><p>The AI Agent autonomously completed these steps:</p><ol><li><strong>Environment Analysis</strong>: Checked current Hexo version (7.0.0), Node.js version (v24.14.0), and config differences between two sites</li><li><strong>Version Research</strong>: Queried GitHub API for the latest Hexo version (8.1.2) and breaking changes</li><li><strong>Dependency Upgrade</strong>: Batch-updated hexo and all plugins to latest versions</li><li><strong>Theme Comparison</strong>: Analyzed feature coverage between Next and Butterfly, generating a comparison table</li><li><strong>Config Migration</strong>: Mapped 1000+ lines of Next config to Butterfly format</li><li><strong>Bug Detection</strong>:<ul><li>Discovered FA 7.1.0 returns 404 on cdnjs, downgraded to 6.7.2</li><li>Found Zhihu icon needs <code>fab</code> prefix (brand icon)</li><li>Detected English site menu path error</li><li>Recovered lost GA, Baidu Analytics, and site verification configs</li></ul></li><li><strong>Testing</strong>: Build tests, local preview server, HTML output verification</li><li><strong>Code Commits</strong>: Auto-generated commit messages and pushed to GitHub</li></ol><h3 id="Key-Technical-Insights"><a href="#Key-Technical-Insights" class="headerlink" title="Key Technical Insights"></a>Key Technical Insights</h3><h4 id="1-Font-Awesome-Version-Compatibility"><a href="#1-Font-Awesome-Version-Compatibility" class="headerlink" title="1. Font Awesome Version Compatibility"></a>1. Font Awesome Version Compatibility</h4><p>This was the most subtle bug. Butterfly’s <code>plugins.yml</code> configured FA 7.1.0:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">fontawesome:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">&#x27;@fortawesome/fontawesome-free&#x27;</span></span><br><span class="line">  <span class="attr">version:</span> <span class="number">7.1</span><span class="number">.0</span></span><br></pre></td></tr></table></figure><p>But this version doesn’t exist on cdnjs! All brand icons (GitHub, StackOverflow, Zhihu) failed to load.</p><p>The AI Agent diagnosed this by:</p><ul><li>Inspecting generated HTML, finding FA 7.1.0 reference</li><li>Querying cdnjs API, confirming 404 for version 7.1.0</li><li>Testing FA 6.7.2, verifying all required icons exist</li><li>Overriding CDN address in config</li></ul><h4 id="2-YAML-Config-Migration-Pitfalls"><a href="#2-YAML-Config-Migration-Pitfalls" class="headerlink" title="2. YAML Config Migration Pitfalls"></a>2. YAML Config Migration Pitfalls</h4><p>Hexo config files use YAML format, which is sensitive to indentation and line endings:</p><ul><li><strong>CRLF vs LF</strong>: Butterfly’s default config uses CRLF line endings, causing sed pattern matching to fail</li><li><strong>Duplicate keys</strong>: The patch tool created duplicate <code>scroll_percent</code> entries</li><li><strong>Lost values</strong>: Incorrect YAML indentation caused config values to not be written</li></ul><p>These issues were resolved by using Python to directly manipulate file content.</p><h4 id="3-Multi-Site-Synchronization"><a href="#3-Multi-Site-Synchronization" class="headerlink" title="3. Multi-Site Synchronization"></a>3. Multi-Site Synchronization</h4><p>The Chinese and English sites share most config, but have key differences:</p><ul><li>English site needs <code>language: en</code></li><li>English site menu needs to point to Chinese site</li><li>English site Valine placeholder needs English text</li></ul><p>The AI Agent automatically identified and handled these differences.</p><h2 id="Results-Comparison"><a href="#Results-Comparison" class="headerlink" title="Results Comparison"></a>Results Comparison</h2><table><thead><tr><th>Metric</th><th>Manual</th><th>AI Agent</th></tr></thead><tbody><tr><td>Time</td><td>4-8 hours</td><td>30 minutes</td></tr><tr><td>Error Rate</td><td>High (easy to miss configs)</td><td>Low (systematic checking)</td></tr><tr><td>Debugging</td><td>Manual Google searches</td><td>Auto root cause analysis</td></tr><tr><td>Commits</td><td>Manual commit messages</td><td>Auto-generated</td></tr></tbody></table><h2 id="Why-AI-Agents-Excel-at-This"><a href="#Why-AI-Agents-Excel-at-This" class="headerlink" title="Why AI Agents Excel at This"></a>Why AI Agents Excel at This</h2><p>This experience showed me why <strong>AI Agents are perfect for tedious, repetitive work</strong>:</p><h3 id="1-Systematic-Thinking"><a href="#1-Systematic-Thinking" class="headerlink" title="1. Systematic Thinking"></a>1. Systematic Thinking</h3><p>AI Agents don’t work like humans who “fix things as they see them.” They:</p><ul><li>Analyze current state first</li><li>Create a complete plan</li><li>Execute step by step with verification</li><li>Fix issues as they arise</li></ul><h3 id="2-Cross-Domain-Knowledge"><a href="#2-Cross-Domain-Knowledge" class="headerlink" title="2. Cross-Domain Knowledge"></a>2. Cross-Domain Knowledge</h3><p>Theme migration spans multiple technical domains:</p><ul><li>Hexo configuration</li><li>Font Awesome version management</li><li>YAML format handling</li><li>Git workflows</li><li>Frontend resource loading</li></ul><p>AI Agents can freely switch between these domains, while human developers typically only master one or two.</p><h3 id="3-Persistent-Attention"><a href="#3-Persistent-Attention" class="headerlink" title="3. Persistent Attention"></a>3. Persistent Attention</h3><p>Humans lose focus when processing 1000+ line config files, leading to oversights. AI Agents don’t get tired—every config item gets checked.</p><h3 id="4-Automated-Verification"><a href="#4-Automated-Verification" class="headerlink" title="4. Automated Verification"></a>4. Automated Verification</h3><p>AI Agents don’t just modify configs—they also:</p><ul><li>Run build tests</li><li>Start local servers for verification</li><li>Check HTML output</li><li>Confirm critical configs are active</li></ul><h2 id="Best-Use-Cases"><a href="#Best-Use-Cases" class="headerlink" title="Best Use Cases"></a>Best Use Cases</h2><p>Based on this experience, <strong>AI Agents are ideal for</strong>:</p><ol><li><strong>Config Migration</strong>: Converting configs between different systems</li><li><strong>Dependency Upgrades</strong>: Batch-updating multiple packages with compatibility handling</li><li><strong>Code Refactoring</strong>: Large-scale code format adjustments</li><li><strong>Documentation</strong>: Integrating information from multiple sources</li><li><strong>Environment Setup</strong>: New project initialization</li></ol><h2 id="Limitations"><a href="#Limitations" class="headerlink" title="Limitations"></a>Limitations</h2><p>AI Agents have limitations too:</p><ol><li><strong>Creative Work</strong>: UI design, copywriting still need human direction</li><li><strong>Business Decisions</strong>: Whether to upgrade, which theme to choose—these need human judgment</li><li><strong>Complex Debugging</strong>: Some runtime issues require human intervention</li></ol><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>This practice of using an AI Agent to complete Hexo theme migration made me see the huge potential of <strong>LLMs (Large Language Models) in software engineering</strong>.</p><p>AI Agents don’t replace developers—they enhance our capabilities. They free us from tedious, repetitive work so we can focus on more valuable things.</p><p>If you have similar “grunt work,” try letting an AI Agent help. You’ll find that <strong>AI-assisted development</strong> is not the future—it’s the present.</p><h2 id="Tech-Stack"><a href="#Tech-Stack" class="headerlink" title="Tech Stack"></a>Tech Stack</h2><ul><li><strong>AI Agent</strong>: Hermes Agent</li><li><strong>LLM</strong>: DeepSeek V4 Pro &#x2F; MiMo v2.5</li><li><strong>Static Site Generator</strong>: Hexo 8.1.2</li><li><strong>Theme</strong>: Butterfly 5.5.4</li><li><strong>Icon Library</strong>: Font Awesome 6.7.2</li><li><strong>Comment System</strong>: Valine (LeanCloud)</li><li><strong>Analytics</strong>: Google Analytics &#x2F; Baidu Analytics</li></ul><hr><p><em>This article was written with AI Agent assistance, documenting a real theme migration practice.</em></p><p>Source: <a href="https://lichuanyang.top/en/posts/48979/">https://lichuanyang.top/en/posts/48979/</a></p>]]>
    </content>
    <id>https://lichuanyang.top/en/posts/48979/</id>
    <link href="https://lichuanyang.top/en/posts/48979/"/>
    <published>2026-05-28T14:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="The-Problem-A-Tedious-Task-No-One-Wants-to-Do"><a href="#The-Problem-A-Tedious-Task-No-One-Wants-to-Do" class="headerlink"]]>
    </summary>
    <title>Automating Hexo Theme Migration with AI Agent: From Next to Butterfly</title>
    <updated>2026-06-27T03:23:24.830Z</updated>
  </entry>
  <entry>
    <author>
      <name>SandGrid</name>
    </author>
    <category term="Tech Talk" scheme="https://lichuanyang.top/en/categories/Tech-Talk/"/>
    <category term="blog" scheme="https://lichuanyang.top/en/tags/blog/"/>
    <category term="vercel" scheme="https://lichuanyang.top/en/tags/vercel/"/>
    <category term="devops" scheme="https://lichuanyang.top/en/tags/devops/"/>
    <category term="independent-blog" scheme="https://lichuanyang.top/en/tags/independent-blog/"/>
    <content>
      <![CDATA[<p>A few days ago, I wrote a new article and deployed it to Vercel as usual, but the pipeline threw an error. At first I thought it was a build issue, and after messing around for a while I realized something was wrong — it was the Vercel account itself. The account registered with a 163 email couldn’t log in at all. Later I found out that Vercel had banned the entire 163.com email root domain.</p><span id="more"></span><h2 id="What-is-Vercel"><a href="#What-is-Vercel" class="headerlink" title="What is Vercel?"></a>What is Vercel?</h2><p>If you’re into independent blogging, you’ve probably heard of or are currently using Vercel. Simply put, it’s a frontend deployment platform that supports automatically pulling code from GitHub repositories, building, and deploying. It assigns a domain name to your website and includes CDN acceleration. Many people’s blogs use the GitHub Pages + Vercel combination — code on GitHub, using Vercel for deployment and hosting.</p><p>My own blog was set up this way. GitHub Pages can deploy static pages directly, but Vercel is more convenient for custom domains, HTTPS, and CDN, so I chose it.</p><h2 id="The-Full-Story-of-the-163-Email-Ban"><a href="#The-Full-Story-of-the-163-Email-Ban" class="headerlink" title="The Full Story of the 163 Email Ban"></a>The Full Story of the 163 Email Ban</h2><p>Around May 4, 2026, a large number of users who registered Vercel with 163 email suddenly found they couldn’t log into their accounts. I searched online and found it wasn’t just me — forums like NodeSeek and V2EX had people discussing the same issue.</p><p>According to online information, Vercel suffered a relatively serious security incident in mid-April 2026. The hacking group ShinyHunters breached Vercel’s internal systems through a third-party AI tool, stealing employee data, enterprise backend access, and some deployment credentials, and even demanded a $2 million ransom.</p><p>The 163 email ban was likely one of the security measures taken in response. It may be because 163 email was extensively used in this incident for malicious registrations or attacks, or it may just be Vercel applying stricter risk controls to certain email domains. Vercel hasn’t publicly stated the specific reason, but the result was that the entire 163.com email domain was blanket banned.</p><p>The impact was significant. Many domestic developers and independent blog authors use 163 email to register Vercel, and this ban directly made all their blogs inaccessible.</p><p>However, if your Vercel account is still logged in (for example, your work computer’s browser maintains the login session), you can actually salvage it: go to account settings, add a new email (Outlook or Gmail both work), and then remove the 163 email. This way the account can continue to be used normally without redeployment. Someone online shared this method, which can save a lot of trouble. But if you’re like me and by the time you discovered it, you were completely locked out, then you’ll have to follow the rebuild process below.</p><h2 id="Recalling-My-Blog-Deployment-Architecture"><a href="#Recalling-My-Blog-Deployment-Architecture" class="headerlink" title="Recalling My Blog Deployment Architecture"></a>Recalling My Blog Deployment Architecture</h2><p>The account was banned, the blog was inaccessible. The first thing I needed to do was figure out exactly how my blog was deployed. Honestly, the blog configuration was set up a long time ago, and I hadn’t touched it much since. I couldn’t remember the exact process very well.</p><p>It took considerable effort to recall. The general pipeline was:</p><ol><li>Blog source code is in a GitHub repository</li><li>Vercel connects to the GitHub repository, responsible for building and deploying</li><li>Domain name resolution was split into two layers: Alibaba Cloud DNS points to Cloudflare, Cloudflare then points to Vercel</li></ol><p>Why make it so complicated? Mainly to use Cloudflare’s CDN and security capabilities. Alibaba Cloud as the domain registrar, Cloudflare as the intermediate layer for DNS management and CDN, Vercel as the final static page host. When I configured it, I was following some online tutorials. Although the chain was a bit long, it ran stably for a long time, so I never changed it.</p><h2 id="Recovery-Process-New-Account-Redeployment"><a href="#Recovery-Process-New-Account-Redeployment" class="headerlink" title="Recovery Process: New Account + Redeployment"></a>Recovery Process: New Account + Redeployment</h2><p>Once I figured out the deployment architecture, the recovery process was actually not complicated.</p><p><strong>Step 1: Register a new Vercel account</strong></p><p>I learned my lesson this time and didn’t use 163 email. I registered a new Vercel account with Outlook. Gmail would also work, but considering this was Vercel banning specific email domains, using a mainstream international email service would be more reliable.</p><p><strong>Step 2: Redeploy the GitHub project</strong></p><p>After logging into the new account, I connected GitHub and imported the blog repository into Vercel. Vercel automatically detected the project type, and the build and deployment were completed automatically, basically the same as before.</p><p><strong>Step 3: Reconfigure the domain</strong></p><p>This was the key step. Since the previous domain resolution followed the Alibaba Cloud → Cloudflare → Vercel chain, I needed to bind the domain to the new Vercel project after redeployment.</p><p>What pleasantly surprised me was that Vercel now supports automatically modifying Cloudflare DNS configuration when adding a custom domain. I remember previously having to manually go to Cloudflare’s backend to add CNAME records. Now Vercel handles it directly through API — just a few clicks and it’s done.</p><p>The entire recovery process, from registering a new account to having the blog accessible again, took less than half an hour.</p><h2 id="Lessons-Learned"><a href="#Lessons-Learned" class="headerlink" title="Lessons Learned"></a>Lessons Learned</h2><ol><li><p><strong>Don’t use domestic email to register overseas services.</strong> Domestic email services like 163 and QQ mail are inherently high-risk targets in overseas services’ risk control systems. This time it was Vercel; next time it could be another service. It’s recommended to use Gmail or Outlook for primary accounts.</p></li><li><p><strong>Keep good records of your deployment architecture.</strong> The most time-consuming part of this incident was recalling the deployment pipeline. I thought I remembered it all after configuring it, but after so long I had mostly forgotten. I recommend writing down your deployment architecture and key configurations in notes, even if it’s just a few sentences.</p></li><li><p><strong>Vercel’s Cloudflare integration is now quite good.</strong> Previously when configuring DNS manually, it was error-prone. Now with much higher automation, the cost of redeployment has been significantly reduced.</p></li><li><p><strong>If possible, prepare backups and multi-platform options.</strong> I was lucky this time and recovery was relatively quick. But if the GitHub repository also had issues, or if Vercel completely banned the project rather than just the account, recovery would be much more troublesome. For important blog content, it’s recommended to have a copy locally or on another platform.</p></li></ol><h2 id="Final-Thoughts"><a href="#Final-Thoughts" class="headerlink" title="Final Thoughts"></a>Final Thoughts</h2><p>This incident of Vercel banning 163 email serves as a wake-up call for everyone who uses domestic email to register overseas services. We can’t control the platform’s security measures, but we can prepare in advance for our own account security and deployment architecture.</p><p>If you’ve also encountered the issue of Vercel 163 email being banned or Vercel account login failure, I hope this article helps. The core recovery approach is: register a new account with a different email, redeploy the project, and rebind the domain. The operations aren’t complicated, but the prerequisite is that you need to remember your deployment pipeline.</p><p>Original article: <a href="https://lichuanyang.top/posts/39648/">https://lichuanyang.top/posts/39648/</a></p><hr><p>Source: <a href="https://lichuanyang.top/en/posts/39648/">https://lichuanyang.top/en/posts/39648/</a></p><hr><h2 id="Quick-Start-Guide"><a href="#Quick-Start-Guide" class="headerlink" title="Quick Start Guide"></a>Quick Start Guide</h2><h3 id="Step-1-Identify-the-Problem"><a href="#Step-1-Identify-the-Problem" class="headerlink" title="Step 1: Identify the Problem"></a>Step 1: Identify the Problem</h3><p>When your blog is inaccessible or Vercel deployment throws errors, first confirm whether your account has been banned. Try logging into Vercel — if your 163 email-registered account cannot log in, it’s likely been banned.</p><h3 id="Step-2-Investigate-the-Cause"><a href="#Step-2-Investigate-the-Cause" class="headerlink" title="Step 2: Investigate the Cause"></a>Step 2: Investigate the Cause</h3><p>Search related forums (NodeSeek, V2EX, etc.) to confirm whether Vercel has banned 163 email on a large scale. Understand the background and impact scope of the ban to be well-informed.</p><h3 id="Step-3-Migrate-Email-Verification"><a href="#Step-3-Migrate-Email-Verification" class="headerlink" title="Step 3: Migrate Email Verification"></a>Step 3: Migrate Email Verification</h3><p>If you can still log in, immediately add a Gmail or Outlook email in your account settings and remove the 163 email. If you can no longer log in, re-register a Vercel account with a new email. It’s recommended to use mainstream international email services like Gmail or Outlook.</p><h3 id="Step-4-Redeploy"><a href="#Step-4-Redeploy" class="headerlink" title="Step 4: Redeploy"></a>Step 4: Redeploy</h3><p>After logging in with a new account, connect to GitHub and import your blog repository. Vercel will automatically detect the project type and complete building and deploying — the process is essentially the same as the initial deployment.</p><h3 id="Step-5-Verify-Recovery"><a href="#Step-5-Verify-Recovery" class="headerlink" title="Step 5: Verify Recovery"></a>Step 5: Verify Recovery</h3><p>Re-bind your custom domain. Vercel now supports automatically modifying Cloudflare DNS configuration, making the process very simple. Confirm that all pages of your blog are accessible and that HTTPS, CDN, and other functions are working properly.</p>]]>
    </content>
    <id>https://lichuanyang.top/en/posts/39648/</id>
    <link href="https://lichuanyang.top/en/posts/39648/"/>
    <published>2026-05-11T11:30:00.000Z</published>
    <summary>A firsthand account of Vercel banning 163 email causing blog deployment failure, and a complete solution from discovering the issue to migrating and recovering.</summary>
    <title>How I Recovered My Blog After Vercel Banned 163 Email</title>
    <updated>2026-06-27T03:50:00.010Z</updated>
  </entry>
  <entry>
    <author>
      <name>SandGrid</name>
    </author>
    <category term="AI Practice" scheme="https://lichuanyang.top/en/categories/AI-Practice/"/>
    <category term="knowledge-management" scheme="https://lichuanyang.top/en/tags/knowledge-management/"/>
    <category term="llm" scheme="https://lichuanyang.top/en/tags/llm/"/>
    <category term="security-standards" scheme="https://lichuanyang.top/en/tags/security-standards/"/>
    <category term="wiki" scheme="https://lichuanyang.top/en/tags/wiki/"/>
    <category term="practice" scheme="https://lichuanyang.top/en/tags/practice/"/>
    <content>
      <![CDATA[<p>When I was organizing our team’s security development standards recently, I encountered an old problem: security documentation keeps piling up, but when you actually need it, you can’t find it. Every time a new team member joins, they have to dig through various documents to piece together the complete security standards. Every time we have a security incident, the lessons learned are scattered everywhere, and we end up rediscovering the same issues next time.</p><p>I’ve tried managing this with Confluence, Notion, even Git repository READMEs, but none worked well. It wasn’t until I saw Karpathy’s llm-wiki concept that I thought this might be a breakthrough approach.</p><span id="more"></span><h2 id="What-is-Karpathy’s-llm-wiki"><a href="#What-is-Karpathy’s-llm-wiki" class="headerlink" title="What is Karpathy’s llm-wiki?"></a>What is Karpathy’s llm-wiki?</h2><p>Karpathy proposed an idea in his <a href="https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f">Gist</a>: let LLMs maintain a Wiki middleware layer. His exact words were “the wiki is a persistent, compounding artifact” - this Wiki layer acts like a compiler, compiling raw documents into structured data. This way, when the LLM answers questions, it doesn’t need to search through raw documents from scratch; it can directly query the Wiki.</p><p>I think this approach is brilliant. Traditional document management is about “storing” and “finding”, while llm-wiki is about “compiling” and “querying”. Raw documents might be messy, duplicated, or even contradictory, but the Wiki layer is clean, structured, and cross-referenced.</p><p>For example: our team has over a dozen security development standard documents, covering everything from input validation to penetration testing, plus nearly ten security incident post-mortem reports. These documents come in different formats - some in Word, some in Markdown, some are even just Slack chat exports. If we let the LLM process these raw materials directly, efficiency would be very low. But if we first “compile” them into a Wiki, the results are completely different.</p><h2 id="My-Implementation-Process"><a href="#My-Implementation-Process" class="headerlink" title="My Implementation Process"></a>My Implementation Process</h2><h3 id="Step-1-Generate-CLAUDE-md"><a href="#Step-1-Generate-CLAUDE-md" class="headerlink" title="Step 1: Generate CLAUDE.md"></a>Step 1: Generate CLAUDE.md</h3><p>I first gave Claude Karpathy’s Gist link and asked it to generate a CLAUDE.md file based on this concept. This file serves as an “operation manual” for the LLM, telling it how to maintain the Wiki.</p><p>The generated CLAUDE.md contained several key sections:</p><ul><li>Three-layer architecture explanation (raw → wiki → output)</li><li>Wiki page specification (frontmatter format, cross-reference rules)</li><li>Ingestion workflow (how to process new materials)</li><li>Query workflow (how to answer user questions)</li><li>Lint checks (how to maintain Wiki health)</li></ul><p>In practice, I found the model’s output was basically usable without much fine-tuning. I also had the AI generate the output layer for final specification documents. This completed the three-layer architecture: raw for original materials, wiki for structured Wiki, output for final output.</p><h3 id="Step-2-Collect-and-Organize-Raw-Materials"><a href="#Step-2-Collect-and-Organize-Raw-Materials" class="headerlink" title="Step 2: Collect and Organize Raw Materials"></a>Step 2: Collect and Organize Raw Materials</h3><p>This was the most time-consuming step. I collected all the scattered security development standards and incident cases, placing them uniformly in the raw&#x2F;sources&#x2F; directory. To preserve the original state, I didn’t modify the content or unify the formats - PDF, Word, PPT, Markdown, and other formats can all go directly into the raw layer; the model can handle them all.</p><p>Specifically, I collected:</p><ul><li>Over a dozen security development standard documents: covering input validation, SQL injection prevention, XSS prevention, CSRF prevention, sensitive data protection, log security, etc.</li><li>Nearly ten security incident post-mortem reports: including SQL injection vulnerabilities, unauthorized access, sensitive information leaks, session hijacking, and other real cases</li><li>Several security architecture documents: covering zero-trust architecture, secure coding guidelines, penetration testing processes, etc.</li></ul><p>The quality of these materials varied greatly. Some were well-formatted with clear headings and lists; others were rough notes, even screenshots. But that’s fine - the beauty of llm-wiki is that it can handle this kind of “dirty data”.</p><h3 id="Step-3-Let-the-LLM-Generate-the-Wiki-Layer"><a href="#Step-3-Let-the-LLM-Generate-the-Wiki-Layer" class="headerlink" title="Step 3: Let the LLM Generate the Wiki Layer"></a>Step 3: Let the LLM Generate the Wiki Layer</h3><p>With CLAUDE.md and raw materials ready, I could let the LLM start working. I used Claude here, mainly because its context window is large enough to process more content at once.</p><p>The process works like this:</p><ol><li>Use CLAUDE.md as the system prompt</li><li>Tell the LLM: “Please process all materials in the raw&#x2F;sources&#x2F; directory according to the CLAUDE.md specification and generate the wiki layer”</li><li>The LLM reads each raw file sequentially, extracts key information, and generates corresponding Wiki pages</li></ol><p>This process isn’t complete in one go. The LLM’s initial Wiki has issues:</p><ul><li>Cross-references are insufficient</li><li>Some concepts aren’t extracted into standalone pages</li><li>Logical relationships between pages aren’t clear enough</li></ul><p>So multiple iterations are needed. I review the generated Wiki, point out issues, and let the LLM fix them. For example:<br>“This incident case mentions ‘improper connection pool configuration’, but why isn’t there a corresponding concept page? Please create a [[connection-pool-best-practices]] page and reference it in the incident case.”</p><p>After 5-6 iterations, the Wiki layer gradually took shape.</p><h3 id="Step-4-Generate-Final-Specification-Documents"><a href="#Step-4-Generate-Final-Specification-Documents" class="headerlink" title="Step 4: Generate Final Specification Documents"></a>Step 4: Generate Final Specification Documents</h3><p>With the Wiki layer ready, I could generate the final specification documents. I asked the LLM to generate a developer-facing, directly readable specification document based on the Wiki layer.</p><p>Here’s a design decision: the final document goes in the output&#x2F; directory, not modifying the original materials. This maintains the clarity of the three-layer architecture:</p><ul><li>raw&#x2F;: original materials, unmodifiable</li><li>wiki&#x2F;: structured Wiki, maintained by LLM</li><li>output&#x2F;: final output, for human reading</li></ul><p>The generated specification documents have several characteristics:</p><ol><li>Each standard entry includes “why” - not just “do this”, but also “why do this”</li><li>Related incident cases are linked as “negative examples” alongside</li><li>Cross-references exist, for example “database connection pool configuration” links to the “connection pool best practices” concept page</li></ol><h2 id="Actual-Results"><a href="#Actual-Results" class="headerlink" title="Actual Results"></a>Actual Results</h2><h3 id="Reading-Experience"><a href="#Reading-Experience" class="headerlink" title="Reading Experience"></a>Reading Experience</h3><p>The final specification documents can be opened directly in Obsidian. Being in Markdown format, they support:</p><ul><li>Code block syntax highlighting</li><li>Internal link navigation (click to jump to related concepts or incident cases)</li><li>Tag filtering (e.g., filter all #security-related standards)</li></ul><p>More importantly, each security standard is linked to real incident cases. For example, next to the “SQL Injection Prevention” standard, there’s a link to the [[sql-injection-vulnerability-incident]] case, which details how the vulnerability occurred, what impact it had, and how it was fixed. This way developers not only know “what to do” but also see “what happens if you don’t do it”.</p><p>Compared to the previous scattered documents, the reading experience improved by orders of magnitude.</p><h3 id="LLM-Q-A"><a href="#LLM-Q-A" class="headerlink" title="LLM Q&amp;A"></a>LLM Q&amp;A</h3><p>Even more powerful is the LLM’s Q&amp;A capability. Because of the Wiki layer, the LLM’s answers are very accurate. For example, when I ask:<br>“What should I watch out for when doing batch database updates?”</p><p>The LLM will:</p><ol><li>First find the [[batch-operation-security]] concept page in the Wiki</li><li>Combine it with the [[sql-injection-batch-incident]] incident case</li><li>Give specific recommendations: must use parameterized queries, no string concatenation for SQL, batch operations need transaction control, large batches should be paginated, etc.</li><li>Also explain why: because if something goes wrong with batch operations, the impact is N times that of single operations</li></ol><p>Another example: “How should I handle user file uploads securely?”</p><p>The LLM will:</p><ol><li>Find the [[file-upload-security]] concept page</li><li>Combine it with the [[malicious-file-upload-incident]] incident case</li><li>Give specific recommendations: file type whitelist validation, file content detection, rename storage, limit file size, isolated storage, etc.</li></ol><p>This is much more reliable than letting the LLM “search” for answers in a dozen raw documents.</p><h3 id="Continuous-Updates"><a href="#Continuous-Updates" class="headerlink" title="Continuous Updates"></a>Continuous Updates</h3><p>What satisfied me most is the update mechanism. When new standard documents or incident reports arrive, I just need to:</p><ol><li>Place the new file in the raw&#x2F;sources&#x2F; directory</li><li>Tell the LLM: “There are new materials to ingest”</li><li>The LLM automatically updates the Wiki layer and specification documents</li></ol><p>For example, last week we had a new security incident: an XSS vulnerability due to unfiltered user input. After I placed the incident report in the raw directory, the LLM automatically:</p><ol><li>Created the [[xss-user-input-incident]] incident case page</li><li>Updated the [[input-validation-security]] concept page with XSS-related notes</li><li>Added relevant entries to the “Input Validation Standards” section of the specification document</li></ol><h2 id="Problems-Encountered-and-Solutions"><a href="#Problems-Encountered-and-Solutions" class="headerlink" title="Problems Encountered and Solutions"></a>Problems Encountered and Solutions</h2><h3 id="Problem-1-Unstable-Wiki-Quality-from-LLM"><a href="#Problem-1-Unstable-Wiki-Quality-from-LLM" class="headerlink" title="Problem 1: Unstable Wiki Quality from LLM"></a>Problem 1: Unstable Wiki Quality from LLM</h3><p>At first, the LLM-generated Wiki quality varied greatly. Sometimes it extracted key information well, sometimes it missed important content.</p><p><strong>Solution</strong>: I refined the Wiki page specification in CLAUDE.md. For example, I required each page to have:</p><ul><li>Summary line: one sentence describing the core content</li><li>Tags line: mark topics with #tags</li><li>Clear page type (concept&#x2F;entity&#x2F;source&#x2F;comparison)</li></ul><p>For security incident cases, I also required: vulnerability type, attack vector, impact scope, fix solution, prevention measures. This gave the LLM clearer guidance, and generation quality became much more stable.</p><h3 id="Problem-2-Cross-References-Frequently-Break"><a href="#Problem-2-Cross-References-Frequently-Break" class="headerlink" title="Problem 2: Cross-References Frequently Break"></a>Problem 2: Cross-References Frequently Break</h3><p>The core value of Wiki lies in cross-references, but the LLM often creates one-way links or links to non-existent pages.</p><p><strong>Solution</strong>: I strengthened cross-reference rules in CLAUDE.md:</p><ol><li>Must be bidirectional: if page A references [[B]], then page B must also reference [[A]]</li><li>The related field in frontmatter must match the [[wiki-links]] in the body</li><li>Can only reference existing pages; must create before referencing</li></ol><p>I also added Lint checks to regularly check for broken links and orphan pages.</p><h3 id="Problem-3-Low-Efficiency-Processing-Unstructured-Materials"><a href="#Problem-3-Low-Efficiency-Processing-Unstructured-Materials" class="headerlink" title="Problem 3: Low Efficiency Processing Unstructured Materials"></a>Problem 3: Low Efficiency Processing Unstructured Materials</h3><p>Some raw materials are poor quality, like screenshots, handwritten notes, or even Slack chat exports. The LLM processes these materials inefficiently.</p><p><strong>Solution</strong>: For these materials, I first manually organize them into structured Markdown, then hand them to the LLM. Although this increases upfront workload, it ensures final quality.</p><h2 id="Lessons-Learned"><a href="#Lessons-Learned" class="headerlink" title="Lessons Learned"></a>Lessons Learned</h2><ol><li><p><strong>Don’t aim for perfection in one shot</strong>. llm-wiki is an iterative process. The first version of the Wiki won’t be perfect, but it can be improved through continuous iteration.</p></li><li><p><strong>Keep optimizing CLAUDE.md</strong>. As you use it, you’ll discover missing rules in CLAUDE.md. For example, I later added structural requirements like “security incident cases must include: vulnerability type, attack vector, impact scope, fix solution, prevention measures.”</p></li><li><p><strong>Keep the three-layer architecture pure</strong>. raw&#x2F; is raw&#x2F;, don’t modify it; wiki&#x2F; is maintained by LLM; output&#x2F; is for humans. Mixing them together will only get messier.</p></li><li><p><strong>Do regular health checks</strong>. I run Lint weekly to check for broken links, orphan pages, one-way links, etc. Fix problems as soon as they’re found.</p></li><li><p><strong>Accept imperfection</strong>. The LLM-generated Wiki won’t be 100% accurate; there will be omissions and errors. But compared to completely manual maintenance, it’s already much better.</p></li></ol><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>Using llm-wiki to manage security development standards essentially transforms “document management” into “knowledge engineering”. Raw documents are “raw materials”, Wiki is “semi-finished products”, and specification documents are “finished products”. The LLM serves as both “compiler” and “maintainer” in this process.</p><p>The greatest value of this approach is <strong>sustainability</strong>. Traditional document management eventually gets abandoned because maintenance costs are too high. llm-wiki shifts maintenance costs to the LLM, and humans only need to:</p><ol><li>Provide raw materials (this can’t be avoided)</li><li>Review and correct the LLM’s work (much easier than writing from scratch)</li><li>Use the final output (enjoy the results)</li></ol><p>If you’re also struggling with managing security development standards, give this approach a try. You don’t need to copy my implementation exactly; the key is understanding the core concept of “Wiki middleware layer”. As for which tools to use or how to organize directories, you can adjust according to your actual situation.</p><p>After all, tools exist to serve people, don’t they?</p><p>Source: <a href="https://lichuanyang.top/en/posts/16495/">https://lichuanyang.top/en/posts/16495/</a></p>]]>
    </content>
    <id>https://lichuanyang.top/en/posts/64/</id>
    <link href="https://lichuanyang.top/en/posts/64/"/>
    <published>2026-05-11T07:38:16.000Z</published>
    <summary>How to use LLM to manage team security development standards, based on Karpathy's llm-wiki approach to solve scattered documentation problems.</summary>
    <title>Using LLM to Manage Security Development Standards - An llm-wiki Practice</title>
    <updated>2026-06-27T03:48:13.501Z</updated>
  </entry>
  <entry>
    <author>
      <name>SandGrid</name>
    </author>
    <category term="Java" scheme="https://lichuanyang.top/en/categories/Java/"/>
    <category term="java" scheme="https://lichuanyang.top/en/tags/java/"/>
    <category term="vaadin" scheme="https://lichuanyang.top/en/tags/vaadin/"/>
    <category term="springboot" scheme="https://lichuanyang.top/en/tags/springboot/"/>
    <category term="frontend-development" scheme="https://lichuanyang.top/en/tags/frontend-development/"/>
    <content>
      <![CDATA[<h2 id="The-Frontend-Dilemma-for-Backend-Engineers"><a href="#The-Frontend-Dilemma-for-Backend-Engineers" class="headerlink" title="The Frontend Dilemma for Backend Engineers"></a>The Frontend Dilemma for Backend Engineers</h2><p>The pain point of backend engineers developing frontend code is usually that it’s too tedious — you often have to spend a long time looking things up for even the smallest task. Vaadin solves this pain point well by providing backend engineers with an easy-to-learn, convenient solution for writing frontend code. Today, let’s take a look.</p><span id="more"></span><h2 id="What-is-Vaadin"><a href="#What-is-Vaadin" class="headerlink" title="What is Vaadin"></a>What is Vaadin</h2><p>Hello everyone, today I’d like to introduce a tool that’s especially valuable for backend engineers — Vaadin.</p><p>To be honest, getting started with basic HTML and CSS development isn’t really difficult. But if you only know these basics, development can be very tedious. If you want to use the various tools in the frontend ecosystem, convenience improves but so does the learning curve. So unless you’re a professional full-stack engineer, just a backend developer wanting to write some frontend code temporarily — for example, when building a small project on your own — it’s usually a painful process.</p><p>Vaadin solves this pain point well. Through Vaadin’s pre-packaged common frontend components, we can write functional, reasonably presentable pages with nearly zero learning cost. For programmers with a backend background, this will significantly reduce the cost of building small projects on your own.</p><h2 id="Core-Concepts"><a href="#Core-Concepts" class="headerlink" title="Core Concepts"></a>Core Concepts</h2><p>What Vaadin offers is the ability to write pages directly in Java code. Vaadin provides various pre-built frontend styles including input fields, forms, and more, and it deeply integrates with Spring Boot, making it very convenient to use.</p><p>The underlying principle of Vaadin isn’t complex — it’s mainly based on server-side rendering, meaning the final HTML code is generated on the backend and sent to the browser. Server-side rendering isn’t uncommon, and we won’t discuss its advantages and disadvantages compared to client-side rendering here. Of course, for Vaadin, using server-side rendering makes perfect sense — since you’re writing backend code, doing rendering on the backend is a completely natural implementation path. Vaadin’s engine encapsulates the interaction between frontend and backend, so for users, the interaction between frontend and backend is transparent. At the page level, we can also call backend services normally.</p><h2 id="Quick-Start"><a href="#Quick-Start" class="headerlink" title="Quick Start"></a>Quick Start</h2><p>Here’s a code example I wrote to give you a more intuitive sense of Vaadin’s capabilities:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line">@Route(value = &quot;path&quot;, layout = MainView.class)</span><br><span class="line">@PageTitle(&quot;路径规划&quot;)</span><br><span class="line">public class PathView extends VerticalLayout &#123;</span><br><span class="line"></span><br><span class="line">    @Autowired</span><br><span class="line">    private PathService pathService;</span><br><span class="line"></span><br><span class="line">    public PathView() &#123;</span><br><span class="line">        TextField start = new TextField();</span><br><span class="line">        TextField end = new TextField();</span><br><span class="line">        HorizontalLayout path = new HorizontalLayout();</span><br><span class="line">        path.add(start, end);</span><br><span class="line">        Button pathCalculate = new Button(&quot;calculate path&quot;);</span><br><span class="line"></span><br><span class="line">        VerticalLayout result = new VerticalLayout();</span><br><span class="line">        TextField transferNum = new TextField();</span><br><span class="line">        TextField distance = new TextField();</span><br><span class="line">        Text stations = new Text(&quot;&quot;);</span><br><span class="line">        result.add(</span><br><span class="line">                new H3(&quot;换乘数: &quot;),</span><br><span class="line">                transferNum,</span><br><span class="line">                new H3(&quot;总距离 :&quot;),</span><br><span class="line">                distance,</span><br><span class="line">                new H3(&quot;途径站点详情:&quot;),</span><br><span class="line">                stations</span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        pathCalculate.addClickListener(click -&gt; &#123;</span><br><span class="line">            PathInfoVO pathResult = pathService.getPath(start.getValue(), end.getValue());</span><br><span class="line">            System.out.println(pathResult);</span><br><span class="line">            stations.setText(StringUtils.join(pathResult.getDetail(), &quot;,&quot;));</span><br><span class="line">            transferNum.setValue(String.valueOf(pathResult.getTransferNum()));</span><br><span class="line">            distance.setValue(String.valueOf(pathResult.getDistance()));</span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">        add(</span><br><span class="line">                new Text(&quot;hello world&quot;),</span><br><span class="line">                path,</span><br><span class="line">                pathCalculate,</span><br><span class="line">                result</span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>In this code, we used Java code entirely to arrange the various components on the page. Even the button’s click handler is defined in a way familiar to Java developers, and it can directly call other backend services — making it nearly zero learning cost.</p><p>For detailed code and the running effect, you can check the <a href="https://github.com/lcy362/mobo-subway">project repository</a>.</p><p>If you’re interested in Vaadin or have any questions or thoughts, feel free to discuss in the comments section. Let’s explore how to better leverage Vaadin and improve our development efficiency together!</p><p>Source: <a href="https://lichuanyang.top/en/posts/43947/">https://lichuanyang.top/en/posts/43947/</a></p><hr><h2 id="Quick-Start-Guide"><a href="#Quick-Start-Guide" class="headerlink" title="Quick Start Guide"></a>Quick Start Guide</h2><h3 id="Step-1-Environment-Setup"><a href="#Step-1-Environment-Setup" class="headerlink" title="Step 1: Environment Setup"></a>Step 1: Environment Setup</h3><p>Ensure JDK and Maven&#x2F;Gradle are installed. Spring Boot is recommended as the base framework. Install the Vaadin plugin in your IDE for a better development experience.</p><h3 id="Step-2-Create-Your-First-Vaadin-Project"><a href="#Step-2-Create-Your-First-Vaadin-Project" class="headerlink" title="Step 2: Create Your First Vaadin Project"></a>Step 2: Create Your First Vaadin Project</h3><p>Use Spring Initializr or the official Vaadin scaffolding to create a project, adding Vaadin dependencies. After starting the project, visit the default port to see the initial page.</p><h3 id="Step-3-Write-UI-Components"><a href="#Step-3-Write-UI-Components" class="headerlink" title="Step 3: Write UI Components"></a>Step 3: Write UI Components</h3><p>Use Vaadin’s component classes (such as <code>TextField</code>, <code>Button</code>, <code>VerticalLayout</code>, etc.) to build page layouts directly in Java code. Define page routes with the <code>@Route</code> annotation and set page titles with <code>@PageTitle</code>.</p><h3 id="Step-4-Data-Binding"><a href="#Step-4-Data-Binding" class="headerlink" title="Step 4: Data Binding"></a>Step 4: Data Binding</h3><p>Inject business services via <code>@Autowired</code>, call backend logic in button click events, and set the returned results to corresponding UI components to achieve frontend-backend data interaction.</p><h3 id="Step-5-Deployment"><a href="#Step-5-Deployment" class="headerlink" title="Step 5: Deployment"></a>Step 5: Deployment</h3><p>Package into an executable JAR file using <code>mvn package</code> or <code>gradle build</code>, then deploy to a server to run.</p><hr>]]>
    </content>
    <id>https://lichuanyang.top/en/posts/43947/</id>
    <link href="https://lichuanyang.top/en/posts/43947/"/>
    <published>2024-03-06T11:36:22.000Z</published>
    <summary>Introduction to how the Vaadin framework enables backend engineers to efficiently develop web interfaces without diving deep into the frontend ecosystem, solving the pain points of backend developers writing frontend code.</summary>
    <title>Vaadin Framework Tutorial: A Frontend Development Guide for Java Engineers</title>
    <updated>2026-06-27T03:50:04.987Z</updated>
  </entry>
  <entry>
    <author>
      <name>SandGrid</name>
    </author>
    <category term="hexo" scheme="https://lichuanyang.top/en/tags/hexo/"/>
    <category term="blog" scheme="https://lichuanyang.top/en/tags/blog/"/>
    <category term="i18n" scheme="https://lichuanyang.top/en/tags/i18n/"/>
    <content>
      <![CDATA[<h2 id="Common-Multi-language-Blog-Solutions"><a href="#Common-Multi-language-Blog-Solutions" class="headerlink" title="Common Multi-language Blog Solutions"></a>Common Multi-language Blog Solutions</h2><p>The simplest way to use hexo to build a multilingual website is to directly build two independent sites and make a jump link to each other. </p><p>Here’s how to implement it.</p><h2 id="Solution-Comparison"><a href="#Solution-Comparison" class="headerlink" title="Solution Comparison"></a>Solution Comparison</h2><p>Suppose I now have a Chinese blog and plan to add an English site</p><h2 id="Hexo-Dual-site-Implementation-Details"><a href="#Hexo-Dual-site-Implementation-Details" class="headerlink" title="Hexo Dual-site Implementation Details"></a>Hexo Dual-site Implementation Details</h2><ul><li><p>Copy the entire blog directory as the English blog directory. For example, my blog root directory is called blog.source, and copy a blog.source.en. After this, if source code of the blog is maintained using git, you can just create a new git project directly in the outer layer of the original directory and manage it in the outer layer. For example:</p>   <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">blog(git root directory)/blog.source</span><br><span class="line">                        /blog.source.en</span><br></pre></td></tr></table></figure></li><li><p>Delete all articles in the en directory; adjust text such as site description to English content; set the English language to en; set the English site root (root configuration in _config.yml) to &#x2F;en</p></li><li><p>Add jump links under two sites. I used the menu function to directly add an other language item to the menu. The configuration in the next theme is as follows:</p>   <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Chinese site:      English: /en || fa fa-language</span><br><span class="line">English site:      Chinese: https://lichuanyang.top || fa fa-language</span><br><span class="line"></span><br></pre></td></tr></table></figure></li><li><p>After that, the configuration is basically completed. You can write English articles in the en directory. The process is exactly the same as when writing Chinese articles before.</p></li></ul><h2 id="Deployment-and-CI-CD"><a href="#Deployment-and-CI-CD" class="headerlink" title="Deployment and CI&#x2F;CD"></a>Deployment and CI&#x2F;CD</h2><ul><li>The last step is to generate and publish. Pay attention to this step. Everytime we generate, we need to generate a Chinese site first, then generate an English site and copy the English content to the Chinese directory to avoid overwriting the English content when generating Chinese content.  Specific operation examples are as follows:<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo clean &amp;&amp; hexo g &amp;&amp;cd ../blog.source.en &amp;&amp; hexo clean &amp;&amp; hexo g &amp;&amp; cd ../blog.source &amp;&amp;cp -r ../blog.source.en/public/. public/en/ &amp;&amp; hexo s</span><br></pre></td></tr></table></figure>At the end of the command, use hexo s to start locally, use hexo d to publish it, just like normal use.</li></ul><p>In this way, we now have a multi-language site which is quite easy to use. After that, write Chinese content in the Chinese directory, write English content in the English directory, and finally execute the above command.</p><p>Original address： <a href="https://lichuanyang.top/en/posts/40400/">https://lichuanyang.top/en/posts/40400/</a></p>]]>
    </content>
    <id>https://lichuanyang.top/en/posts/40400/</id>
    <link href="https://lichuanyang.top/en/posts/40400/"/>
    <published>2024-01-23T10:41:29.000Z</published>
    <summary>
      <![CDATA[<h2 id="Common-Multi-language-Blog-Solutions"><a href="#Common-Multi-language-Blog-Solutions" class="headerlink" title="Common]]>
    </summary>
    <title>An Easy Way to Build a Multi-Language Blog with Hexo</title>
    <updated>2026-06-27T02:18:12.737Z</updated>
  </entry>
  <entry>
    <author>
      <name>SandGrid</name>
    </author>
    <category term="Tech Talk" scheme="https://lichuanyang.top/en/categories/Tech-Talk/"/>
    <category term="zhihu" scheme="https://lichuanyang.top/en/tags/zhihu/"/>
    <category term="tampermonkey" scheme="https://lichuanyang.top/en/tags/tampermonkey/"/>
    <category term="chrome-extension" scheme="https://lichuanyang.top/en/tags/chrome-extension/"/>
    <content>
      <![CDATA[<p>Zhihu, as a high-quality text community, often features deeply insightful comment discussions. However, Zhihu’s default comment timestamp format has a subtle but frustrating problem — it only shows relative times like “3 hours ago,” “yesterday,” or “5 days ago.” That’s fine for casual browsing, but if you’re trying to trace the timeline of a discussion or compare the order of different comments, this fuzzy time display is completely inadequate.</p><span id="more"></span><h2 id="The-Problem-Limitations-of-Relative-Time"><a href="#The-Problem-Limitations-of-Relative-Time" class="headerlink" title="The Problem: Limitations of Relative Time"></a>The Problem: Limitations of Relative Time</h2><p>Relative timestamps come with a few clear downsides:</p><ol><li><strong>Information loss</strong>: You never know exactly what hour and minute “3 hours ago” actually refers to — there’s no way to form a precise mental timestamp anchor</li><li><strong>Cross-day confusion</strong>: After today passes, “yesterday” becomes “the day before yesterday,” and the reference frame keeps shifting</li><li><strong>Discussion tracking is hard</strong>: In a technical discussion, person A might reply to person B’s point, but B’s comment and A’s reply might both show “2 days ago” — you can’t tell who posted first</li></ol><h2 id="How-This-Script-Came-About"><a href="#How-This-Script-Came-About" class="headerlink" title="How This Script Came About"></a>How This Script Came About</h2><p>I previously saw a complaint on <a href="https://meta.appinn.net/t/topic/47711/18">Appinn</a> about the chaotic time display in Zhihu’s comment sections. Someone in that thread quickly whipped up a Tampermonkey script to solve the problem right then and there.</p><p>I used it for several months and the experience was excellent. Then, a while back, Zhihu pushed a frontend update that broke the script — time display became messy again. I spent an afternoon digging into Zhihu’s new DOM structure, fixed the script’s logic, and decided to publish it on GreasyFork. This way, if Zhihu makes more adjustments in the future, updates can be pushed directly from GreasyFork — no need for manual reinstallation.</p><h2 id="Installation-Usage"><a href="#Installation-Usage" class="headerlink" title="Installation &amp; Usage"></a>Installation &amp; Usage</h2><p>The script is published on GreasyFork:</p><p><strong><a href="https://greasyfork.org/zh-CN/scripts/482871-%E7%9F%A5%E4%B9%8E%E5%A2%9E%E5%BC%BA-%E8%AF%84%E8%AE%BA%E6%97%B6%E9%97%B4%E7%B2%BE%E7%A1%AE%E5%88%B0%E7%A7%92">Install link</a></strong></p><p>Prerequisite: you need the Tampermonkey extension installed in your browser:</p><ul><li><strong>Chrome</strong>: <a href="https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo">Chrome Web Store</a></li><li><strong>Edge</strong>: Search for Tampermonkey directly in the Edge Add-ons store</li><li><strong>Firefox</strong>: <a href="https://addons.mozilla.org/en-US/firefox/addon/tampermonkey/">Firefox Add-ons</a></li></ul><p>Once Tampermonkey is installed, click the GreasyFork link above and then click “Install this script.” The script will automatically take effect in Zhihu’s comment sections — no additional configuration needed.</p><h2 id="Technical-Implementation-Overview"><a href="#Technical-Implementation-Overview" class="headerlink" title="Technical Implementation Overview"></a>Technical Implementation Overview</h2><p>The core logic of the script isn’t complicated. It comes down to three key steps:</p><p><strong>1. Locating the Time Elements</strong></p><p>Zhihu stores comment timestamp information on a <code>span</code> element. In the old version, the full timestamp was stored in the <code>title</code> attribute. After Zhihu’s update, they switched to a different attribute, so the script needed to adapt to the new DOM structure.</p><p><strong>2. Extraction &amp; Formatting</strong></p><p>The script reads the raw ISO time string from the DOM element (e.g., <code>2024-01-15T14:32:08.000Z</code>) and formats it into a human-readable format like <code>2024-01-15 14:32:08</code> using JavaScript, replacing the original relative time text.</p><p><strong>3. Handling Dynamic Loading</strong></p><p>Zhihu’s comment sections use lazy loading — more comments only load as you scroll to the bottom. The script uses a <code>MutationObserver</code> to listen for DOM changes, so when new comments are inserted into the page, it automatically applies the same precise timestamp treatment to them.</p><h2 id="Future-Plans"><a href="#Future-Plans" class="headerlink" title="Future Plans"></a>Future Plans</h2><p>I’m planning to extend this script with more features, such as:</p><ul><li><strong>One-click jump to referenced comments</strong>: Zhihu’s reply format is “replying to xxx,” but there’s no way to jump directly to the quoted comment</li><li><strong>Highlight OP (Original Poster) comments</strong>: Quickly spot the question author’s replies in long discussions</li><li><strong>Comment export</strong>: Export insightful discussions as Markdown</li></ul><p>If you have other pain points with the Zhihu experience, feel free to leave a comment with your feature requests. If there’s interest in Tampermonkey script development, I might consider putting together a beginner’s tutorial on the topic in the future.</p><hr>]]>
    </content>
    <id>https://lichuanyang.top/en/posts/44912/</id>
    <link href="https://lichuanyang.top/en/posts/44912/"/>
    <published>2023-12-22T09:24:18.000Z</published>
    <summary>Tampermonkey script: Display Zhihu comment timestamps with second-level precision, fixing the time display issues after Zhihu's update.</summary>
    <title>Zhihu Enhancement Tool — Comment Timestamps Down to the Second</title>
    <updated>2026-06-27T00:37:08.383Z</updated>
  </entry>
  <entry>
    <author>
      <name>SandGrid</name>
    </author>
    <category term="Tech Talk" scheme="https://lichuanyang.top/en/categories/Tech-Talk/"/>
    <category term="zhihu" scheme="https://lichuanyang.top/en/tags/zhihu/"/>
    <category term="tampermonkey" scheme="https://lichuanyang.top/en/tags/tampermonkey/"/>
    <category term="chrome-extension" scheme="https://lichuanyang.top/en/tags/chrome-extension/"/>
    <content>
      <![CDATA[<p>Everyone knows what kind of website Zhihu is. Today, let’s look at it from a different angle — I’m sharing a small tool to make “browsing Zhihu at work” safer and more efficient.</p><span id="more"></span><h2 id="The-Economics-of-Browsing-at-Work-Why-Zhihu"><a href="#The-Economics-of-Browsing-at-Work-Why-Zhihu" class="headerlink" title="The Economics of Browsing at Work: Why Zhihu?"></a>The Economics of Browsing at Work: Why Zhihu?</h2><p>I was looking through my own Zhihu account analytics recently and noticed something quite interesting: <strong>over 40% of traffic came from the PC side</strong>. In the mobile internet era, that number is unusually high.</p><p>I also learned from other sources that Zhihu’s traffic on weekends and holidays is noticeably lower than on workdays. What does this tell us?</p><p><strong>You’re not the only one browsing Zhihu at work.</strong></p><p>For a significant portion of people, Zhihu’s primary use case is — browsing during work hours. And Zhihu happens to be uniquely suited for this:</p><ul><li><strong>Primarily text-based content</strong>: Unlike short videos, there’s no sound and minimal data usage</li><li><strong>Plenty of professional content</strong>: When your boss walks by and glances at your screen, you can genuinely say “I’m looking up technical material”</li><li><strong>Right-sized articles</strong>: A timeline of posts you can read a few minutes at a time, with very little mental overhead</li></ul><h2 id="Three-Pain-Points-of-Browsing-Zhihu-at-Work"><a href="#Three-Pain-Points-of-Browsing-Zhihu-at-Work" class="headerlink" title="Three Pain Points of Browsing Zhihu at Work"></a>Three Pain Points of Browsing Zhihu at Work</h2><p>However, some of Zhihu’s PC design choices aren’t exactly friendly for the covert browser:</p><p><strong>1. Oversized Images</strong></p><p>Article images on Zhihu often take up more than half the screen. No matter how serious the article itself is, a giant image on your display is instantly noticeable to anyone passing by — at that point, saying “I was reading a technical article” doesn’t sound very convincing.</p><p><strong>2. Attention-Grabbing Title Banner</strong></p><p>When browsing Zhihu questions, there’s a very conspicuous title banner hovering at the top of the page. It doesn’t just take up a full line of space — its white background with black text makes it extremely eye-catching. A passerby can tell at a glance you’re on Zhihu.</p><p><strong>3. In-Your-Face Logo</strong></p><p>Zhihu’s logo sits prominently in the top-left corner of every page. Anytime someone glances at your screen or you take a screenshot, that brand identity is unmistakable.</p><h2 id="The-Script-Solution"><a href="#The-Script-Solution" class="headerlink" title="The Script Solution"></a>The Script Solution</h2><p>I wrote a Tampermonkey script that targets these three problems head-on:</p><ul><li><strong>Limit image size</strong>: Maximum width set to 300px, turning all article images into unobtrusive thumbnails</li><li><strong>Hide the title banner</strong>: The question page title banner is removed entirely</li><li><strong>Remove the Zhihu logo</strong>: The logo in the top-left corner is hidden</li></ul><p>The result: Zhihu pages end up looking like a plain text reader — clean, low-key, and perfectly suited for an office environment.</p><h2 id="Installation-Usage"><a href="#Installation-Usage" class="headerlink" title="Installation &amp; Usage"></a>Installation &amp; Usage</h2><p>The script is published on GreasyFork:</p><p><strong><a href="https://greasyfork.org/zh-CN/scripts/483047-%E6%91%B8%E9%B1%BC%E5%8A%A9%E6%89%8B-%E7%9F%A5%E4%B9%8E">Install link</a></strong></p><p>Prerequisite: you need the Tampermonkey extension installed in your browser. Once installed, the script takes effect automatically — no configuration required.</p><h2 id="Technical-Implementation"><a href="#Technical-Implementation" class="headerlink" title="Technical Implementation"></a>Technical Implementation</h2><p>The script’s implementation is actually extremely simple. At its core, it just injects custom CSS via <code>GM_addStyle</code>:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Limit maximum image width</span></span><br><span class="line"><span class="title function_">GM_addStyle</span>(<span class="string">&#x27;.RichContent img &#123; max-width: 300px !important; &#125;&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Hide the title banner</span></span><br><span class="line"><span class="title function_">GM_addStyle</span>(<span class="string">&#x27;.QuestionHeader &#123; display: none !important; &#125;&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Remove the logo</span></span><br><span class="line"><span class="title function_">GM_addStyle</span>(<span class="string">&#x27;.AppHeader-logo &#123; display: none !important; &#125;&#x27;</span>);</span><br></pre></td></tr></table></figure><p>Essentially just a few lines of CSS — but remarkably effective. That’s part of the beauty of Tampermonkey scripts: often you don’t need complex logic; just injecting some CSS can dramatically change a webpage’s visual experience.</p><h2 id="How-It-Differs-from-the-“Zhihu-Enhancement-Tool”"><a href="#How-It-Differs-from-the-“Zhihu-Enhancement-Tool”" class="headerlink" title="How It Differs from the “Zhihu Enhancement Tool”"></a>How It Differs from the “Zhihu Enhancement Tool”</h2><p>I previously wrote a <a href="/posts/44912/">Zhihu Enhancement Tool</a>, which focuses on <strong>information enhancement</strong> — displaying comment timestamps down to the second. This script, by contrast, is about <strong>visual optimization</strong> — making Zhihu look more like a legitimate reading tool in a workplace setting.</p><p>Both scripts can be installed simultaneously; they don’t conflict with each other.</p><p>If you have other pain points with the Zhihu experience, feel free to leave a comment. You might also want to check out my other <a href="/posts/44912/">Zhihu Enhancement Tool</a> — it shows comment timestamps down to the second, which is great for tracing the timeline of technical discussions.</p><hr>]]>
    </content>
    <id>https://lichuanyang.top/en/posts/23393/</id>
    <link href="https://lichuanyang.top/en/posts/23393/"/>
    <published>2022-12-28T10:29:01.000Z</published>
    <summary>Sharing a Tampermonkey script that optimizes the Zhihu PC experience for more efficient browsing during work hours.</summary>
    <title>How to Scientifically Browse Zhihu at Work</title>
    <updated>2026-06-27T00:37:22.426Z</updated>
  </entry>
  <entry>
    <author>
      <name>SandGrid</name>
    </author>
    <category term="Tech Miscellany" scheme="https://lichuanyang.top/en/categories/Tech-Miscellany/"/>
    <category term="personal-growth" scheme="https://lichuanyang.top/en/tags/personal-growth/"/>
    <category term="book-reflections" scheme="https://lichuanyang.top/en/tags/book-reflections/"/>
    <category term="systems-thinking" scheme="https://lichuanyang.top/en/tags/systems-thinking/"/>
    <category term="logical-thinking" scheme="https://lichuanyang.top/en/tags/logical-thinking/"/>
    <content>
      <![CDATA[<p>The primary purpose of this book is to teach readers how to think more effectively when facing complex problems in reality. The book itself focuses on basic systems concepts, which are actually quite easy to understand. Additionally, the author provides numerous examples. When first reading this book, you might feel that the theory and practical examples are somewhat disconnected. But gradually you’ll realize that this topic is indeed not easy to explain in a way that’s easily accessible — it requires readers to put in more thought to gain substantive understanding. The examples provided by the author serve as excellent prompts for guiding readers to think. By following the author’s thinking through these examples, we can better grasp the concepts and logic presented in the book.</p><span id="more"></span><p>So, in these notes, I mainly extract some of the book’s core concepts in a relatively mechanical way, while also attempting to provide some everyday examples. For a better understanding of each section, I recommend reading the book itself and following the author’s reasoning to think through the logic of those examples.</p><h2 id="Basic-Concepts"><a href="#Basic-Concepts" class="headerlink" title="Basic Concepts"></a>Basic Concepts</h2><p>A system is a set of interconnected things that interact with each other over time in specific behavioral patterns.</p><p>Any system consists of three components: elements, connections, and function or purpose.</p><p>Stocks are the foundation of all systems — such as water in a bathtub, population size, books in a bookstore, the volume of trees, money in the bank, and so on. However, stocks don’t necessarily have to be physical; your confidence, your good reputation among friends, or your hopes for the world can all be stocks.</p><p>Stocks change over time, and what causes these changes are “flows.”</p><p>Drawing stock-flow diagrams is a fundamental way to understand systems.</p><p>When a change in one stock affects the inflow or outflow associated with it, a feedback loop is formed.</p><p>Balancing loops and reinforcing loops are two common types of feedback loops — they’re quite intuitive and mean exactly what they sound like. Balancing loops tend to keep system stocks stable, while reinforcing loops continuously amplify and strengthen existing trends.</p><h2 id="Common-Systems"><a href="#Common-Systems" class="headerlink" title="Common Systems"></a>Common Systems</h2><p>Next, let’s explore some common system models to help better understand what a system is.</p><h2 id="Single-Stock-Systems"><a href="#Single-Stock-Systems" class="headerlink" title="Single-Stock Systems"></a>Single-Stock Systems</h2><ul><li><p>System 1.1: A system with one stock and two opposing balancing loops, such as a thermostat, which uses a heating balancing loop and a cooling balancing loop to keep the stock (temperature) stable.</p></li><li><p>System 1.2: A system with one stock, one reinforcing loop, and one balancing loop, such as population — births form the reinforcing loop, and deaths form the balancing loop.</p></li><li><p>System 1.3: A system with time delays, such as inventory. Compared to a simple thermostat, the main difference is that there are delays in the balancing loops.</p></li></ul><h2 id="Double-Stock-Systems"><a href="#Double-Stock-Systems" class="headerlink" title="Double-Stock Systems"></a>Double-Stock Systems</h2><ul><li><p>System 2.1: A system where a renewable stock is constrained by a non-renewable stock, such as extracting oil or other non-renewable resources. In this case, there are two stocks — capital and resources. There is a reinforcing loop: extracting resources generates profits, which increase capital, expand production capacity, and increase extraction. Meanwhile, since oil is non-renewable, extraction becomes increasingly difficult, forming a balancing loop.</p></li><li><p>System 2.2: A system with two renewable stocks, such as fisheries. It’s similar to the oil system, except fish are renewable resources, which leads to different developmental patterns compared to the previous system type.</p></li></ul><h2 id="Three-Key-Characteristics-of-Systems"><a href="#Three-Key-Characteristics-of-Systems" class="headerlink" title="Three Key Characteristics of Systems"></a>Three Key Characteristics of Systems</h2><ul><li><p>Resilience. Systems have resilience because their internal structures contain many interacting feedback loops. These loops support each other, allowing the system to recover to its original state through various means even when subjected to major disturbances. The human body, for example, is a highly resilient system.</p></li><li><p>Self-organization. The ability of a system to make its own structure more complex is called “self-organization.” For systems, self-organization means the system can “evolve” on its own to accomplish larger goals or implement more complex functions. My former boss often told me our goal was to build a team with “self-organization” capabilities. Only then can the team progress alongside business development.</p></li><li><p>Hierarchy. As new structures emerge and complexity increases, self-organizing systems often generate hierarchies or levels. A large system contains many subsystems, which can be decomposed into even more, smaller subsystems. If each subsystem can basically maintain itself, perform certain functions, and serve the needs of a larger system — while the larger system is responsible for regulating and strengthening the operations of each subsystem — then relatively stable, resilient, and efficient structures can emerge and be maintained.</p></li></ul><h2 id="System-Pitfalls"><a href="#System-Pitfalls" class="headerlink" title="System Pitfalls"></a>System Pitfalls</h2><p>Everything we think we know about the world is just a model. Our models are usually highly consistent with reality. This is why we’ve become one of the most successful species on the planet. But our models are still far from being able to fully depict the world. This is why we often make mistakes and are frequently surprised.</p><p>So, let’s examine what problems can be identified from a systems perspective, to help us make fewer mistakes. The author mainly organized the following six points:</p><ul><li>System structure is the root cause of behavior, while system behavior manifests as a series of events over time; yet people tend to be misled by surface appearances</li><li>In a nonlinear world, don’t use linear thinking patterns</li><li>Need to appropriately define system boundaries</li><li>Must be able to see various limiting factors</li><li>Time delays are ubiquitous in systems</li><li>People have only bounded rationality, leading to decisions that are often not globally optimal</li></ul><h2 id="System-Traps-and-Countermeasures"><a href="#System-Traps-and-Countermeasures" class="headerlink" title="System Traps and Countermeasures"></a>System Traps and Countermeasures</h2><p>This chapter covers more specific problems and potential countermeasures for addressing them.</p><h2 id="Policy-Resistance-Treating-Symptoms-Rather-Than-Root-Causes"><a href="#Policy-Resistance-Treating-Symptoms-Rather-Than-Root-Causes" class="headerlink" title="Policy Resistance; Treating Symptoms Rather Than Root Causes"></a>Policy Resistance; Treating Symptoms Rather Than Root Causes</h2><p>Some long-term behavioral patterns may not match people’s expectations and are often seen as problems. Despite people developing various technologies and implementing policy measures to “fix” them, the system seems stubborn, producing the same behavior year after year. This is a common system trap, commonly referred to as “treating symptoms rather than root causes” or “policy resistance,” such as drug proliferation and unemployment.</p><p>“Policy resistance” comes from the bounded rationality of various participants in the system, each with their own goals. When the goals of different subsystems are different or inconsistent, resistance to change emerges. The most effective way to address “policy resistance” is to try to align the goals of various subsystems — typically by establishing a larger overarching goal that allows all participants to transcend their individual bounded rationality.</p><h2 id="Tragedy-of-the-Commons"><a href="#Tragedy-of-the-Commons" class="headerlink" title="Tragedy of the Commons"></a>Tragedy of the Commons</h2><p>For resources shared by people, it’s easy for exploitation (or consumption) to gradually escalate or grow. An important reason the “Tragedy of the Commons” arises is that the feedback between resource consumption and the growth of resource users is missing, or the time delay is too long.</p><p>There are three ways to prevent the “Tragedy of the Commons”: first, education to help people see consequences more clearly; second, privatization of resources; third, implementing regulatory measures such as quota systems.</p><h2 id="Drift-to-Low-Performance"><a href="#Drift-to-Low-Performance" class="headerlink" title="Drift to Low Performance"></a>Drift to Low Performance</h2><p>Performance standards are influenced by past performance, especially when people evaluate past performance too negatively — that is, focusing too much on bad news. This triggers a vicious cycle that continuously lowers both goals and system performance levels. In plain terms, it’s like boiling a frog — the situation deteriorates gradually without being noticed.</p><p>The countermeasure is to maintain an absolute performance standard. A better approach is to set the performance standard at the past best level, thereby continuously raising one’s own goals and using this to motivate improvement.</p><h2 id="Escalation"><a href="#Escalation" class="headerlink" title="Escalation"></a>Escalation</h2><p>“An eye for an eye, a tooth for a tooth.” Each participant’s desired system state is relative to other participants, and they try to surpass them, staying one step ahead — even being tied is unacceptable. Moreover, each participant tends to overestimate the other’s hostility and exaggerate the other’s strength. The system structure of escalation is a reinforcing loop that develops exponentially — once it exceeds a certain threshold, the speed at which competition intensifies will surpass most people’s imagination.</p><p>One countermeasure relies on one party making a concession; a more elegant approach is for both parties to reach an agreement.</p><h2 id="Success-to-the-Successful"><a href="#Success-to-the-Successful" class="headerlink" title="Success to the Successful"></a>Success to the Successful</h2><p>Using accumulated wealth, power, special channels, or insider information can create even more wealth, power, channels, and information. These are all examples of another archetype called “Success to the Successful.”</p><p>Countermeasures: diversification — allowing the losing party in competition to exit and start a new game; antitrust laws — strictly limiting the maximum share the winner can hold; modifying competition rules — limiting the advantages of the strongest participants or giving special consideration to disadvantaged participants to enhance their competitiveness (such as charity, gifts, tax adjustments, transfer payments, etc.); giving diverse rewards to winners to prevent them from competing for the same limited resources or developing biases in the next round of competition.</p><h2 id="Shifting-the-Burden"><a href="#Shifting-the-Burden" class="headerlink" title="Shifting the Burden"></a>Shifting the Burden</h2><p>When facing a systemic problem, if the solution adopted doesn’t address the underlying root cause at all but merely alleviates (or masks) the symptoms, it will lead to shifting the burden, dependency, and addiction.</p><p>The best way to deal with this trap is prevention — don’t fall into the trap in the first place. Always be aware that policies or practices that merely alleviate symptoms or mask signals cannot truly solve problems.</p><h2 id="Rule-Beating"><a href="#Rule-Beating" class="headerlink" title="Rule Beating"></a>Rule Beating</h2><p>Any rules may have loopholes or exceptions, creating opportunities to beat the rules. This means that although some behaviors may appear to comply with or not violate the rules on the surface, they don’t actually align with the spirit of the rules, or even distort the system.</p><p>The countermeasure is to design or redesign rules, gaining creative feedback from rule-beating behaviors so that they serve a positive function and achieve the original purpose of the rules.</p><h2 id="Goal-Misalignment"><a href="#Goal-Misalignment" class="headerlink" title="Goal Misalignment"></a>Goal Misalignment</h2><p>System behavior is particularly sensitive to the goals of feedback loops. If goals are inaccurately or incompletely defined, even if the system faithfully executes all operational rules, the output may not be what people actually want.</p><p>The countermeasure is to appropriately set goals and indicators that reflect the true welfare of the system.</p><h2 id="Ways-to-Change-Systems-Leverage-Points-for-Intervening-in-Systems"><a href="#Ways-to-Change-Systems-Leverage-Points-for-Intervening-in-Systems" class="headerlink" title="Ways to Change Systems: Leverage Points for Intervening in Systems"></a>Ways to Change Systems: Leverage Points for Intervening in Systems</h2><p>This section covers ways we can intervene in systems, ranked from least to most effective. Many of the theories in this chapter have probably been encountered elsewhere to some extent. Many excellent people, even without reading this book, actually think this way in many situations. The book “Thinking in Systems” helps us understand more systematically why we should do it this way.</p><p>12: Numbers — regulating the system through the numerical values of various flows. Like the balance designers in Honor of Kings, most of their energy goes into this: when a hero’s mechanics are too strong, they heavily nerf the numbers; when a hero isn’t performing well, they add numbers to make them viable even in simple face-to-face combat. This approach is actually quite low in effectiveness — it cannot change the fundamental structure of the system. But most people focus 90% of their attention on parameters.</p><p>11: Buffers — By increasing buffer capacity, we can usually stabilize the system. However, if buffers are too large, the system will become inflexible, and its response speed to changes will be too slow. At the same time, establishing, expanding, or maintaining buffer capacity requires significant time and capital, such as building reservoirs or warehouses. For this reason, some enterprises have invented “zero inventory” and “just-in-time” production models. In their view, compared to spending huge sums to maintain fixed inventory, the losses from occasional fluctuations or stockouts are not that significant.</p><ol start="10"><li><p>Stock-flow structure: Physical systems and their intersection nodes. This mainly concerns the overall design of the system. Appropriate leverage points need to be designed correctly from the beginning. Once the physical structure is established, finding leverage points requires understanding the system’s limits and bottlenecks, maximizing their efficiency while avoiding major fluctuations or expansions that exceed their capacity. If the system is already running, adjusting key nodes becomes very difficult. The well-known “anti-corruption layer” concept in software engineering can optimize this problem to some extent by using pre-set intermediate layers to reduce the cost of adjusting key nodes.</p></li><li><p>Time delays: The speed at which the system responds to changes. Time delays are a high-leverage point, but in practice, time delays are usually not easy to change. The development of many things follows its own internal laws — it takes however long it takes. You can’t accumulate a large amount of capital overnight, children can’t grow up overnight, and pulling seedlings to help them grow faster won’t speed up crop growth. But if there’s a way to change time delays, it can often produce significant results. For example, in recent pandemic prevention and control efforts, the reason for frequently conducting large-scale nucleic acid testing is to reduce the time delay between virus transmission and detection.</p></li><li><p>Balancing loops: The feedback force that attempts to correct external influences. This is easy to understand — we can strengthen control over the system by adding balancing loops. The controllers in Kubernetes can be considered implementations of balancing loops, which use different controllers to control the values (stocks) of different components at their desired levels.</p></li><li><p>Reinforcing loops: The feedback force that drives the growth of returns. Similar to balancing loops, but with a different goal.</p></li><li><p>Information flows: The structure of who can access information. This is equivalent to a new loop that allows people to get feedback in places where they previously couldn’t. For example, the capitalistic practice of ranking overtime hours tells you about colleagues’ overtime information, naturally creating pressure that promotes overtime work.</p></li><li><p>System rules: Incentives, penalties, and constraints. This section essentially defines the scope, boundaries, and degrees of freedom of the system.</p></li><li><p>Self-organization: The force that increases, changes, or evolves system structure. Returning to the topic at the beginning, building a self-organizing team requires doing many things, such as improving each team member’s capabilities so everyone has decision-making ability, and enhancing information transparency within the team. Once such a team is built, not only will output improve qualitatively, but team members’ work satisfaction and happiness will also increase significantly.</p></li><li><p>Goals: The purpose or function of the system. A participant in the system can clearly set, articulate, repeat, support, and persist in new goals, thereby guiding the system’s transformation. This is why OKRs have become so popular in recent years. Through clear and reasonable OKRs, people are guided to make better decisions and take better actions.</p></li><li><p>Social paradigms: The mental models that determine what a system is. Some socially recognized concepts, some underlying basic assumptions, and widespread views about the nature of social reality constitute the social paradigm — or a whole worldview. These are a series of basic assumptions, rules, or beliefs that people generally hold about how the world works. These beliefs are implicit because in a society, almost everyone already knows them and thus there’s no need to specifically declare them. These paradigms naturally have a huge impact on system operations, and of course, changing them is more difficult than changing other things.</p></li><li><p>Transcending paradigms. Compared to changing paradigms, at a higher level, there is another leverage point: freeing oneself from the control of any paradigm. This point has a somewhat metaphysical quality, so we won’t elaborate much here. The core idea is that we need to consciously step outside the current system.</p></li></ol><h2 id="Rules-for-Living-with-Systems-Dancing-with-Systems"><a href="#Rules-for-Living-with-Systems-Dancing-with-Systems" class="headerlink" title="Rules for Living with Systems: Dancing with Systems"></a>Rules for Living with Systems: Dancing with Systems</h2><p>People who grew up in industrial society and are enthusiastic about systems thinking are likely to make a serious mistake. They may assume that through systems analysis, they can understand the interconnections and complex entanglements within systems, and with the power of computers, ultimately find the key to predicting and controlling systems. Unfortunately, this is a mistaken notion rooted in the deep-seated mental model of the industrial era — the belief that there exists a key to prediction and control. But in reality, achieving this is completely unrealistic. We need to recognize and be willing to give up the illusion of control, and adopt a completely different approach. This way, we can still make significant contributions. This approach is dancing with systems. We cannot control systems, but we can live better within them.</p><p>Next are some rules for dancing with systems — this section is essentially a collection of tips, all fairly easy to understand, so we’ll list them briefly:</p><ul><li>Keep up with the rhythm of the system: You need to observe how the system operates in order to keep up with it. We need to pay more attention to facts and data so as not to be led astray by other people’s theories.</li><li>Expose your mental models to the light, draw system structure diagrams, force yourself to project various hidden assumptions from within, and express them precisely.</li><li>Trust, respect, and share information</li><li>Use language carefully and enrich it with systems concepts</li><li>Focus on what’s important, not just what’s easy to measure</li><li>Design policies with feedback functions for feedback systems. For dynamic, self-regulating feedback systems, static, rigid policies cannot be used for regulation. Good policies must be able to flexibly adjust in a timely manner according to changes in system state.</li><li>Pursue overall interests</li><li>Listen to the wisdom of the system</li><li>Define system responsibilities</li><li>Stay humble, be a learner</li><li>Celebrate complexity</li><li>Expand the time horizon</li><li>Break through various taboos and rigid rules</li><li>Expand the scope of concern</li><li>Don’t lower the standards of “good”</li></ul><p>Source: <a href="https://lichuanyang.top/en/posts/53791/">https://lichuanyang.top/en/posts/53791/</a></p>]]>
    </content>
    <id>https://lichuanyang.top/en/posts/53791/</id>
    <link href="https://lichuanyang.top/en/posts/53791/"/>
    <published>2022-05-16T10:34:01.000Z</published>
    <summary>Book notes on 'Thinking in Systems', learning systematic thinking methods for facing complex problems and building the right mental frameworks.</summary>
    <title>Book Notes: Thinking in Systems - How to Face Complex Problems in Reality</title>
    <updated>2026-06-27T02:18:32.206Z</updated>
  </entry>
  <entry>
    <author>
      <name>SandGrid</name>
    </author>
    <category term="distributed-systems" scheme="https://lichuanyang.top/en/tags/distributed-systems/"/>
    <category term="distributed-system-design" scheme="https://lichuanyang.top/en/tags/distributed-system-design/"/>
    <content>
      <![CDATA[<p>Previously, I translated an article about distributed systems (<a href="https://lichuanyang.top/posts/3914/">https://lichuanyang.top/posts/3914/</a>) which received positive feedback across various platforms. So I recently reorganized the related knowledge, combined with new insights gained over the past year, and rewrote this article.</p><span id="more"></span><p>First, let’s clarify what kind of distributed systems we’re discussing here. Simply put, they need to satisfy two conditions: <strong>multi-node</strong> and <strong>stateful</strong>. Multi-node is straightforward to understand. Being stateful means the system needs to maintain some data — otherwise, we could simply scale horizontally without limit, and there would be no distributed systems problem to speak of.</p><p>Common distributed systems — whether databases like MySQL, Cassandra, HBase, message queues like RocketMQ, Kafka, Pulsar, or infrastructure like ZooKeeper — all satisfy these two conditions.</p><p>The implementation of these distributed systems typically needs to address two aspects: first, the system’s own functionality; and second, maintaining good performance and stability in a distributed environment. Even two systems with completely different functions will have many similarities in how they address the second type of problem. This article focuses on the second type.</p><h2 id="Core-Challenges-of-Distributed-Systems"><a href="#Core-Challenges-of-Distributed-Systems" class="headerlink" title="Core Challenges of Distributed Systems"></a>Core Challenges of Distributed Systems</h2><p>Next, let’s enumerate the common goals of distributed systems, including but not limited to:</p><ul><li>A large number of ordinary servers interconnected via a network, collectively providing services to the outside world.</li><li>As the cluster scales up, overall system performance scales linearly.</li><li>Automatic fault tolerance — failed nodes are automatically migrated, and data consistency across different nodes is maintained.</li></ul><p>What are the challenges in achieving these goals? Here are the main ones:</p><ol><li><p><strong>Process crashes</strong>: Caused by many factors, including hardware failures, software bugs, routine maintenance, and in cloud environments, more complex reasons. The biggest problem with process crashes is data loss. For performance reasons, we often don’t write synchronously to disk — instead, we temporarily buffer data in memory and periodically flush to disk. When a process crashes, the data in memory buffers is clearly lost.</p></li><li><p><strong>Network delays and interruptions</strong>: When communication between nodes becomes very slow, how does one node determine whether another node is healthy?</p></li><li><p><strong>Network partitions</strong>: The cluster’s nodes split into two subsets — communication within each subset works, but communication between subsets is broken (brain split). How should the cluster continue to serve requests?</p><p>A brief digression: under the CAP theorem, real-world systems typically adopt one of two modes — CP mode (giving up high availability) or AP mode (giving up strong consistency). Why is there no CA mode that gives up partition tolerance? Because we cannot assume network communication will always be reliable. Once a cluster splits into two partitions, re-merging is not realistic.</p></li><li><p><strong>Process pauses</strong>: For example, a full GC may cause a process to become temporarily unavailable before quickly recovering. During the unavailable period, the cluster may have already reacted accordingly. How should state consistency be maintained when the node comes back?</p></li><li><p><strong>Clock skew and message reordering</strong>: We want operations across different nodes in the cluster to have a clear ordering. Clock skew between nodes prevents us from using timestamps to guarantee this. Message reordering further complicates distributed system processing.</p></li></ol><p>Now, let’s walk through the solutions to these problems.</p><h2 id="Partitioning-and-Replication"><a href="#Partitioning-and-Replication" class="headerlink" title="Partitioning and Replication"></a>Partitioning and Replication</h2><p>For process crashes, the key point is that simply ensuring no data loss during a crash is not difficult at all — the real challenge is achieving this while maintaining system performance.</p><p>The first pattern to introduce is <strong>Write-Ahead Log (WAL)</strong>: the server stores every state change as a command in an append-only file on disk. Since append operations are sequential disk writes, they are typically very fast and can be completed without impacting performance. During server failure recovery, the log can be replayed to rebuild in-memory state.</p><p>The core idea is to first persist data in a low-cost way (not necessarily limited to sequential disk writes), then acknowledge the client without blocking other operations. The server asynchronously performs the subsequent high-cost work.</p><p>Typical scenarios and variants: MySQL redo log; Redis AOF; Kafka itself; common practices in application development — for time-consuming operations, first write a database record indicating the task will be executed, then perform the actual task asynchronously.</p><p>WAL introduces a minor issue: the log keeps growing. How to handle its own storage? Two natural approaches: <strong>segmentation</strong> and <strong>cleanup</strong>.</p><p>Segmentation splits a large log into smaller ones. Since WAL logic is generally simple, splitting is not complex — much easier than typical database sharding. This pattern is called <strong>Segmented Log</strong>, and the canonical implementation is Kafka’s partitions.</p><p>For cleanup, there is a pattern called <strong>low-water mark</strong>: a marker indicating which parts of the log can be cleaned up. The marking can be based on data status (redo log), preset retention time (Kafka), or more fine-grained cleanup and compaction (AOF).</p><h2 id="Consistency-Protocols"><a href="#Consistency-Protocols" class="headerlink" title="Consistency Protocols"></a>Consistency Protocols</h2><p>Moving to network-related issues, a simple <strong>heartbeat</strong> pattern solves inter-node state synchronization. If no heartbeat is received within a time window, the node is considered down.</p><p>For brain-split scenarios, the <strong>Quorum</strong> pattern is typically used. It requires the number of live nodes in the cluster to reach a Quorum value (typically, with 2f+1 nodes, at most f nodes can go down, so the Quorum value is f+1) before the cluster can serve requests. When examining implementations of distributed systems like RocketMQ or ZooKeeper, you’ll find the requirement for a minimum number of live nodes — this is exactly the Quorum pattern.</p><p>Quorum solves the data durability problem — successfully written data will not be lost even if nodes fail. However, Quorum alone cannot guarantee strong consistency, because data on different nodes may differ in time. When clients connect to different nodes, they may see different results. This can be solved through the <strong>Leader and Followers</strong> pattern, where one node is elected as the leader to coordinate data replication across nodes and determine which data is visible to clients.</p><p>The <strong>High-Water Mark</strong> pattern determines which data is visible to clients. Generally, after a write is completed on a Quorum number of follower nodes, the data can be marked as visible to clients. The line of completed replication is the high-water mark.</p><p>The Leader-Followers pattern is so widely used that examples are unnecessary. There are many distributed election algorithms — Bully, ZAB, Paxos, Raft, among others. Paxos is difficult both to understand and implement. Bully triggers frequent elections when nodes go up and down frequently. Raft, on the other hand, strikes a relatively balanced trade-off in terms of stability, implementation complexity, and adoption — it is the most widely used distributed election algorithm. For example, Elasticsearch replaced Bully with Raft in version 7.0; Kafka 2.8 switched from ZooKeeper’s ZAB protocol to Raft.</p><p>At this point, let’s summarize. Essentially, an operation on a distributed system can be概括为 the following steps:</p><ol><li>Write to the leader’s Write-Ahead Log</li><li>Write to 1 follower’s WAL</li><li>Write to the leader’s data store</li><li>Write to 1 follower’s data store</li><li>Write to a Quorum of followers’ WALs</li><li>Write to a Quorum of followers’ data stores</li></ol><p>The order of steps 2–5 is not fixed. The most important way a distributed system balances performance and stability is, in essence, deciding the order of these operations and when to return a success acknowledgment to the client. For example, MySQL’s synchronous replication, asynchronous replication, and semi-synchronous replication are classic examples of these different orderings.</p><h2 id="Fault-Detection-and-Recovery"><a href="#Fault-Detection-and-Recovery" class="headerlink" title="Fault Detection and Recovery"></a>Fault Detection and Recovery</h2><p>For process pauses, the main problem scenario is: suppose the leader pauses. During the pause, a new leader is elected. When the original leader recovers, what should happen? The <strong>Generation Clock</strong> pattern addresses this — simply put, each leader is assigned a monotonically increasing generation number to indicate which “generation” of leader it is. Concepts like Raft’s “term” and ZAB’s “epoch” are implementations of this Generation Clock idea.</p><p>For clock skew, in a distributed environment, clock differences between nodes are inevitable. Under the Leader-Followers pattern, this problem is already largely minimized. Many systems choose to perform all operations on the leader, with leader-follower replication taking the form of replicating and replaying logs. In this way, clock issues generally don’t need to be considered. The only potential issue arises during leader failover, where the old leader and new leader might produce out-of-order data.</p><p>One solution to clock skew is to set up a dedicated synchronization service called an <strong>NTP service</strong>. However, this approach is not perfect — since it involves network operations, some error is inevitable. When relying on NTP to solve clock skew, the system design must tolerate very small timing errors.</p><p>Actually, beyond forcibly aligning clocks, there are simpler approaches to consider. First, let’s think about a question: do we really need to ensure messages are ordered by real-world physical time? Not really. What we need is a <strong>consistent, reproducible way to determine message order</strong> so that all nodes agree on the order. In other words, messages don’t have to follow physical chronological order, but the order determined by different nodes should be the same.</p><p>The <strong>Lamport Clock</strong> technique achieves this goal. Its logic is simple, as shown in the diagram:</p><p><img src="/en/img/lamport.png" alt="lamport stamp"></p><p>Operations on a local node increment the local stamp by 1. During network communication — for example, when node C receives data from node B — it compares its current stamp with B’s stamp + 1 and takes the larger value as its new stamp. This simple operation guarantees that the order of any two causally related operations (whether on the same node or communicated between nodes) is consistent across all nodes.</p><h2 id="Design-Principles-Summary"><a href="#Design-Principles-Summary" class="headerlink" title="Design Principles Summary"></a>Design Principles Summary</h2><p>Additionally, there are relatively simpler considerations that frequently arise in distributed system design — such as how to distribute data evenly across nodes. For this, we might need to find an appropriate shard key based on the business scenario, or find a suitable hash algorithm. There is also <strong>consistent hashing</strong> technology, which gives us more flexibility in controlling data distribution.</p><p>Another key consideration in distributed system design is how to measure system performance, with metrics including latency, throughput, availability, consistency, and scalability. These are easy to understand conceptually, but measuring them more comprehensively — especially observing them conveniently — is itself a large topic.</p><p>Source: <a href="https://lichuanyang.top/en/posts/45718/">https://lichuanyang.top/en/posts/45718/</a></p>]]>
    </content>
    <id>https://lichuanyang.top/en/posts/45718/</id>
    <link href="https://lichuanyang.top/en/posts/45718/"/>
    <published>2022-04-13T12:13:24.000Z</published>
    <summary>An exploration of common patterns in distributed system design, including sharding, replication, consistency models, and fault tolerance strategies.</summary>
    <title>Common Patterns in Distributed System Design</title>
    <updated>2026-06-27T03:48:16.252Z</updated>
  </entry>
  <entry>
    <author>
      <name>SandGrid</name>
    </author>
    <category term="Tech Talk" scheme="https://lichuanyang.top/en/categories/Tech-Talk/"/>
    <category term="computational-advertising" scheme="https://lichuanyang.top/en/tags/computational-advertising/"/>
    <category term="internet-advertising" scheme="https://lichuanyang.top/en/tags/internet-advertising/"/>
    <category term="rtb" scheme="https://lichuanyang.top/en/tags/rtb/"/>
    <content>
      <![CDATA[<p>In this article, I will introduce the current state of internet advertising and untangle the underlying logic of internet advertising, to help everyone better understand this industry.</p><span id="more"></span><h2 id="What-Is-Advertising"><a href="#What-Is-Advertising" class="headerlink" title="What Is Advertising"></a>What Is Advertising</h2><p>When discussing internet advertising, we must first start with what advertising itself is. Only by understanding the underlying logic of advertising can we better understand what problems exist in internet advertising, why programmatic advertising trading emerged, why various systems like ADX, SSP, DSP, DMP appeared, and other similar questions.</p><p>The advertising industry has actually existed since ancient times — from street vendors’ hawking in ancient times, to signs on buildings, to ads in print newspapers, magazines, TV, and other traditional media, and then to internet advertising, this industry has always evolved along with societal development.</p><p>For advertising, let’s first provide a very official, very standard definition: Advertising is a paid, comprehensive, persuasive, non-personal information dissemination activity about products (goods, services, and ideas) conducted through various media by identified sponsors.</p><p>More colloquially, in advertising transactions, there are three core participants: the media (the supply side), the advertisers (the demand side), and the audience (the media’s users). The media and advertisers are active participants, while the audience is a passive participant. Advertising is essentially a process of game theory among these three parties. Advertisers want more audience members to see their ads and achieve higher conversion rates; media want to maximize advertising revenue while minimizing disruption to users; and the audience — ordinary users — simply wants to avoid being bothered by ads they don’t want to see.</p><p>Next, let’s analyze the strategic considerations in this process. As everyone knows, if something is a zero-sum game for all participants, it will inevitably fall into cutthroat competition and be difficult to develop. Since the advertising industry has been able to sustain healthy development, it is mainly due to its impact outside the advertising ecosystem. So let’s analyze what external factors influence the advertising ecosystem.</p><p>Let’s first break down what each party considers. For the media, the main metrics are advertising revenue and product retention. For advertisers, it’s advertising costs and the revenue gained from advertising. As passive participants, the audience can do little during this process — they can only vote with their feet by leaving products with terrible ads. Among these, what lies outside the entire advertising ecosystem are two aspects: the revenue advertisers gain from advertising, and reducing the disruption advertising causes to the audience. In fact, improving these two points is the driving force behind the continuous development of the entire advertising industry.</p><p>Understanding this point helps us better understand the development history of internet advertising.</p><h2 id="The-Development-History-of-Internet-Advertising"><a href="#The-Development-History-of-Internet-Advertising" class="headerlink" title="The Development History of Internet Advertising"></a>The Development History of Internet Advertising</h2><h2 id="Early-Development-of-Internet-Advertising"><a href="#Early-Development-of-Internet-Advertising" class="headerlink" title="Early Development of Internet Advertising"></a>Early Development of Internet Advertising</h2><p>The earliest internet advertising was essentially a direct migration of traditional media advertising formats online. For example, in a newspaper, there would be a dedicated area for ads. The ads we used to see on websites like Sohu and Sina were also in this format. Advertisers would directly purchase a specific area on a webpage to display certain ad content.</p><p>Later, some people gradually noticed a key difference between internet advertising and traditional media: internet advertising content can vary from person to person. Printed newspapers look the same to everyone, but websites can easily show different ads to different groups, such as men and women. The media only needed to guarantee the advertiser a certain delivery volume and compensation methods when the delivery targets were not met. This is known as contract-based advertising.</p><p>A natural development trend of contract-based advertising is increasingly granular targeting — gender, age, location, interests, and hobbies can all become part of the contract terms. At the same time, more and more advertisers entered the scene. Both of these factors would cause the difficulty of fulfilling contracts to increase dramatically. Moreover, under such complex logic, the media also felt that a lot of their traffic was not being sold at the highest possible price.</p><p>In this situation, a new advertising format emerged: auction-based advertising. The supply side no longer guarantees volume through contracts, only guaranteeing the cost per unit of traffic. Each ad impression is decided based on the highest revenue principle. In this way, advertising gradually evolved into a “traffic trading” model, forming programmatic advertising trading.</p><h2 id="Programmatic-Advertising-Trading"><a href="#Programmatic-Advertising-Trading" class="headerlink" title="Programmatic Advertising Trading"></a>Programmatic Advertising Trading</h2><p>In the scenario of auction-based advertising, without contract constraints, a large number of media and advertisers entered a multi-party game environment, making the transaction process increasingly complex and giving rise to transaction methods such as RTB (Real-Time Bidding). Programmatic advertising trading emerged precisely in this context.</p><p>Meanwhile, with the development of technology, more data, deeper computation and prediction became possible, and all participants — advertisers, media, and related advertising agencies — needed richer functionality. The terms mentioned in our title — SSP, ADX, DSP, RTB — also gradually developed into very important systems in the advertising ecosystem under these circumstances.</p><p>Overall, programmatic advertising trading is very similar to real-time trading scenarios like stock trading — there is a trading venue (ADX), buyers (advertisers), and sellers (media), each making real-time buying and selling decisions within it. The only important difference is that for media, there is no opportunity to time the market; when traffic arrives, it must be sold immediately, with no way to hold it and wait for a good opportunity. This point is precisely the starting point for the development of various advertising-related technologies.</p><h2 id="Key-Issues-and-Components-of-Internet-Advertising"><a href="#Key-Issues-and-Components-of-Internet-Advertising" class="headerlink" title="Key Issues and Components of Internet Advertising"></a>Key Issues and Components of Internet Advertising</h2><p>As mentioned earlier, there are two driving forces behind the development of internet advertising: increasing the revenue advertisers gain from advertising, and reducing the disruption advertising causes to the audience. Both of these relate to advertising effectiveness, so let’s analyze what factors influence advertising effectiveness.</p><p>The lifecycle of an ad can roughly be broken down into these steps:</p><p>Exposure → Attention → Understanding → Acceptance → Retention → Decision</p><p>Simply put, the ad must first be displayed, then be seen by the audience, who must understand the message the ad is trying to convey. Furthermore, they must accept this message and retain it for some time, ultimately completing the product decision promoted by the ad (download, purchase, payment, etc.).</p><p>Next, let’s go through the specific impact of each step and what role internet technology can play.</p><p><strong>Exposure</strong>: This stage mainly depends on the physical properties of the ad placement. For example, an ad on an app’s splash screen versus an ad buried deep on some hidden page — their effectiveness will obviously differ greatly. This is the same in real-world advertising, and there isn’t much room for technological optimization here.</p><p><strong>Attention</strong>: This means getting users to notice the ad. It’s worth noting that “seeing” does not equal “noticing.” If the product design is unreasonable, users can easily overlook what’s right in front of them. For example, if a user comes to a page with a very specific task — say, downloading something — then forcing an ad into their download process will very likely cause the user to completely ignore it. Therefore, this step is closely related to product design. Additionally, this is a stage where technology can play an important role. By building a DMP (Data Management Platform) and combining various machine learning algorithms, ads can be delivered to users who are more likely to be interested, thereby increasing ad effectiveness.</p><p><strong>Understanding</strong>: The factor with the greatest impact at this step is the ad creative. Well-designed creative content helps users better understand the message.</p><p><strong>Acceptance</strong>: Simply put, this is about whether users can认可 the information or viewpoint your ad presents. On one hand, this relates to whether the creative is professional; on the other hand, it has a lot to do with the media’s own attributes. For example, the same information placed on DingXiang Doctor versus some Putian hospital website would have vastly different effectiveness.</p><p><strong>Retention and Attention</strong>: These two steps gradually move beyond the scope of the advertising ecosystem, but through quality creative design, appropriate scenarios, and precise audience targeting, they can still lay the groundwork for the final conversion.</p><p>Additionally, for stages that these technologies cannot directly intervene in, providing more complete data and more convenient testing methods can also bring significant help to ad performance.</p><p>Next, let’s look separately from the perspective of media and advertisers to understand their respective needs, and briefly examine how platforms like SSP and DSP work. Due to space constraints, this part will not be detailed here. If you’re interested, we can explore each one in future articles.</p><p>The platform serving the media is what’s commonly known as SSP, whose main function is to help media increase revenue. Some basic features include allowing media to configure ad placements and specifying dimensions like width and height. For increasing revenue, the main approaches are matching the right audience with the right ads, optimizing pricing strategies, and attempting to control IT costs such as network expenses.</p><p>The counterpart to SSP is DSP, which serves the advertiser side. The basic function of DSP is to allow advertisers to set up delivery plans — for example, over what time period, to what type of users, with what total budget. The value the platform provides is mainly helping advertisers spend less to achieve better ad results.</p><p>Even today, every module involved in the advertising ecosystem is still undergoing continuous iteration, but the underlying principle remains the same: the key driving force is making ad delivery more effective while providing users with a better ad browsing experience. Only in this way can the entire industry develop in a healthy direction.</p><p>Original article: <a href="https://lichuanyang.top/posts/27934/">https://lichuanyang.top/posts/27934/</a></p><hr><p>Source: <a href="https://lichuanyang.top/en/posts/27934/">https://lichuanyang.top/en/posts/27934/</a></p>]]>
    </content>
    <id>https://lichuanyang.top/en/posts/27934/</id>
    <link href="https://lichuanyang.top/en/posts/27934/"/>
    <published>2022-03-07T08:28:58.000Z</published>
    <summary>Starting from the essence of advertising, systematically untangle the evolution of internet advertising, explaining core concepts like SSP, DSP, RTB, ADX and the underlying logic of programmatic trading.</summary>
    <title>What Are SSP, DSP, RTB, and ADX? Understanding the Concepts and Evolution of Internet Advertising</title>
    <updated>2026-06-27T03:57:34.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>SandGrid</name>
    </author>
    <category term="Database" scheme="https://lichuanyang.top/en/categories/Database/"/>
    <category term="database-transactions" scheme="https://lichuanyang.top/en/tags/database-transactions/"/>
    <category term="database" scheme="https://lichuanyang.top/en/tags/database/"/>
    <category term="acid" scheme="https://lichuanyang.top/en/tags/acid/"/>
    <category term="transaction-isolation-levels" scheme="https://lichuanyang.top/en/tags/transaction-isolation-levels/"/>
    <content>
      <![CDATA[<p>Recently, I read Zhou Zhiming’s book “Phoenix Architecture” and gained deeper insights into many aspects of technology. I plan to make some summaries. Today, I’ll start with the section on transactions, combining content from the book with my own understanding, aiming to explain local transactions clearly and thoroughly. If there are any inaccuracies, I welcome corrections.</p><span id="more"></span><p>A transaction ensures that data in the database remains in a valid state. Through continuous CRUD operations, the database transitions from one correct state to another, without exposing intermediate “incorrect” states to the outside world.</p><p>A common example: when A has 100 yuan and B has 100 yuan, and A wants to transfer 10 yuan to B, there will inevitably be an intermediate state where A has transferred out 10 yuan but it hasn’t reached B yet. A transaction ensures that users can only perceive two states — A:100, B:100 and A:90, B:110 — and never the strange intermediate state of A:90, B:100, etc.</p><p>This description actually corresponds to the Consistency property in ACID. While ACID is a popular term, A, C, I, and D are not equally weighted concepts. Simply put, A, I, and D are methods, while C is the goal. In other words, by implementing Atomicity, Durability, and Isolation, you achieve Consistency — and thus achieve a transaction.</p><p>Next, let’s look at how each of A, I, and D can be implemented.</p><h2 id="Atomicity-and-Durability"><a href="#Atomicity-and-Durability" class="headerlink" title="Atomicity and Durability"></a>Atomicity and Durability</h2><p>Atomicity and Durability face many of the same implementation challenges, so they’re introduced together.</p><p>Let’s review the basic concepts: Atomicity means all operations within a transaction either all succeed or all fail; Durability means that once committed, completed operations are not lost.</p><p>It’s worth noting that “simply implementing atomicity and durability” is not inherently difficult. The real question is “how to implement atomicity and durability with high performance” (the same applies to Isolation — only high-performance isolation is meaningful).</p><p>A key point is that writing to disk is a very expensive operation, so there is usually a memory buffer. Data to be written to disk is first written to the buffer and then flushed to disk at an appropriate time. If the system fails after a transaction is committed but before the data is flushed to disk, this unflushed data will naturally be lost, and the database loses its durability. A natural solution is to force a disk flush when the transaction commits. Is this feasible? Of course it is. But the problem is that it impacts performance. System failures are rare events, and handling this rare scenario effectively means every operation incurs additional overhead.</p><p>In practice, a common approach to this problem is to use a commit log — that is, before actually writing data, first record all information about modifications in a log. If the above failure occurs, the system can recover data based on the commit log upon restart. Since writing this log is a sequential disk write operation, its performance is far better than random disk writes, so this approach has no performance issues. After the data is truly written, a marker is added to indicate that this log entry has been persisted.</p><p>Now, let’s consider whether the commit log has room for optimization. Of course it does. A major drawback of the commit log is that all actual disk operations must occur after the transaction commits. If a transaction is very large, it will occupy a significant amount of memory buffer, which also impacts system performance. The improvement is the write-ahead log (WAL) mechanism — a topic I’ve covered in a previous article (<a href="https://lichuanyang.top/posts/3914/">https://lichuanyang.top/posts/3914/</a>). WAL is very similar to the commit log — it also sequentially writes a log file, with the only difference being that WAL allows writing before the transaction commits. MySQL’s redo log is a typical implementation of write-ahead logging.</p><p>At this point, let’s pause and review the above content. You’ll notice that most of it actually discusses durability, because with these mechanisms, atomicity comes naturally — if the commit log is written, the transaction is effectively complete; if the commit log is not written, the transaction effectively never happened. However, with write-ahead logging, the situation is different: a transaction involves multiple disk writes, so atomicity is no longer guaranteed. Therefore, additional mechanisms are needed to ensure atomicity. Undo logs are a typical approach for this purpose. Before writing data changes to disk, the undo log must first be recorded, noting which data was modified, the original value, and the new value, so that during transaction rollback or crash recovery, data changes written earlier can be undone based on the undo log.</p><p>In MySQL, this is essentially what happens: redo logs and undo logs are used together to implement efficient and reliable durability and atomicity.</p><h2 id="Isolation-and-Isolation-Levels"><a href="#Isolation-and-Isolation-Levels" class="headerlink" title="Isolation and Isolation Levels"></a>Isolation and Isolation Levels</h2><p>How to implement isolation between transactions? A natural approach is locking, and this is indeed how conventional databases are implemented. Generally, there are several types of locks: read locks (also called shared locks), write locks (also called exclusive locks), and range locks.</p><p>For a given piece of data, only one transaction can hold a write lock; different transactions can simultaneously hold read locks. Once a data item has a read lock, a write lock cannot be added, and once it has a write lock, a read lock cannot be added. Range locks apply a write lock to a range, preventing data writes within that range.</p><p>We know that databases have four common isolation levels: Serializable, Repeatable Read, Read Committed, and Read Uncommitted. The difference between them is essentially the granularity of locking.</p><p>If we add all possible locks to all operations, the result is effectively serializable execution. This approach provides excellent isolation but terrible performance, so it’s generally not used.</p><p>Repeatable Read adds read and write locks to all involved data and holds them until the transaction ends, but does not add range locks. This can lead to phantom reads — if two range queries are executed within a transaction, and new data is inserted between them, the two queries may return inconsistent results.</p><p>Read Committed differs from Repeatable Read in that its read locks are released immediately after the query completes. This means that during transaction execution, data that has been read can be modified by other transactions, leading to non-repeatable reads.</p><p>Under Read Uncommitted, no read locks are added at all. The problem this causes is that read operations don’t request read locks, which actually allows reading data that has write locks from other transactions — leading to dirty reads.</p><p>Ultimately, isolation and performance are contradictory requirements. The more locks you add, the better the isolation, but the worse the performance. We need to decide the appropriate locking level based on the actual use case.</p><p>Another approach is to look for alternatives beyond locking. Considering the 80&#x2F;20 principle, can we sacrifice 20% of performance to solve 80% of the problem? Specifically, isolation problems can be simplified into two scenarios: one read transaction plus one write transaction, and two write transactions. In most cases, the read-plus-write scenario is more common, so we look for ways to solve phantom reads in that scenario. Many of you have probably already guessed — this is MVCC (Multi-Version Concurrency Control). There is abundant information about MVCC online, so I won’t go into further detail here.</p><p>After the above introduction to the various transaction properties, I believe you now have a deep understanding of local transactions. If you have any questions, feel free to leave a comment. In the next article, I will discuss distributed transactions. If you’re interested, you can follow my personal blog, Zhihu, or WeChat official account for updates~</p><p>Source: <a href="https://lichuanyang.top/en/posts/7774/">https://lichuanyang.top/en/posts/7774/</a></p>]]>
    </content>
    <id>https://lichuanyang.top/en/posts/7774/</id>
    <link href="https://lichuanyang.top/en/posts/7774/"/>
    <published>2022-02-07T08:40:02.000Z</published>
    <summary>A comprehensive exploration of database transactions and ACID properties, covering redo log, undo log, and isolation levels.</summary>
    <title>From Redo Log and Undo Log to Isolation Levels: A Deep Dive into Database Transactions and ACID</title>
    <updated>2026-06-27T03:50:56.858Z</updated>
  </entry>
  <entry>
    <author>
      <name>SandGrid</name>
    </author>
    <category term="continuous-integration" scheme="https://lichuanyang.top/en/tags/continuous-integration/"/>
    <category term="kubernetes" scheme="https://lichuanyang.top/en/tags/kubernetes/"/>
    <content>
      <![CDATA[<p>Compared with large manufacturers, many start-up companies have a very important disadvantage that their infrastructure is imperfect and they do not have a variety of complete tools. Therefore, I plan to sort out how to build a development process with a good experience based on the capabilities provided by the open source community with as little operation and maintenance and development costs as possible.</p><span id="more"></span><p>First, let’s sort out the necessary steps in the entire development process. I will briefly list them, including project creation, development, code review, deployment of test environment, deployment of grayscale and online environments, viewing online monitoring, and logs. , and troubleshooting problems, etc.</p><p>Based on this entire process chain, let’s take a look at what aspects we need to ensure in order to improve development and operation and maintenance efficiency as much as possible, and what low-cost solutions can be found in these aspects.</p><p>There are a few points:</p><ul><li>Quickly create a project and add necessary web, monitoring, log and other components;</li><li>Improve the development efficiency of some conventional code development, such as database addition, deletion, modification and query;</li><li>Simplify the process from packaging to deployment, and the test environment can be deployed automatically;</li><li>A complete set of monitoring and alarms can be easily added to each service, including common machine, JVM, interface level indicators, and business-customized indicators;</li><li>There is a place to centrally view the logs of each node;</li><li>When necessary, powerful tools can be used on online nodes to help troubleshoot problems.</li></ul><p>Next, let’s take a look at how to solve these problems respectively.</p><h2 id="Deployment-environment"><a href="#Deployment-environment" class="headerlink" title="Deployment environment"></a>Deployment environment</h2><p>First of all, in the title of this article, it is actually assumed that the deployment environment is kubernetes, so let me first talk about why kubernetes is the only option. Just flip through any introduction to kubernetes, and we can see many explanations of its advantages, so we won’t go into details here. The most critical one, and the benefit that everyone can experience personally, is that it provides a complete automatic expansion and contraction mechanism. It is very simple to set an expected value for CPU consumption, and the k8s cluster can adjust the number of pods based on the current CPU load. I compared the data before and after we enabled automatic expansion and contraction. The daily machine cost can differ by nearly 600 US dollars, accounting for more than 1&#x2F;3 of the total cost.</p><p>Since the operation and maintenance cost of k8s itself is very high, it is recommended to directly purchase the services of a cloud service provider. Both AWS and Alibaba Cloud provide very complete k8s cluster functions.</p><p>In addition, it is recommended to use kuboard (<a href="https://kuboard.cn/">https://kuboard.cn/</a>) this tool to manage k8s. kuboard is a graphical k8s management tool, including common operations such as deployment, configuration, expansion, and logging in to pods, all of which can be operated on a graphical interface and is very convenient to use.</p><h2 id="Project-Development"><a href="#Project-Development" class="headerlink" title="Project Development"></a>Project Development</h2><p>Generally, you can create a project based on spring initializr. For common configurations such as logging and monitoring, it is recommended to compile a list of best practices and create a template project. New projects can be created based on this template project, using mvn archetype or similar tools to make the process smoother.</p><p>As for some commonly used basic codes such as databases and caches, I feel that Spring Family Bucket is easy enough to use.</p><p>For code review, we can use GitLab’s webhook to automatically send notifications to the project team when the code is submitted.</p><h2 id="Packaging-Deployment"><a href="#Packaging-Deployment" class="headerlink" title="Packaging &amp; Deployment"></a>Packaging &amp; Deployment</h2><p>For springboot projects, you can use buildpacks to create docker images without having to consider the details of the docker file. For specific buildpacks, we use paketo buildpacks. Others such as cloudfoundry and heroku are similar. Currently, we have not studied the specific requirements for these packages.</p><p>Next, we can still use gitlab’s webhook to trigger a Jenkins build task. In the Jenkins build task, we can complete the operations of packaging and deploying to the kubernetes test cluster.</p><h2 id="Monitoring-Alarming"><a href="#Monitoring-Alarming" class="headerlink" title="Monitoring &amp; Alarming"></a>Monitoring &amp; Alarming</h2><p>It is recommended to use the prometheus + grafana package. Regarding the use of prometheus and the configuration under the springboot project, you can refer to my previous article h[ttps:&#x2F;&#x2F;lichuanyang.top&#x2F;posts&#x2F;28288&#x2F;](<a href="https://lichuanyang/">https://lichuanyang</a>. top&#x2F;posts&#x2F;28288&#x2F;) .</p><p>On k8s, you can install a weave cloud agent, and then configure the automatic crawling of the prometheus interface.</p><p>In grafana, you can directly write promeql configuration monitoring reports. In addition, on the Grafana official website, there are a large number of charts shared by others that can be used directly.</p><p>In grafana, you can also configure alarms with various custom rules. If you use Feishu, you can easily use Feishu as the alarm channel for grafana by configuring grafana assistant in Feishu.</p><h2 id="log"><a href="#log" class="headerlink" title="log"></a>log</h2><p>You can use loki (<a href="https://grafana.com/oss/loki/">https://grafana.com/oss/loki/</a>) as a tool for log collection and query. Loki can be considered as a lightweight ELK, and its maintenance cost will be much lower than ELK.</p><h2 id="Troubleshooting"><a href="#Troubleshooting" class="headerlink" title="Troubleshooting"></a>Troubleshooting</h2><p>For java projects, using the artifact arthas can solve most problem troubleshooting needs. For arthas access, you can use arthas springboot starter. For pods on k8s, if the environment can be connected to the office environment, you can use the port forward function of k8s to forward the arthas port to the local. Of course, if you do this, be sure to control permissions.</p><h2 id="Canary-deployment"><a href="#Canary-deployment" class="headerlink" title="Canary deployment"></a>Canary deployment</h2><p>Regarding the role and implementation of canary deployment, you can refer to my other article <a href="https://lichuanyang.top/posts/30764/">https://lichuanyang.top/posts/30764/</a></p><p>To sum up, for operation and maintenance, you only need to maintain some infrastructure such as gitlab, kuboard, prometheus, grafana, loki, etc., and they are basically tools that are relatively simple to maintain. On this basis, with the help of reasonable processes and techniques, we can achieve a very good development experience.</p><p>Original address: <a href="https://lichuanyang.top/en/posts/40964/">https://lichuanyang.top/en/posts/40964/</a></p>]]>
    </content>
    <id>https://lichuanyang.top/en/posts/40964/</id>
    <link href="https://lichuanyang.top/en/posts/40964/"/>
    <published>2022-01-26T07:19:32.000Z</published>
    <summary>Practical experience using Kubernetes in startup Java projects with low infrastructure costs, covering CI/CD, deployment, and monitoring setup.</summary>
    <title>Practical experience using kubernetes in java projects with low learning costs</title>
    <updated>2026-06-27T03:50:56.861Z</updated>
  </entry>
  <entry>
    <author>
      <name>SandGrid</name>
    </author>
    <category term="Tech Talk" scheme="https://lichuanyang.top/en/categories/Tech-Talk/"/>
    <category term="reflections" scheme="https://lichuanyang.top/en/tags/reflections/"/>
    <content>
      <![CDATA[<p>2021 was a year of upheaval for me personally. A job I had planned to pursue long-term — one with excellent comfort and promising prospects — suddenly collapsed due to certain reasons. Consequently, I faced a critical crossroads regarding my future direction. At least so far, I feel I’ve made some correct choices. I’ve also made good progress on the personal growth goals I set at the beginning of the year. So I’m approaching this year-end review with a reasonably positive mindset.</p><span id="more"></span><h2 id="Career-Review"><a href="#Career-Review" class="headerlink" title="Career Review"></a>Career Review</h2><p>The biggest change in 2021 came from work. Since the industry I was in disappeared from the spotlight overnight, my plans to develop at one company for the long haul fell through, and I was forced to restart my career planning. A key consideration was whether to join a big company or a small company. The reason I mentioned reaching a critical crossroads is that at this age, whichever path I choose, there’s essentially no turning back. The capability requirements for a senior employee at big companies versus small companies are vastly different. At this stage, you no longer have the freedom to jump between big and small companies like you could in your early career after graduation.</p><p>To make this decision, I asked myself several questions:</p><p>Would I grow faster personally at a big company compared to a small one?</p><p>Would life at a big company be more comfortable than at a small one?</p><p>Will I need another big company experience as credentials in the future?</p><p>Could I earn more income at a big company?</p><p>After careful consideration, the answer to all these questions was actually no. Personal growth depends on the individual — I’m fundamentally not someone easily influenced by my environment. Regarding work-life balance, in theory it shouldn’t have much to do with company size, it just happens that several big companies in China aren’t great at this. On the credentials front, since I already have big tech experience, plus my educational background and other prior experience, I don’t think I’d lose out in that regard. Also, based on this job search and my experience interviewing candidates over the past two years, I’ve come to believe that the substance of your projects matters more than which company you were at. As for income, it depends on the macro environment, the company’s trajectory, the boss’s style, and luck — it generally doesn’t correlate much with company size.</p><p>After all this deliberation, I realized that the only advantage of joining a big tech company was that it would look good on paper. So I made my decision: completely abandon the big company idea and instead seek out promising startup companies with good culture and outlooks. Speaking of which, I’m sometimes deeply grateful for what seems like fate’s grace — it’s as if the universe always presents me with suitable options at key moments. In reality, though, I mainly have to thank the headhunter who provided information and support throughout the process, leading me to a new company that satisfied me on every front. I’ve been at the new company for almost four months now, and during this period, the room for doing meaningful work, personal growth, WLB, and financial returns have all exceeded expectations.</p><h2 id="Life-Changes"><a href="#Life-Changes" class="headerlink" title="Life Changes"></a>Life Changes</h2><p>With the new work schedule, I also have more time with my daughter. The little girl has recently entered a language explosion phase, saying new words almost every day, and my sense of life happiness has reached a very high level.</p><h2 id="Technical-Growth"><a href="#Technical-Growth" class="headerlink" title="Technical Growth"></a>Technical Growth</h2><p>Additionally, I set some personal growth goals at the beginning of each year. At the start of 2021, my key goals were to expand and solidify my capability circle — striving to master new skills and reinforce existing ones. Specifically, this included improving technical and management skills related to my day job, enhancing business acumen and investment ability, and building some foundational skills.</p><p>On the professional front, I read Zhou Zhiming’s “Phoenix Architecture,” which played a key role in helping me build a more complete knowledge system and fill in some knowledge gaps. I studied Wang Zheng’s design patterns column on Geek Time, gained deeper understanding of design patterns, and gave a design patterns talk within my company. I also did a concentrated study of Elasticsearch, a technology I’d been using for a long time without seriously understanding.</p><p>On management, I learned some theoretical knowledge this year and have been working on applying it to practical work, with decent results.</p><p>On investments, I actually made some very mistaken attempts this year. Starting from early in the year, I tried to analyze company fundamentals myself to find suitable investment targets, reading many books and studying the experiences of notable investors. My current conclusion is that the barrier to this is extremely high — it’s unrealistic to achieve professional-level investment results through self-study alone. Based on this experience, my investment returns turned negative for the first time this year. Going forward, I’ll abandon individual stock investing and shift entirely to fund and index dollar-cost averaging. However, I won’t stop learning about business and investment knowledge, because I’ve realized that this knowledge can meaningfully transform one’s way of thinking, and the gains are well worth it.</p><p>Regarding foundational capabilities, I focused this year on cultivating what I call “clarity power.” This concept comes from the book “Cognitive Awakening” — the idea being that our fears are often rooted in “ambiguity,” meaning we haven’t clearly figured out what we’re actually facing. Once we have a clear understanding of a situation, we realize it’s not that difficult. The first time I encountered this concept, I immediately recognized that I’d always had a significant problem in this area, so I began deliberate practice. The results have been quite good — whether in work or life matters, I feel much more capable of handling things smoothly.</p><h2 id="2022-Outlook"><a href="#2022-Outlook" class="headerlink" title="2022 Outlook"></a>2022 Outlook</h2><p>In summary, this year has been a year of critical decisions for me, and I’m steadily moving toward my life goals. I’m grateful to everyone who has helped me along the way. Here’s to continuing to give it my all in 2022.</p><p>Source: <a href="https://lichuanyang.top/en/posts/2345/">https://lichuanyang.top/en/posts/2345/</a></p><h2 id="Frequently-Asked-Questions"><a href="#Frequently-Asked-Questions" class="headerlink" title="Frequently Asked Questions"></a>Frequently Asked Questions</h2><h3 id="Q-What’s-the-path-forward-for-mid-career-engineers"><a href="#Q-What’s-the-path-forward-for-mid-career-engineers" class="headerlink" title="Q: What’s the path forward for mid-career engineers?"></a>Q: What’s the path forward for mid-career engineers?</h3><p>The path doesn’t depend on company size — it depends on your capability circle. At the mid-to-late career stage, what matters more than “which company to join” is: what domain you have unique insights in, what level of technical problems you can solve, and whether your experience has crystallized into transferable methodologies. The author’s choice — letting go of big-tech prestige to seek startups with good culture and promising prospects — offers one viable path. But it’s not the only one. The key is to actively think about what you want, not passively wait for opportunities to land.</p><h3 id="Q-How-should-technical-people-respond-to-industry-changes"><a href="#Q-How-should-technical-people-respond-to-industry-changes" class="headerlink" title="Q: How should technical people respond to industry changes?"></a>Q: How should technical people respond to industry changes?</h3><p>In one phrase: expand your capability circle. An industry can vanish overnight (as the tutoring industry did for the author), but if you’ve been consistently broadening your capabilities, you gain the mobility to move across industries. Concretely: maintain technical sharpness (read seminal books like “Phoenix Architecture”), cultivate transferable foundational skills (like “clarity power”), and never anchor your sense of professional security to a single company or industry.</p><h3 id="Q-Big-company-or-startup-—-which-is-better-for-senior-engineers"><a href="#Q-Big-company-or-startup-—-which-is-better-for-senior-engineers" class="headerlink" title="Q: Big company or startup — which is better for senior engineers?"></a>Q: Big company or startup — which is better for senior engineers?</h3><p>It depends on what you need most at this stage. Big companies offer platform credentials and resource leverage, but personal growth pace and work-life balance aren’t guaranteed. Startups offer broader scope and faster decision-making, but stability is lower. The author’s conclusion after comparison: personal growth comes from yourself, not company size; if you already have big-tech experience, credentials matter less; and income depends more on the macro environment and luck than company size. The core advice: clarify your current-stage selection criteria before deciding, because at this stage of your career, there’s rarely a path back.</p>]]>
    </content>
    <id>https://lichuanyang.top/en/posts/2345/</id>
    <link href="https://lichuanyang.top/en/posts/2345/"/>
    <published>2022-01-04T13:49:57.000Z</published>
    <summary>Year-end review for 2021: career transition amid industry upheaval, progress on personal growth goals, and reflections on the future.</summary>
    <title>2021 in Upheaval — A Mid-Career Engineer's Year-End Review</title>
    <updated>2026-06-27T02:41:01.235Z</updated>
  </entry>
  <entry>
    <author>
      <name>SandGrid</name>
    </author>
    <category term="Cloud Native" scheme="https://lichuanyang.top/en/categories/Cloud-Native/"/>
    <category term="cloud-native" scheme="https://lichuanyang.top/en/tags/cloud-native/"/>
    <category term="continuous-integration" scheme="https://lichuanyang.top/en/tags/continuous-integration/"/>
    <category term="canary" scheme="https://lichuanyang.top/en/tags/canary/"/>
    <content>
      <![CDATA[<p>Canary release is actually a process well-suited for cloud-native environments, so I believe many people have the need to perform canary releases under Kubernetes. In this article, we introduce a very simple approach to canary release.</p><span id="more"></span><h2 id="What-is-Canary-Release"><a href="#What-is-Canary-Release" class="headerlink" title="What is Canary Release"></a>What is Canary Release</h2><p>First, let me introduce what a canary release is. The name “canary” originates from the fact that miners discovered canaries are very sensitive to gas. Before descending into the mine, miners would send a canary down first. If the canary stopped singing, it indicated a high concentration of gas.</p><p>In the context of systems, it means that after a release begins, a new version of the application is started first, but traffic is not switched over directly. Instead, testers perform online testing on the new version. The newly started application is our “canary.” After testing on the canary shows no issues, the production traffic is then switched to the new version.</p><p>To be more specific, a domain name can be mapped to two groups of servers — one group is the production environment, and the other is the canary environment.</p><p>If we are not deploying with Kubernetes but directly deploying to multiple servers, canary deployment is very simple — just designate one or more machines as the canary.</p><p>However, in a Kubernetes environment, since Kubernetes manages the deployment process, we need to do something different to achieve this effect.</p><h2 id="Canary-Release-Approach-in-Kubernetes"><a href="#Canary-Release-Approach-in-Kubernetes" class="headerlink" title="Canary Release Approach in Kubernetes"></a>Canary Release Approach in Kubernetes</h2><p>A very simple approach is to create two Deployments that are associated with the same Service through labels. This distributes traffic from the same Service across two groups of containers. The two Deployments can be deployed independently, allowing different versions of images to be deployed.</p><h2 id="Deployment-Configuration-Example"><a href="#Deployment-Configuration-Example" class="headerlink" title="Deployment Configuration Example"></a>Deployment Configuration Example</h2><p>For example, here are two Deployments:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Deployment</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-deployment</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">3</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">nginx</span></span><br><span class="line">  <span class="attr">template:</span></span><br><span class="line">    <span class="attr">metadata:</span></span><br><span class="line">      <span class="attr">labels:</span></span><br><span class="line">        <span class="attr">app:</span> <span class="string">nginx</span></span><br><span class="line">    <span class="attr">spec:</span></span><br><span class="line">      <span class="attr">containers:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">nginx:1.7.9</span></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-service</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx-port</span></span><br><span class="line">    <span class="attr">protocol:</span> <span class="string">TCP</span></span><br><span class="line">    <span class="attr">port:</span> <span class="number">80</span></span><br><span class="line">    <span class="attr">nodePort:</span> <span class="number">32600</span></span><br><span class="line">    <span class="attr">targetPort:</span> <span class="number">80</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">NodePort</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Deployment</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-deployment-canary</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx</span></span><br><span class="line">    <span class="attr">track:</span> <span class="string">canary</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">1</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">nginx</span></span><br><span class="line">      <span class="attr">track:</span> <span class="string">canary</span></span><br><span class="line">  <span class="attr">template:</span></span><br><span class="line">    <span class="attr">metadata:</span></span><br><span class="line">      <span class="attr">labels:</span></span><br><span class="line">        <span class="attr">app:</span> <span class="string">nginx</span></span><br><span class="line">        <span class="attr">track:</span> <span class="string">canary</span></span><br><span class="line">    <span class="attr">spec:</span></span><br><span class="line">      <span class="attr">containers:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">nginx:1.8.0</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>The Service is only configured in the first Deployment, with a selector rule of <code>app: nginx</code>. Both Deployments have the <code>app: nginx</code> label applied.</p><p>This achieves the effect we described.</p><h2 id="Limitations"><a href="#Limitations" class="headerlink" title="Limitations"></a>Limitations</h2><p>Of course, this approach only implements a very simple canary release process and cannot perform more granular routing, such as gray releases based on user information. For the same user, one request might go to the canary while the next goes to the production environment. If more fine-grained gray release rules are needed, consider using tools like Spring Cloud, Istio, etc.</p><h2 id="FAQ"><a href="#FAQ" class="headerlink" title="FAQ"></a>FAQ</h2><h3 id="Q-What’s-the-difference-between-canary-release-blue-green-deployment-and-rolling-updates"><a href="#Q-What’s-the-difference-between-canary-release-blue-green-deployment-and-rolling-updates" class="headerlink" title="Q: What’s the difference between canary release, blue-green deployment, and rolling updates?"></a>Q: What’s the difference between canary release, blue-green deployment, and rolling updates?</h3><ul><li><strong>Rolling Update</strong>: Replaces old Pods batch by batch, with the new version going live gradually. It’s the simplest, but during the brief period when old and new versions coexist, traffic is mixed, and rollback can only be done in reverse batch order.</li><li><strong>Blue-Green Deployment</strong>: Prepare a complete “green” environment with the new version, then switch all traffic once testing passes. Fast switching and fast rollback, but requires double the resources.</li><li><strong>Canary Release</strong>: Route a small portion of traffic to the new version (the canary) first, then gradually scale up after verification. The safest approach, but requires more fine-grained traffic control capabilities.</li></ul><p>These three approaches are not mutually exclusive: canary can be seen as a “gradual version” of blue-green, and they are often combined in real-world projects.</p><h3 id="Q-Can-traffic-be-precisely-controlled-with-this-dual-Deployment-approach"><a href="#Q-Can-traffic-be-precisely-controlled-with-this-dual-Deployment-approach" class="headerlink" title="Q: Can traffic be precisely controlled with this dual-Deployment approach?"></a>Q: Can traffic be precisely controlled with this dual-Deployment approach?</h3><p>Traffic percentages cannot be precisely controlled. The Service balances load via kube-proxy’s iptables&#x2F;ipvs rules, defaulting to round-robin. The traffic ratio between the two Deployments roughly approximates the replica count ratio. For example, if you deploy 3 old Pods + 1 new Pod, about 25% of traffic goes to the new version — but this is only a statistical approximation, not precise control.</p><p>If you need precise traffic percentages (e.g., strictly 10% to canary), you’ll need an Ingress Controller (such as Nginx Ingress’s canary annotations) or a Service Mesh (such as Istio’s VirtualService).</p><h3 id="Q-What-if-I-want-to-route-by-user-dimension-for-canary-releases"><a href="#Q-What-if-I-want-to-route-by-user-dimension-for-canary-releases" class="headerlink" title="Q: What if I want to route by user dimension for canary releases?"></a>Q: What if I want to route by user dimension for canary releases?</h3><p>The approach described in this article is based on <strong>traffic-percentage-based</strong> simple canary releases and cannot route by user dimension (e.g., VIP users to the new version, internal employees to the new version). If you need user-dimension routing, consider:</p><ul><li><strong>Istio &#x2F; Linkerd</strong>: Route rules based on HTTP Headers (e.g., Cookie, User-Agent), enabling “requests with userId&#x3D;xxx go to canary”</li><li><strong>Spring Cloud Gateway</strong>: Route based on request parameters at the gateway layer</li><li><strong>Nginx Ingress canary-by-header</strong>: Route to the canary service based on specific Header values</li></ul><p>Original article: <a href="https://lichuanyang.top/posts/30764/">https://lichuanyang.top/posts/30764/</a></p><hr><p>Source: <a href="https://lichuanyang.top/en/posts/30764/">https://lichuanyang.top/en/posts/30764/</a></p>]]>
    </content>
    <id>https://lichuanyang.top/en/posts/30764/</id>
    <link href="https://lichuanyang.top/en/posts/30764/"/>
    <published>2021-12-23T10:55:20.000Z</published>
    <summary>A simple approach to canary release in Kubernetes, reducing deployment risk by gradually routing traffic to new versions.</summary>
    <title>A Canary Release Approach in Kubernetes</title>
    <updated>2026-06-27T02:42:06.579Z</updated>
  </entry>
  <entry>
    <author>
      <name>SandGrid</name>
    </author>
    <category term="Cloud Native" scheme="https://lichuanyang.top/en/categories/Cloud-Native/"/>
    <category term="monitoring" scheme="https://lichuanyang.top/en/tags/monitoring/"/>
    <category term="cloud-native" scheme="https://lichuanyang.top/en/tags/cloud-native/"/>
    <category term="prometheus" scheme="https://lichuanyang.top/en/tags/prometheus/"/>
    <category term="grafana" scheme="https://lichuanyang.top/en/tags/grafana/"/>
    <category term="prometheus-tutorial" scheme="https://lichuanyang.top/en/tags/prometheus-tutorial/"/>
    <content>
      <![CDATA[<p>As the “default” monitoring system in the cloud-native ecosystem, Prometheus is gaining increasing attention. Today, we’ll write a tutorial about Prometheus’s design philosophy, examining how it uses very simple designs to support such complex functionality.</p><span id="more"></span><p>First, let’s think about what the challenges would be in building a monitoring system similar to Prometheus:</p><ul><li>Each service has different monitoring requirements. How should a monitoring system design its data model to balance ease of use and generality?</li><li>How should large volumes of data be stored?</li><li>How can various complex reports be generated?</li><li>…</li></ul><p>With these questions in mind, let’s look at how Prometheus is designed.</p><h2 id="History"><a href="#History" class="headerlink" title="History"></a>History</h2><p>Let’s start with the history. Prometheus was originally developed by SoundCloud and later donated to the open-source community. In 2016, it joined CNCF (Cloud Native Computing Foundation). Prometheus is CNCF’s second project, second only to Kubernetes. As you can imagine, Prometheus plays a vital role in the entire cloud-native ecosystem. It has gradually become the de facto standard for monitoring systems in cloud-native environments.</p><h2 id="Core-Design-Philosophy"><a href="#Core-Design-Philosophy" class="headerlink" title="Core Design Philosophy"></a>Core Design Philosophy</h2><p>For a monitoring system, there are three core problems to solve:</p><ol><li>How monitoring metrics are represented</li><li>How to collect and store metrics</li><li>How to use metrics to generate reports</li></ol><p>For these three questions, Prometheus offers very elegant solutions.</p><h2 id="Data-Model"><a href="#Data-Model" class="headerlink" title="Data Model"></a>Data Model</h2><p>Prometheus’s data model is, in short, “time-series” metric data. A metric is a measurement of data, and time-series means these metrics continuously generate data points at different time points.</p><p>Each metric has a unique name identifier and can have multiple labels set for filtering and aggregation. The format is as follows:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;metric name&gt;&#123;&lt;label name&gt;=&lt;label value&gt;, ...&#125;</span><br></pre></td></tr></table></figure><p>This way, for any business, monitoring data can be designed into a unified metric format. This allows Prometheus to keep its approach simple — it only needs to handle this one data format. At the same time, it is flexible enough to accommodate diverse business scenarios.</p><p>Prometheus provides four core metric types: counter, gauge, histogram, and summary. However, their differences only manifest on the client side and in PromQL. As of now (2021.11), different metric types don’t differ on the Prometheus server side.</p><h2 id="Data-Collection-and-Storage"><a href="#Data-Collection-and-Storage" class="headerlink" title="Data Collection and Storage"></a>Data Collection and Storage</h2><p>The Prometheus server periodically scrapes data from HTTP endpoints exposed by the services being monitored — this is a typical pull model.</p><p>Compared to the push model, the pull model has some advantages, such as making it easier to detect whether a specific node is functioning properly, and easier to debug locally. Of course, for a monitoring system, whether to use push or pull is not a fundamental issue.</p><p>Prometheus data is typical time-series data. Prometheus itself stores data on local disk. Note that local storage is not replicable and cannot form a cluster. If the local disk or node fails, storage cannot be scaled or migrated. Therefore, local storage should generally be treated only as a short-term sliding window for recent data.</p><p>Regarding persistent storage, Prometheus doesn’t actually try to solve this problem itself. Instead, it defines standard read&#x2F;write interfaces, allowing data to be stored on any third-party storage system.</p><h2 id="Generating-Reports"><a href="#Generating-Reports" class="headerlink" title="Generating Reports"></a>Generating Reports</h2><p>Prometheus defines the powerful PromQL language, which can satisfy various complex query scenarios. For details, refer to <a href="https://prometheus.io/docs/prometheus/latest/querying/basics/">https://prometheus.io/docs/prometheus/latest/querying/basics/</a></p><h2 id="Ecosystem"><a href="#Ecosystem" class="headerlink" title="Ecosystem"></a>Ecosystem</h2><p>The development of any open-source project depends on the growth of its ecosystem. Prometheus now has a very mature ecosystem. In mainstream programming languages like Java, Go, and Python, there are complete client libraries available. In Spring, you can easily add instrumentation to various components — we’ll cover this in detail in the practical section below. In Kubernetes, you can easily configure automatic scraping of Prometheus metrics from each node. With tools like Grafana, you can also configure a wide variety of dashboards and reports.</p><h2 id="Hands-On-Practice"><a href="#Hands-On-Practice" class="headerlink" title="Hands-On Practice"></a>Hands-On Practice</h2><p>In the next part of this tutorial, we’ll use a Spring Boot project as an example to see Prometheus in action.</p><p>The core idea is to use Spring Actuator to configure monitoring for a Spring Boot application and expose it in Prometheus format.</p><p>First, add the dependencies:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">implementation(&quot;org.springframework.boot:spring-boot-starter-actuator&quot;)</span><br><span class="line">implementation(&quot;io.micrometer:micrometer-registry-prometheus&quot;)</span><br></pre></td></tr></table></figure><p>Then add the Spring configuration:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">management:</span><br><span class="line">  endpoints:</span><br><span class="line">    web:</span><br><span class="line">      exposure:</span><br><span class="line">        include: &quot;prometheus&quot;</span><br><span class="line">  metrics:</span><br><span class="line">    distribution:</span><br><span class="line">      sla:</span><br><span class="line">        http:</span><br><span class="line">          server:</span><br><span class="line">            requests: &quot;100ms,150ms,250ms,500ms,1s&quot;</span><br><span class="line">      percentiles-histogram:</span><br><span class="line">        http:</span><br><span class="line">          server:</span><br><span class="line">            requests: true</span><br><span class="line">    web:</span><br><span class="line">      server:</span><br><span class="line">        request:</span><br><span class="line">          autotime:</span><br><span class="line">            enabled: true</span><br><span class="line">    export:</span><br><span class="line">      prometheus:</span><br><span class="line">        enabled: true</span><br><span class="line">    tags:</span><br><span class="line">      application: name</span><br></pre></td></tr></table></figure><p>This configuration does several things: exposes data in Prometheus format, automatically adds histogram monitoring for HTTP requests, and adds an <code>application</code> identifier that appears as a label in all metrics.</p><p>After starting the Spring Boot project and visiting the <code>/actuator/prometheus</code> path, you’ll see a large number of metrics, such as:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"># HELP executor_pool_size_threads The current number of threads in the pool</span><br><span class="line"># TYPE executor_pool_size_threads gauge</span><br><span class="line">executor_pool_size_threads&#123;application=&quot;ads-programad&quot;,name=&quot;asyncExecutor&quot;,&#125; 0.0</span><br><span class="line"># HELP tomcat_servlet_request_seconds  </span><br><span class="line"># TYPE tomcat_servlet_request_seconds summary</span><br><span class="line">tomcat_servlet_request_seconds_count&#123;application=&quot;ads-programad&quot;,name=&quot;dispatcherServlet&quot;,&#125; 1.0</span><br><span class="line">tomcat_servlet_request_seconds_sum&#123;application=&quot;ads-programad&quot;,name=&quot;dispatcherServlet&quot;,&#125; 0.0</span><br><span class="line"># HELP executor_pool_core_threads The core number of threads for the pool</span><br><span class="line"># TYPE executor_pool_core_threads gauge</span><br><span class="line">executor_pool_core_threads&#123;application=&quot;ads-programad&quot;,name=&quot;asyncExecutor&quot;,&#125; 70.0</span><br><span class="line"># HELP jvm_classes_unloaded_classes_total The total number of classes unloaded since the Java virtual machine has started execution</span><br><span class="line"># TYPE jvm_classes_unloaded_classes_total counter</span><br><span class="line">jvm_classes_unloaded_classes_total&#123;application=&quot;ads-programad&quot;,&#125; 0.0</span><br><span class="line"># HELP executor_completed_tasks_total The approximate total number of tasks that have completed execution</span><br><span class="line"># TYPE executor_completed_tasks_total counter</span><br><span class="line">executor_completed_tasks_total&#123;application=&quot;ads-programad&quot;,name=&quot;asyncExecutor&quot;,&#125; 0.0</span><br><span class="line"># HELP tomcat_threads_config_max_threads  </span><br><span class="line"># TYPE tomcat_threads_config_max_threads gauge</span><br><span class="line">tomcat_threads_config_max_threads&#123;application=&quot;ads-programad&quot;,name=&quot;http-nio-9000&quot;,&#125; 500.0</span><br><span class="line"># HELP process_cpu_usage The &quot;recent cpu usage&quot; for the Java Virtual Machine process</span><br><span class="line"># TYPE process_cpu_usage gauge</span><br><span class="line">process_cpu_usage&#123;application=&quot;ads-programad&quot;,&#125; 0.0</span><br><span class="line"># HELP tomcat_sessions_active_current_sessions  </span><br><span class="line"># TYPE tomcat_sessions_active_current_sessions gauge</span><br><span class="line">tomcat_sessions_active_current_sessions&#123;application=&quot;ads-programad&quot;,&#125; 0.0</span><br><span class="line"># HELP jvm_memory_committed_bytes The amount of memory in bytes that is committed for the Java virtual machine to use</span><br><span class="line"># TYPE jvm_memory_committed_bytes gauge</span><br><span class="line">jvm_memory_committed_bytes&#123;application=&quot;ads-programad&quot;,area=&quot;heap&quot;,id=&quot;G1 Eden Space&quot;,&#125; 3.5651584E7</span><br><span class="line">jvm_memory_committed_bytes&#123;application=&quot;ads-programad&quot;,area=&quot;heap&quot;,id=&quot;G1 Old Gen&quot;,&#125; 4.6137344E7</span><br><span class="line">jvm_memory_committed_bytes&#123;application=&quot;ads-programad&quot;,area=&quot;nonheap&quot;,id=&quot;Compressed Class Space&quot;,&#125; 5767168.0</span><br><span class="line">jvm_memory_committed_bytes&#123;application=&quot;ads-programad&quot;,area=&quot;nonheap&quot;,id=&quot;CodeHeap &#x27;non-profiled nmethods&#x27;&quot;,&#125; 8847360.0</span><br><span class="line">jvm_memory_committed_bytes&#123;application=&quot;ads-programad&quot;,area=&quot;nonheap&quot;,id=&quot;CodeHeap &#x27;non-nmethods&#x27;&quot;,&#125; 2555904.0</span><br><span class="line">jvm_memory_committed_bytes&#123;application=&quot;ads-programad&quot;,area=&quot;nonheap&quot;,id=&quot;Metaspace&quot;,&#125; 4.2287104E7</span><br><span class="line">jvm_memory_committed_bytes&#123;application=&quot;ads-programad&quot;,area=&quot;heap&quot;,id=&quot;G1 Survivor Space&quot;,&#125; 4194304.0</span><br><span class="line"># HELP tomcat_servlet_request_max_seconds  </span><br><span class="line"># TYPE tomcat_servlet_request_max_seconds gauge</span><br><span class="line">tomcat_servlet_request_max_seconds&#123;application=&quot;ads-programad&quot;,name=&quot;dispatcherServlet&quot;,&#125; 0.0</span><br><span class="line"># HELP tomcat_connections_current_connections  </span><br><span class="line"># TYPE tomcat_connections_current_connections gauge</span><br><span class="line">tomcat_connections_current_connections&#123;application=&quot;ads-programad&quot;,name=&quot;http-nio-9000&quot;,&#125; 3.0</span><br><span class="line"># HELP tomcat_sessions_active_max_sessions  </span><br><span class="line"># TYPE tomcat_sessions_active_max_sessions gauge</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>In addition to the explicitly configured HTTP monitoring, there is also a large amount of basic monitoring information such as JVM metrics and machine load.</p><p>Beyond that, monitoring for other components is also easy to add, such as thread pools, HTTP connection pools, and custom metrics. You can refer to <a href="https://github.com/lcy362/springboot-prometheus-demo">https://github.com/lcy362/springboot-prometheus-demo</a></p><p>This way, regardless of how the Spring Boot project is deployed — whether using native Java deployment, Docker deployment, or deployment on Kubernetes — it’s very easy to obtain all the monitoring metrics data.</p><h2 id="Quick-Start-Steps"><a href="#Quick-Start-Steps" class="headerlink" title="Quick Start Steps"></a>Quick Start Steps</h2><h3 id="Step-1-Install-and-configure-Prometheus"><a href="#Step-1-Install-and-configure-Prometheus" class="headerlink" title="Step 1: Install and configure Prometheus"></a>Step 1: Install and configure Prometheus</h3><p>Download and install Prometheus on your server, then edit the <code>prometheus.yml</code> configuration file to set basic parameters such as the global scrape interval and evaluation interval. Once Prometheus is running, visit <code>http://localhost:9090</code> to access the Web UI.</p><h3 id="Step-2-Configure-data-collection-targets"><a href="#Step-2-Configure-data-collection-targets" class="headerlink" title="Step 2: Configure data collection targets"></a>Step 2: Configure data collection targets</h3><p>Define the monitoring targets in the <code>scrape_configs</code> section of the configuration file, including the <code>/actuator/prometheus</code> endpoint exposed by your application. Use Spring Actuator and Micrometer to expose Spring Boot metrics in Prometheus format, and Prometheus will automatically pull data at the configured interval.</p><h3 id="Step-3-Write-PromQL-queries"><a href="#Step-3-Write-PromQL-queries" class="headerlink" title="Step 3: Write PromQL queries"></a>Step 3: Write PromQL queries</h3><p>Use Prometheus’s built-in PromQL query language to flexibly query the collected time-series data. You can filter by metric name and labels, perform aggregations, calculate rates, and more to address various monitoring analysis needs.</p><h3 id="Step-4-Connect-Grafana-for-visualization"><a href="#Step-4-Connect-Grafana-for-visualization" class="headerlink" title="Step 4: Connect Grafana for visualization"></a>Step 4: Connect Grafana for visualization</h3><p>Add Prometheus as a data source in Grafana and use Grafana’s rich panels and dashboard features to visualize monitoring data in charts and other graphical formats. You can also configure alerting rules in Grafana to receive timely notifications when metrics become abnormal.</p><p>Original article: <a href="https://lichuanyang.top/posts/28288/">https://lichuanyang.top/posts/28288/</a></p><hr><p>Source: <a href="https://lichuanyang.top/en/posts/28288/">https://lichuanyang.top/en/posts/28288/</a></p>]]>
    </content>
    <id>https://lichuanyang.top/en/posts/28288/</id>
    <link href="https://lichuanyang.top/en/posts/28288/"/>
    <published>2021-11-10T11:54:05.000Z</published>
    <summary>A Prometheus monitoring system tutorial that explains how its simple design supports complex functionality, starting from its design philosophy.</summary>
    <title>Prometheus Tutorial: Everything You Need to Know</title>
    <updated>2026-06-27T03:48:04.168Z</updated>
  </entry>
  <entry>
    <author>
      <name>SandGrid</name>
    </author>
    <category term="Java" scheme="https://lichuanyang.top/en/categories/Java/"/>
    <category term="open-source-project" scheme="https://lichuanyang.top/en/tags/open-source-project/"/>
    <content>
      <![CDATA[<p>A high-performance Java IP address-to-country lookup tool. The core approach uses binary search over a pre-loaded array of IP ranges, achieving microsecond-level latency per query.</p><span id="more"></span><h2 id="Design-Thinking"><a href="#Design-Thinking" class="headerlink" title="Design Thinking"></a>Design Thinking</h2><p>An IP address is essentially a 32-bit integer. An “IP range” is simply a continuous range of integers. Given an IP, we need to find which range it falls into — a classic <strong>interval search</strong> problem.</p><p>The most direct approach is to iterate through all IP ranges — O(n) complexity. But there are hundreds of thousands of IP ranges globally, so iteration is too slow. A better approach is to sort the starting addresses of the IP ranges into an array and use <strong>binary search</strong> — O(log n) complexity, with only about 19 comparisons needed for hundreds of thousands of ranges.</p><h2 id="Data-Source"><a href="#Data-Source" class="headerlink" title="Data Source"></a>Data Source</h2><p>IP address database data can be obtained for free from <a href="http://download.ip2location.com/lite/">IP2Location Lite</a>. Sample data format:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">16781312,JP</span><br><span class="line">16785408,CN</span><br><span class="line">16793600,JP</span><br></pre></td></tr></table></figure><p>Each line contains two fields: the IP range’s <strong>starting address</strong> (converted to an integer) and the <strong>country code</strong>.</p><p>The original data also includes the end address of each IP range, but IP2Location’s data segments are <strong>continuous and gap-free</strong> — the end address of one range is exactly the starting address of the next range minus one. Therefore, we can store only the starting addresses, with the end address naturally determined by the next range’s starting address. This optimization saves half the memory.</p><h2 id="Data-Structure"><a href="#Data-Structure" class="headerlink" title="Data Structure"></a>Data Structure</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">IpCountryLookup</span> &#123;</span><br><span class="line">    <span class="comment">// IP range starting addresses (sorted)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">long</span>[] startIps;</span><br><span class="line">    <span class="comment">// Corresponding country codes</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> String[] countryCodes;</span><br><span class="line">    <span class="comment">// Total number of ranges</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> size;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Two parallel arrays: <code>startIps[i]</code> and <code>countryCodes[i]</code> correspond one-to-one. The binary search is performed on <code>startIps</code>, and once found, the result is taken from <code>countryCodes</code>.</p><h2 id="Core-Implementation-Binary-Search"><a href="#Core-Implementation-Binary-Search" class="headerlink" title="Core Implementation: Binary Search"></a>Core Implementation: Binary Search</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> String <span class="title function_">lookup</span><span class="params">(String ip)</span> &#123;</span><br><span class="line">    <span class="type">long</span> <span class="variable">ipLong</span> <span class="operator">=</span> ipToLong(ip);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// Binary search: find the last starting address &lt;= ipLong</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = size - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (startIps[mid] &lt;= ipLong) &#123;</span><br><span class="line">            left = mid + <span class="number">1</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            right = mid - <span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// &#x27;right&#x27; is the position of the last element &lt;= ipLong</span></span><br><span class="line">    <span class="keyword">if</span> (right &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> countryCodes[right];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;Unknown&quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Convert IP string to long</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">long</span> <span class="title function_">ipToLong</span><span class="params">(String ip)</span> &#123;</span><br><span class="line">    String[] parts = ip.split(<span class="string">&quot;\\.&quot;</span>);</span><br><span class="line">    <span class="keyword">return</span> (Long.parseLong(parts[<span class="number">0</span>]) &lt;&lt; <span class="number">24</span>)</span><br><span class="line">         + (Long.parseLong(parts[<span class="number">1</span>]) &lt;&lt; <span class="number">16</span>)</span><br><span class="line">         + (Long.parseLong(parts[<span class="number">2</span>]) &lt;&lt; <span class="number">8</span>)</span><br><span class="line">         + Long.parseLong(parts[<span class="number">3</span>]);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Performance-Data"><a href="#Performance-Data" class="headerlink" title="Performance Data"></a>Performance Data</h2><ul><li><strong>Memory Usage</strong>: ~300,000 IP ranges, two arrays with ~300K elements each, total about 4 MB</li><li><strong>Query Latency</strong>: &lt; 10 microseconds per query (~19 comparisons for binary search)</li><li><strong>QPS</strong>: Easily handles millions of queries per second on a single machine</li><li><strong>Data Updates</strong>: IP database updates roughly once a month; a scheduled task automatically pulls and reloads</li></ul><h2 id="Other-Data-Source-Comparisons"><a href="#Other-Data-Source-Comparisons" class="headerlink" title="Other Data Source Comparisons"></a>Other Data Source Comparisons</h2><table><thead><tr><th>Data Source</th><th>Granularity</th><th>Free Version</th><th>Characteristics</th></tr></thead><tbody><tr><td>IP2Location Lite</td><td>Country</td><td>Yes</td><td>Clean data, uniform format</td></tr><tr><td>GeoIP2 (MaxMind)</td><td>City</td><td>Yes (limited)</td><td>High community recognition, good Java API</td></tr><tr><td>Chunzhen IP Database</td><td>ISP</td><td>Yes</td><td>Best accuracy within China</td></tr><tr><td>ipip.net</td><td>City&#x2F;ISP</td><td>No</td><td>Most accurate for China, but paid</td></tr></tbody></table><p>If you only need country-level granularity, IP2Location Lite is sufficient. For city-level accuracy, MaxMind GeoLite2 is recommended.</p><h2 id="Deployment"><a href="#Deployment" class="headerlink" title="Deployment"></a>Deployment</h2><p>The project is built on Spring Boot and supports two deployment methods:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Run directly</span></span><br><span class="line">mvn spring-boot:run</span><br><span class="line"></span><br><span class="line"><span class="comment"># Docker</span></span><br><span class="line">docker run -p 8080:8080 lcy362/ip-country</span><br></pre></td></tr></table></figure><p>Try it online: <a href="http://ip-country.lichuanyang.top/">http://ip-country.lichuanyang.top/</a></p><p>Project source: <a href="https://github.com/lcy362/ip-country">https://github.com/lcy362/ip-country</a></p><hr><p>Source: <a href="https://lichuanyang.top/en/posts/36780/">https://lichuanyang.top/en/posts/36780/</a></p><hr>]]>
    </content>
    <id>https://lichuanyang.top/en/posts/36780/</id>
    <link href="https://lichuanyang.top/en/posts/36780/"/>
    <published>2021-10-12T11:25:15.000Z</published>
    <summary>A high-performance IP address country lookup tool implemented in Java, based on IP2Location data source with scheduled updates.</summary>
    <title>Building a Simple High-Performance Java IP Address Country Lookup Tool</title>
    <updated>2026-06-27T03:51:55.445Z</updated>
  </entry>
  <entry>
    <author>
      <name>SandGrid</name>
    </author>
    <category term="Tech Talk" scheme="https://lichuanyang.top/en/categories/Tech-Talk/"/>
    <category term="iterm2" scheme="https://lichuanyang.top/en/tags/iterm2/"/>
    <category term="ssh-auto-login" scheme="https://lichuanyang.top/en/tags/ssh-auto-login/"/>
    <category term="ssh-passwordless-login" scheme="https://lichuanyang.top/en/tags/ssh-passwordless-login/"/>
    <content>
      <![CDATA[<h2 id="Pain-Point-Frequent-SSH-Logins"><a href="#Pain-Point-Frequent-SSH-Logins" class="headerlink" title="Pain Point: Frequent SSH Logins"></a>Pain Point: Frequent SSH Logins</h2><p>If you’re like me and frequently need to access different remote servers, recording server IPs and entering passwords can be very tedious. Fortunately, by making some configurations in iTerm2, this pain point can be well addressed. The final result is similar to configuring SSH bookmarks, enabling iTerm2 to remember SSH passwords and achieve passwordless and automatic login.</p><span id="more"></span><h2 id="Complete-Configuration-Steps"><a href="#Complete-Configuration-Steps" class="headerlink" title="Complete Configuration Steps"></a>Complete Configuration Steps</h2><p>iTerm2 (<a href="https://iterm2.com/">https://iterm2.com/</a>) is a widely used terminal alternative on Mac, providing many powerful features. To achieve SSH bookmarks with passwordless and automatic login, the key lies in three features: profile, trigger, and password manager.</p><p>Profile, as the name suggests, is a set of configurations. When we normally open iTerm2, we’re actually opening the default profile. The entry point for configuring profiles is under the Profiles option in the toolbar, where you can add or edit existing profiles. Change the “Command” section under the General tab of the desired profile to “Command”, and fill in the SSH command, such as <code>ssh root@1.1.1.1</code>, so that the SSH command is automatically executed when the profile is opened. Other text, color, and other configurations in the profile are not critical and can be filled in as needed.</p><p>Trigger is also a feature of the profile. The entry point is under the Advanced tab in the profile configuration page. Its purpose is to use a keyword to trigger an action. What we need to do now is use the keyword “password” to trigger opening the password manager. The operation is simple: add a trigger, fill in “password” for the Regular Expression, select “Open Password Manager” for the Action, and make sure to check both the “Instant” and “Enabled” options.</p><p>The last thing to configure is the password manager. The password manager is a password management tool that comes as a default plugin in iTerm2. Its entry point is under the Window tab in the toolbar. Open the password manager and enter the passwords you want to save.</p><h2 id="Security-Reminder"><a href="#Security-Reminder" class="headerlink" title="Security Reminder"></a>Security Reminder</h2><p>This way, we’ve implemented “bookmarks” in iTerm2 to save remote server addresses and passwords. When using them, simply access the corresponding profile, wait for the password manager to pop up, select the corresponding password record, and click to input it.</p><p>Source: <a href="https://lichuanyang.top/en/posts/20763/">https://lichuanyang.top/en/posts/20763/</a></p><hr><h2 id="Quick-Start-Guide"><a href="#Quick-Start-Guide" class="headerlink" title="Quick Start Guide"></a>Quick Start Guide</h2><h3 id="Step-1-Generate-SSH-Key"><a href="#Step-1-Generate-SSH-Key" class="headerlink" title="Step 1: Generate SSH Key"></a>Step 1: Generate SSH Key</h3><p>Run <code>ssh-keygen</code> in the terminal to generate an SSH key pair. Add the public key to the target server’s <code>~/.ssh/authorized_keys</code> to ensure the passwordless login foundation is set up.</p><h3 id="Step-2-Configure-iTerm2-Profile"><a href="#Step-2-Configure-iTerm2-Profile" class="headerlink" title="Step 2: Configure iTerm2 Profile"></a>Step 2: Configure iTerm2 Profile</h3><p>Open iTerm2, go to Profiles → Open Profiles → Edit Profiles. In the General tab, set the Command to an SSH command in the format <code>ssh root@1.1.1.1</code>.</p><h3 id="Step-3-Add-Bookmarks"><a href="#Step-3-Add-Bookmarks" class="headerlink" title="Step 3: Add Bookmarks"></a>Step 3: Add Bookmarks</h3><p>In the Profile editor’s Advanced tab, add a Trigger: enter <code>password</code> for Regular Expression, select Open Password Manager for Action, and check Instant and Enabled. Then go to Window → Password Manager to store server passwords.</p><h3 id="Step-4-Verify-Auto-Login"><a href="#Step-4-Verify-Auto-Login" class="headerlink" title="Step 4: Verify Auto Login"></a>Step 4: Verify Auto Login</h3><p>Select the configured Profile from the Profiles menu, wait for the Password Manager to pop up, select the corresponding password record, and confirm that SSH login completes automatically.</p>]]>
    </content>
    <id>https://lichuanyang.top/en/posts/20763/</id>
    <link href="https://lichuanyang.top/en/posts/20763/"/>
    <published>2021-10-08T08:45:42.000Z</published>
    <summary>Configure SSH bookmarks in iTerm2 to save passwords and enable auto login, eliminating the pain of repeatedly entering IP addresses and passwords.</summary>
    <title>Configuring SSH Bookmarks in iTerm2 for Password Storage and Auto Login</title>
    <updated>2026-06-27T02:20:10.608Z</updated>
  </entry>
  <entry>
    <author>
      <name>SandGrid</name>
    </author>
    <category term="Tech Talk" scheme="https://lichuanyang.top/en/categories/Tech-Talk/"/>
    <category term="tech-talks" scheme="https://lichuanyang.top/en/tags/tech-talks/"/>
    <category term="tech-talk-topics" scheme="https://lichuanyang.top/en/tags/tech-talk-topics/"/>
    <content>
      <![CDATA[<p>Tech talks exist in nearly every company. The effectiveness varies from person to person and team to team. In some teams, tech talks gradually become a useless time killer due to various reasons.</p><p>This article aims to improve the baseline of tech talks through some fundamental rules, starting from topic selection and other aspects, striving to help 80% of people deliver a tech talk that scores above 80 points.</p><span id="more"></span><h2 id="Topic-Selection"><a href="#Topic-Selection" class="headerlink" title="Topic Selection"></a>Topic Selection</h2><h3 id="Basic-Principles"><a href="#Basic-Principles" class="headerlink" title="Basic Principles"></a><strong>Basic Principles</strong></h3><p>First, you need to understand that in-person tech talks are not the only way to share knowledge. Other forms include writing blog posts and articles, making videos, or simply sharing a sentence or two — all are common sharing formats. For different types of knowledge, you should consider which format is more appropriate.</p><p>For example, for an introductory introduction to a new technology, writing an article is recommended. Readers can quickly scan or search for key information, which is much more efficient than sitting down to listen to a presentation.</p><h3 id="Recommended-Topics"><a href="#Recommended-Topics" class="headerlink" title="Recommended Topics"></a><strong>Recommended Topics</strong></h3><p>Generally speaking, good topics should have strong practical value, be able to attract the audience, and have an appropriate level of difficulty. They should strike a balance between being understandable and providing new insights for the audience.</p><p>Here are some examples of good topics and some things to keep in mind:</p><ul><li>For introducing new technologies, you need to clearly explain the background of why the new technology was created, what problems it solves, its advantages and disadvantages compared to previous similar solutions, and what new problems it introduces, etc.</li><li>For explaining obscure or difficult technologies, the topic scope can be smaller. You need solid presentation skills based on a thorough understanding of the problem to ensure everyone can follow along.</li><li>In-depth technical discussions on specific details are similar to the previous category — pay attention to background introduction.</li><li>For high-level summaries of a category of problems, such as cache consistency, distributed IDs, distributed transactions, etc., these topics can start from real existing problems, analyze the pros and cons of different solutions, and discuss the evolution of technical approaches.</li><li>Business-oriented talks are best when choosing common business domains, and pay attention to background introduction.</li></ul><h2 id="Content-Preparation"><a href="#Content-Preparation" class="headerlink" title="Content Preparation"></a>Content Preparation</h2><p>For talk effectiveness, while it will be influenced by various factors, the most important thing at its core is whether the content has enough substance.</p><p>To have substance, the best approach is consistent daily accumulation. I recommend thinking more in your daily work and building deeper understanding of the things you use. I recommend reading the book “How to Take Smart Notes” — you need continuous input in daily life to produce output.</p><p>Additionally:</p><p>During preparation, think and summarize to form your own viewpoints. Simply rehashing others’ material actually wastes your own time.</p><p>Try to incorporate real application scenarios into your talk content.</p><p>For difficult knowledge points, pay attention to presentation skills to make sure the audience can understand. Consider doing a dry run with one or two colleagues first on the key parts of your talk.</p><p>Watching videos can help you not only learn the content itself but also see how others present, which is quite helpful for improving your talk’s effectiveness.</p><p>Beyond the actual content, I recommend preparing the following:</p><p>Pre-study materials. You can prepare materials in advance that help understand the talk content, such as certain chapters from a book or specific articles, and ask everyone to read them beforehand. The speaker needs to provide estimated reading time for each material — the total reading time for a single talk should not exceed one hour.</p><p>Detailed explanation of background, especially when business knowledge or uncommon scenarios are involved.</p><h2 id="Talk-Process"><a href="#Talk-Process" class="headerlink" title="Talk Process"></a>Talk Process</h2><p>After preparing the talk content, you need to find someone to review it. The reviewer can be your mentor, leader, or someone else very familiar with the relevant field.</p><p>The reviewer, based on a thorough understanding of the talk content, mainly reviews the topic selection and content to ensure they meet the requirements listed in this article. They should also try to ensure the content doesn’t contain technical errors.</p><p>The talk materials must be sent out in advance to give the audience a chance to familiarize themselves with them.</p><p>During the talk, pay attention to pacing — don’t speak too fast.</p><p>Original article: <a href="https://lichuanyang.top/posts/54216/">https://lichuanyang.top/posts/54216/</a></p><hr><p>Source: <a href="https://lichuanyang.top/en/posts/54216/">https://lichuanyang.top/en/posts/54216/</a></p>]]>
    </content>
    <id>https://lichuanyang.top/en/posts/54216/</id>
    <link href="https://lichuanyang.top/en/posts/54216/"/>
    <published>2021-08-31T06:27:43.000Z</published>
    <summary>A systematic guide on topic selection, structure, and presentation skills to deliver a tech talk that scores above 80 points.</summary>
    <title>How to Give a Good Tech Talk</title>
    <updated>2026-06-08T07:50:49.099Z</updated>
  </entry>
  <entry>
    <author>
      <name>SandGrid</name>
    </author>
    <category term="Cloud Native" scheme="https://lichuanyang.top/en/categories/Cloud-Native/"/>
    <category term="microservices" scheme="https://lichuanyang.top/en/tags/microservices/"/>
    <category term="cloud-native-architecture" scheme="https://lichuanyang.top/en/tags/cloud-native-architecture/"/>
    <content>
      <![CDATA[<h2 id="Definition-of-Cloud-Native"><a href="#Definition-of-Cloud-Native" class="headerlink" title="Definition of Cloud Native"></a>Definition of Cloud Native</h2><p>In recent years, cloud native has become an increasingly popular concept across the open-source community. But what exactly is cloud native? Is it an architecture? A platform? What does it affect? System security? Development efficiency? So today, let’s dig deep and sort out what cloud native really is.</p><span id="more"></span><p>To understand what cloud native is, we need to start with its name. The English name for cloud native is “cloud native,” which obviously contains two parts: cloud and native. Cloud means the application runs in the cloud, not locally. Native means the application should run in the way best suited for the cloud, not just be migrated from local to the cloud.</p><p>So what kind of application is suited for the cloud? It’s one that maximizes the use of cloud capabilities and leverages the advantages of the cloud.</p><p>The core advantage of cloud computing is essentially just centralizing more resources for unified management and allocation, making it more convenient to flexibly allocate resources on demand and improve resource utilization.</p><p>By analogy, many people have used streaming frameworks like Storm. What are their advantages? One important factor is the ability to break down a complex workflow into multiple sub-nodes, where each node can be configured with different levels of concurrency based on its needs. Nodes with higher concurrency demands can receive more resources. This way, resource utilization is improved.</p><h2 id="Microservices"><a href="#Microservices" class="headerlink" title="Microservices"></a>Microservices</h2><p>For microservices, it’s similar — splitting different functions into separate services allows independent scaling at a finer granularity.</p><p>It’s worth noting that splitting includes not only separating different business domains, but also separating business code, third-party software (third-party libraries), and non-functional features (high availability, security, observability, etc.) into three distinct categories.</p><p>Pure business splitting has actually been happening since very early stages of software development. The trend accompanying the rise of cloud native is to maximize the separation of non-business code portions from cloud applications, allowing cloud infrastructure to take over the many non-functional features originally in applications (such as elasticity, resilience, security, observability, canary releases, etc.) — this is the so-called service mesh.</p><p>Since resources and applications in the cloud are not strongly bound, to make resource utilization more convenient, we need a more universal runtime format that allows applications to have a certain degree of decoupling from their runtime environment. </p><h2 id="Containerization"><a href="#Containerization" class="headerlink" title="Containerization"></a>Containerization</h2><p>This is container technology. Containers provide a logical packaging mechanism. Applications packaged with this mechanism can operate independently of their actual runtime environment. Using this decoupling, regardless of whether the target environment is a private data center, public cloud, or a developer’s personal laptop, you can easily and consistently deploy container-based applications. Containerization makes the concerns of developers and IT operations teams distinct — developers focus on application logic and dependencies, while IT operations teams can focus on deployment and management without being distracted by specific software versions and application-specific configurations.</p><h2 id="Observability"><a href="#Observability" class="headerlink" title="Observability"></a>Observability</h2><p>On the other hand, after splitting services into finer granularity, the system’s inherent complexity obviously increases. For example, local calls become network requests, and call chains cannot be reflected through code structure. Therefore, operations need to be more intelligent and automated to ensure stronger stability of individual services. At the same time, a powerful monitoring system is needed that can analyze dependencies between microservices and quickly detect anomalies in the system.</p><p>Furthermore, with smaller individual services and comprehensive monitoring data, we can deploy more frequently, even deploying directly to production after each change. If a deployment has issues, we can detect them promptly through monitoring, thereby controlling losses to a minimum. Small-scale deployments also make it easier to locate problems or roll back.</p><p>From the analysis above, we can organize some keywords related to cloud native, such as service-oriented, elastic, observable, resilient, automated, etc. These keywords can be summarized into four categories: microservices, DevOps, continuous delivery, and containerization.</p><p>The key characteristics of these four categories are:</p><p>Microservices: Can be independently deployed, updated, restarted, and scaled</p><p>DevOps: Automated, rapid, development-operations collaboration</p><p>Continuous delivery: Frequent releases, fast feedback</p><p>Containerization: Logical packaging mechanism</p><h2 id="Cloud-Native-Mindset"><a href="#Cloud-Native-Mindset" class="headerlink" title="Cloud Native Mindset"></a>Cloud Native Mindset</h2><p>We’ve covered a lot of theory above. So what are the concrete implementation paths for adopting cloud native? We can consider the following aspects:</p><ol><li>Business service splitting: This is a very fundamental thing in software development. Splitting needs to follow basic design principles like SOLID.</li><li>Comprehensive monitoring system: Including collecting information across log, trace, metric, and alert dimensions. Logs focus on recording information during code execution, traces are mainly for tracking the flow of the same request across different services, metrics are for monitoring system runtime status, and alerts are for anomaly notifications. The industry already has many open-source implementations, such as Prometheus, Jaeger, etc.</li><li>Containers and container orchestration: This basically means Docker and K8s.</li><li>Middleware mesh化: Business applications only retain a thin client layer, with the main logic in the middleware placed at the mesh layer.</li><li>DevOps and continuous delivery: This mainly involves development workflows and many process-related aspects of development-operations collaboration. In a cloud environment, we advocate a pattern of small batches, frequent releases, and fast feedback.</li></ol><p>I am Liu Sha. I hope this article can help everyone better understand what exactly cloud native is. Actually, cloud native is simple to describe — it’s about adopting various approaches to better utilize cloud resources. But when explained in detail, it’s a very comprehensive system covering everything from development to operations. Welcome to follow my WeChat public account (Mobility), or visit my <a href="https://lichuanyang.top/">personal website</a>. I will gradually expand on all aspects of cloud native in future articles.</p><h2 id="FAQ"><a href="#FAQ" class="headerlink" title="FAQ"></a>FAQ</h2><h3 id="Q-Is-cloud-native-just-about-adopting-Kubernetes"><a href="#Q-Is-cloud-native-just-about-adopting-Kubernetes" class="headerlink" title="Q: Is cloud native just about adopting Kubernetes?"></a>Q: Is cloud native just about adopting Kubernetes?</h3><p>No. Kubernetes is the core container orchestration tool in the cloud native ecosystem, but cloud native goes far beyond K8s. Cloud native is a comprehensive methodology covering microservices architecture, containerization, DevOps, continuous delivery, observability, and more. Running on K8s doesn’t automatically mean you’re “cloud native” — if your application is still a monolithic blob, with no automated CI&#x2F;CD, no monitoring or alerting, then you’re simply “running a traditional application on K8s.”</p><h3 id="Q-Is-it-necessary-to-migrate-legacy-applications-to-cloud-native"><a href="#Q-Is-it-necessary-to-migrate-legacy-applications-to-cloud-native" class="headerlink" title="Q: Is it necessary to migrate legacy applications to cloud native?"></a>Q: Is it necessary to migrate legacy applications to cloud native?</h3><p>It depends on the context. If the application is small in scale, has a low iteration frequency, and a small team, forcing a full cloud native adoption will only add complexity. But if the application needs frequent iteration, elastic scaling, high availability, or the team is large enough to warrant microservice decomposition — that’s when cloud native delivers value. In short: don’t adopt cloud native for its own sake; adopt it to solve real problems.</p><h3 id="Q-What’s-the-difference-between-containerization-and-virtualization"><a href="#Q-What’s-the-difference-between-containerization-and-virtualization" class="headerlink" title="Q: What’s the difference between containerization and virtualization?"></a>Q: What’s the difference between containerization and virtualization?</h3><p>Virtual machines (VMs) virtualize at the hardware layer — each VM has its own OS kernel, with slow startup and significant resource overhead. Containers virtualize at the OS layer — all containers share the host kernel, isolating only the application and its dependencies, with fast startup (seconds) and minimal resource footprint. A simple analogy: VMs are like “each household has its own kitchen in an apartment building,” while containers are like “sharing one large kitchen, each person having their own stove.”</p><h3 id="Q-What’s-the-relationship-between-microservices-and-cloud-native"><a href="#Q-What’s-the-relationship-between-microservices-and-cloud-native" class="headerlink" title="Q: What’s the relationship between microservices and cloud native?"></a>Q: What’s the relationship between microservices and cloud native?</h3><p>Microservices are a core component of cloud native architecture, but not the whole picture. Microservices address “how to split applications for independent deployment and scaling,” while cloud native also encompasses “how these microservices run” (containers and orchestration), “how they are delivered” (CI&#x2F;CD), “how they are monitored” (observability), “how they communicate” (Service Mesh), and more. Think of microservices as the “business layer” of cloud native architecture.</p><p>Original article: <a href="https://lichuanyang.top/posts/42843/">https://lichuanyang.top/posts/42843/</a></p>]]>
    </content>
    <id>https://lichuanyang.top/en/posts/42843/</id>
    <link href="https://lichuanyang.top/en/posts/42843/"/>
    <published>2021-06-09T11:54:37.000Z</published>
    <summary>Starting from the literal meaning of Cloud Native, systematically organizing the definition, core concepts, and technology ecosystem of cloud native.</summary>
    <title>What Exactly is Cloud Native?</title>
    <updated>2026-06-27T03:50:56.855Z</updated>
  </entry>
  <entry>
    <author>
      <name>SandGrid</name>
    </author>
    <category term="Tech Miscellany" scheme="https://lichuanyang.top/en/categories/Tech-Miscellany/"/>
    <category term="personal-growth" scheme="https://lichuanyang.top/en/tags/personal-growth/"/>
    <category term="book-notes" scheme="https://lichuanyang.top/en/tags/book-notes/"/>
    <content>
      <![CDATA[<p>“Amoeba Management” is a classic book about work attitudes and work methods. In today’s world of severe labor-capital conflicts and increasingly rigid class structures, it’s difficult to fully practice the philosophy of the book’s author, Kazuo Inamori. But this doesn’t mean the book has no value. Working hard and enhancing one’s own value has always been a spiritual pursuit hidden deep within the human heart. What we need to do is ensure that the returns from value enhancement go to ourselves.</p><span id="more"></span><p>Before reading this book, I strongly recommend carefully thinking about what your relationship with work actually is. If you’re the company’s owner, there’s nothing more to say — the company’s returns are yours. If you’re not, then you need to figure out where the value you create goes: does it result in financial returns, capability improvement, credentials that can serve as proof of your abilities in the future, or does it just help your boss buy two more sports cars?</p><p>I previously read an article that suggested treating yourself as a company to manage, and treating your employer as your client. I think this is an excellent way of thinking. You need to consider whether your “company” should pursue cash income immediately, or first hone your capabilities and reputation in anticipation of greater returns in the future. You also need to consider whether the transaction with your employer is fair.</p><p>Once you’ve thought through these things, then come read “Amoeba Management.” Otherwise, you might think this book was written by Teacher Ma. Speaking of which, Teacher Ma did practice many of the book’s concepts when he was an English teacher. Without the hard work during those “spare” hours back then, where would today’s blessings come from?</p><p>Alright, let’s get to the main topic. I’ll discuss the work-related values conveyed in the book from three aspects: the meaning of work, how to approach work, and work methods and attitudes.</p><h2 id="The-Meaning-of-Work"><a href="#The-Meaning-of-Work" class="headerlink" title="The Meaning of Work"></a>The Meaning of Work</h2><p>As I said at the beginning of this article, working hard and creating value is actually a very important spiritual pursuit hidden deep within the human heart. We can see that many people who have achieved financial freedom still seek work opportunities — either starting businesses or finding comfortable and free work. This is the deep-seated spiritual drive that motivates human behavior.</p><p>Beyond the financial returns obtained through work,</p><blockquote><p>Working wholeheartedly and striving for excellence is itself a practice of character cultivation that promotes our growth. Labor can bring a sense of joy, help people understand the meaning of life, and labor is a noble act.</p></blockquote><p>If you can accept the premise of “treating yourself as a company to manage,” you’ll find that immediate financial returns are actually not that important. Personal growth and the happiness brought by work are more meaningful rewards, and after achieving these, financial returns will naturally follow.</p><h2 id="How-to-Approach-Work"><a href="#How-to-Approach-Work" class="headerlink" title="How to Approach Work"></a>How to Approach Work</h2><blockquote><p>Most beginners have to start with work they don’t like.</p></blockquote><p>In this world, the number of people who truly turn their hobbies into work is inherently small. Among that small group, the vast majority will feel after working that it’s no longer their hobby. Because the difference between something as a hobby and as a work is enormous. Many people love gaming, and many envy professional esports players, thinking they’re doing what they love for a living. But if you pay attention to the esports scene, you’ll find plenty of people who don’t train properly, and some take every opportunity to play other games. They originally became professional players because they loved the game, only to find themselves gradually losing interest.</p><p>So, doing work you don’t like is the norm in this world. What we need to do is learn to love the work itself. The author provides some methods for how to make yourself love your work.</p><blockquote><p>Only through effort can you come to love your work.<br>Delight in small successes at work, use the energy they bring as motivation, and work even harder.</p></blockquote><h2 id="Work-Methods-and-Attitudes"><a href="#Work-Methods-and-Attitudes" class="headerlink" title="Work Methods and Attitudes"></a>Work Methods and Attitudes</h2><p>First, use high goals as motivation.</p><blockquote><p>Use “future tense” when assessing capabilities</p></blockquote><p>Imagine this: if you placed yourself from some time ago into the present moment, wouldn’t you find what you’re doing now difficult? This is a normal phenomenon. When you’re on a normal development path, capability improvement happens不知不觉.</p><p>With high goals, you need to continuously put in effort and think about how to achieve the goals.</p><blockquote><p>You must think. Without serious thought, nothing can be achieved.</p></blockquote><p>Only you yourself can be responsible for your own growth — relying on anyone else is unrealistic. Recently, I’ve been reading complaints on Maimai, and many people’s dissatisfaction with their companies is “no growth.” This confuses me — what exactly is the relationship between growth and a company? This goes back to the relationship between people and companies. A company is your client — you complete tasks and receive compensation. What does this have to do with personal growth? Growth requires your own reflection, not waiting for someone to assign it to you, saying “do these things and you’ll grow.” The only scenario I can think of where someone at a company gains nothing is if they already understand every aspect of the company and could easily start another company to defeat it.</p><p>Additionally, strive for perfectionism.</p><blockquote><p>Erasers can never completely erase mistakes</p></blockquote><p>Many mistakes, even if you compensate for them afterward, will inevitably leave some traces. Whatever you do, try your best to make it perfect — at least within the scope of your own understanding. For example, when developing a feature, think through the entire process to ensure there are no logical flaws or implementation bugs, rather than just looking at the product spec once and implementing it simply, leaving everything else to the testers.</p><blockquote><p>Work results &#x3D; Thinking × Passion × Ability</p></blockquote><p>At the end of the book, the author provides a formula. For work, thinking and passion are just as important as ability. And given this understanding, improving passion and changing your thinking is far easier than improving ability. So, wanting to improve work effectiveness isn’t actually that difficult — the key is to build a reasonable understanding of work.</p><p>I am Liusha, thank you for reading this article. If you found it helpful, please give it a like and follow. If you have questions, you can also reach me on my <a href="https://lichuanyang.top/">personal blog</a> or WeChat public account (Mobility).</p><h2 id="Frequently-Asked-Questions"><a href="#Frequently-Asked-Questions" class="headerlink" title="Frequently Asked Questions"></a>Frequently Asked Questions</h2><h3 id="Q-Should-programmers-read-a-book-like-this"><a href="#Q-Should-programmers-read-a-book-like-this" class="headerlink" title="Q: Should programmers read a book like this?"></a>Q: Should programmers read a book like this?</h3><p>Yes, but I recommend approaching it with a critical mindset rather than accepting everything at face value. The book’s core value lies in getting you to first think through the question: “What is my relationship with work?” — are you working for the boss, or are you building skills and reputation for yourself? Once you’ve sorted that out, the book’s ideas about passion, perfectionism, and sustained effort become genuinely informative. I suggest pairing it with the mental framework of “treating yourself as a company” for the best results.</p><h3 id="Q-What-is-work-for"><a href="#Q-What-is-work-for" class="headerlink" title="Q: What is work for?"></a>Q: What is work for?</h3><p>It’s both an economic source and a spiritual pursuit. The financial side is obvious, but more importantly, the drive to create value is deeply embedded in human nature — many financially independent people still choose to work. The book’s answer is direct: work is not just a means of earning compensation, but a practice of character cultivation, a source of joy, and a way to understand the meaning of life. If you can’t fully embrace this view yet, start with “run yourself as a company” — at least make sure what you produce at work translates concretely into your own accumulation.</p><h3 id="Q-How-do-you-learn-to-enjoy-work-you-don’t-like"><a href="#Q-How-do-you-learn-to-enjoy-work-you-don’t-like" class="headerlink" title="Q: How do you learn to enjoy work you don’t like?"></a>Q: How do you learn to enjoy work you don’t like?</h3><p>Doing work you don’t like is the norm — very few people truly turn a hobby into a career, and even those who do often find the hobby losing its charm once it becomes work. The book offers two practical approaches: first, <strong>put in effort first</strong> — the positive feedback from your effort gradually builds engagement; second, <strong>rejoice in small successes at work</strong> — let every “I figured it out” moment fuel the energy to keep going. This isn’t about motivational platitudes — it’s about building a positive feedback loop through action.</p>]]>
    </content>
    <id>https://lichuanyang.top/en/posts/3513/</id>
    <link href="https://lichuanyang.top/en/posts/3513/"/>
    <published>2021-05-15T09:29:34.000Z</published>
    <summary>Book notes on Kazuo Inamori's 'Amoeba Management', reflecting on work attitudes and methods, exploring how to achieve personal value enhancement through work.</summary>
    <title>Book Notes: Kazuo Inamori's 'Amoeba Management' — Reflecting on How We Should Work</title>
    <updated>2026-06-27T02:41:13.961Z</updated>
  </entry>
  <entry>
    <author>
      <name>SandGrid</name>
    </author>
    <category term="Tech Talk" scheme="https://lichuanyang.top/en/categories/Tech-Talk/"/>
    <category term="development-practices" scheme="https://lichuanyang.top/en/tags/development-practices/"/>
    <content>
      <![CDATA[<h2 id="Why-Code-Review-is-Needed"><a href="#Why-Code-Review-is-Needed" class="headerlink" title="Why Code Review is Needed"></a>Why Code Review is Needed</h2><p>In a typical development workflow, there are various review stages — product spec reviews, design reviews, test case reviews, code reviews, and so on. To conduct these reviews, meetings are inevitably introduced. Some people might think these activities are useless and a waste of time. In reality, they are extremely important. If you have the impression that reviews don’t matter, it’s likely due to one of two reasons: either a lack of understanding, or poor review practices in your environment.</p><span id="more"></span><h2 id="Value-of-Review-for-the-Team"><a href="#Value-of-Review-for-the-Team" class="headerlink" title="Value of Review for the Team"></a>Value of Review for the Team</h2><p>Regarding the value of reviews, the most significant impact for a team is raising the team’s baseline. Imagine if no reviews were conducted at any stage — a product spec is handed off, developers just start coding, and any individual oversight could bring endless risks to the entire project. Multiple review stages essentially describe a business solution in different forms — product specs, code, test cases — and then involve more people to inspect each form, preventing individual oversights from creating business risks.</p><p>Furthermore, the focus of this article is to discuss the value of participating in reviews for an individual, beyond the benefits it brings to the team.</p><h2 id="Value-of-Review-for-the-Reviewee"><a href="#Value-of-Review-for-the-Reviewee" class="headerlink" title="Value of Review for the Reviewee"></a>Value of Review for the Reviewee</h2><p>First, for the person being reviewed — the author of the product spec, code, or other deliverables — a review is essentially a process where others help you improve. It’s a rare opportunity to receive direct feedback. For many people, compared to their student days, the biggest challenge of learning during their career is that no one “grades” their homework. After reading books or watching videos, there’s no way to confirm whether they’ve truly understood the material. Timely feedback, regardless of what you’re learning, is crucial. Among the reviewers will be your superiors and more capable colleagues. If they can thoroughly understand your solution or code, they can certainly provide valuable feedback. A review essentially gives you the opportunity to receive their feedback.</p><p>This implicitly requires the submitter to express their solution clearly, so others can participate in the review with minimal effort. For example, when writing code, use appropriate design patterns and write readable code. When writing documents, have a well-organized layout with proper sections and highlight key points. When presenting a solution to others, consider the audience’s knowledge background to ensure they can understand your proposal.</p><p>Another important aspect of reviews for the submitter is risk and responsibility sharing. Regardless of your company’s policies or your position, when a project you’re responsible for doesn’t go well, a significant portion of the consequences will ultimately fall on you. This may come as direct performance penalties, or as others questioning your capabilities — either way, it’s something nobody wants to face. Reviews transfer some of these consequences to the entire team. Of course, don’t think too much — the primary responsibility still remains with the submitter. This again highlights the importance of clear expression. The submitter needs to ensure that the key points of their solution are effectively communicated to the reviewers. If the reviewers successfully absorb this information, have thorough discussions during the review, and reach a consensus, that part of the decision can be considered as jointly made by the team.</p><h2 id="Value-of-Review-for-the-Reviewer"><a href="#Value-of-Review-for-the-Reviewer" class="headerlink" title="Value of Review for the Reviewer"></a>Value of Review for the Reviewer</h2><p>Now, let’s look at the value for reviewers — what does actively participating in others’ reviews offer? The primary benefit is a learning opportunity. As the saying goes, “When three people walk together, there must be one who can teach me.” From others’ solutions, you can certainly discover excellent points worth learning. When you find that someone else’s approach differs from your own expectations, it’s an opportunity for deep thinking — you can continuously question yourself to determine which approach is better until you reach an irrefutable conclusion. This kind of deep thinking significantly improves your own thinking methods.</p><p>Additionally, participating in reviews is also a way to rapidly gain experience. Reviewing is essentially spending review time to achieve an effect nearly identical to doing the work yourself. If you review thoroughly enough, you’ll gain a comprehensive understanding of how something is done, and this experience can fully count as part of your project experience.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>This article comes to an end here. I hope you’ve gained an understanding of why reviews should be done and how to conduct them. Feel free to reach out to me through my WeChat official account (Mobility) or <a href="https://lichuanyang.top/">personal website</a>.</p><p>Source: <a href="https://lichuanyang.top/en/posts/57205/">https://lichuanyang.top/en/posts/57205/</a></p>]]>
    </content>
    <id>https://lichuanyang.top/en/posts/57205/</id>
    <link href="https://lichuanyang.top/en/posts/57205/"/>
    <published>2021-04-27T10:07:11.000Z</published>
    <summary>Analyzing the value of code review from the perspective of personal growth — not just about finding bugs, but a key means of team knowledge sharing and code quality improvement.</summary>
    <title>The Personal Value of Code Review</title>
    <updated>2026-06-27T01:10:12.390Z</updated>
  </entry>
  <entry>
    <author>
      <name>SandGrid</name>
    </author>
    <category term="Tech Talk" scheme="https://lichuanyang.top/en/categories/Tech-Talk/"/>
    <category term="career-development" scheme="https://lichuanyang.top/en/tags/career-development/"/>
    <content>
      <![CDATA[<p>Today I want to talk about career choices for fresh graduates, primarily focused on backend development (the topic of role selection is also quite broad, and I may write a separate article about it if I get the chance). Over the past few years, I’ve worked in state-owned enterprises, large companies, and small companies, so I feel I have some real perspective on what different companies are like.</p><span id="more"></span><h2 id="Trade-offs-Across-Four-Dimensions"><a href="#Trade-offs-Across-Four-Dimensions" class="headerlink" title="Trade-offs Across Four Dimensions"></a>Trade-offs Across Four Dimensions</h2><p>Career choice ultimately comes down to two key decisions: city and company. Choosing a city is highly subjective. For example, as someone from the north, I don’t like the climate in southern cities, I want to be close to home, and I need the opportunities that a big city offers — so Beijing was almost the only option. As practical advice, I’d suggest considering fast-growing strong second-tier cities like Chongqing or Hefei, where you can ride the wave of urban development and increase your own value along the way.</p><p>Within the scope of Beijing, I’ll discuss company choices based on my own experience.</p><h2 id="Hukou-The-Ticket-to-a-Tier-1-City"><a href="#Hukou-The-Ticket-to-a-Tier-1-City" class="headerlink" title="Hukou: The Ticket to a Tier-1 City"></a>Hukou: The Ticket to a Tier-1 City</h2><p>When it comes to Beijing, one topic that inevitably comes up is the hukou (household registration). However, <strong>I strongly advise against letting a hukou decision dictate your entire life trajectory</strong> — for instance, joining a state-owned enterprise just for a hukou even though you have no desire to work in the system. Always prioritize thinking through your career development direction before worrying about hukou.</p><p>Many people agonize over public sector vs. private sector, big company vs. small company — but these are all surface-level choices. Even within the public sector, experiences can vary enormously. The right approach is to first clarify what you truly want, then seek out suitable opportunities. Knowing what you truly want isn’t easy for everyone, so let me break it down. Consider these questions:</p><ul><li>What do you most want from work? Money? Power? A sense of inner achievement? Or do you not expect anything from work at all?</li><li>How much of your life do you want work to occupy?</li><li>Among the resources gained through work (connections, information, etc.) and the capabilities developed, which do you feel more confident carrying into a different environment?</li><li>Do you prefer doing routine, administrative work, or do you want to continuously tackle challenging problems?</li></ul><p>For any of these questions, no answer is inherently better than another. Tackling difficult problems isn’t superior to doing routine work — it’s simply a choice based on personal values.</p><p>With any combination of these choices, you can find suitable opportunities. For instance, if you don’t expect much from work, don’t want it to dominate your life, and are willing to do routine work, some peripheral state-owned institutions could be a good fit. If you want power, are willing to let work take up significant time, and prefer to accumulate resources through work, consider core state-owned institutions with real authority. And if you want to earn money and prefer to develop personal capabilities, being a programmer is a solid choice.</p><p>Once you’ve thought through these questions, you can choose a company. Many times, we may not find a place that satisfies all our requirements. That’s okay — we can use job-hopping to address one part at a time, continuously gather information and improve our abilities, and find opportunities when we’re ready to seize them.</p><h2 id="Big-Tech-The-Value-and-Cost-of-the-Platform"><a href="#Big-Tech-The-Value-and-Cost-of-the-Platform" class="headerlink" title="Big Tech: The Value and Cost of the Platform"></a>Big Tech: The Value and Cost of the Platform</h2><p>Let me share how I approached my own key decisions based on my experience.</p><p>When I first graduated, my goal was to get a Beijing hukou while staying in the internet industry. I had two broad directions: competing for an SSP offer at an internet company — but my abilities at the time weren’t sufficient, so that path was hard to take. The other path was to find opportunities within state-owned enterprises where the business closely resembled internet scenarios, ideally consumer-facing products with large user bases and data volumes, so I wouldn’t fall too far behind industry-leading technologies. Fortunately, I found such an opportunity and successfully seized it. After joining, it was pretty much as I expected — although the company’s technical level wasn’t high and the corporate culture had many issues, the business scenarios were excellent and I had plenty of room to contribute. Through my work, I learned a great deal.</p><p>Next, I needed to address my big tech experience, so when I changed jobs, I only considered top-tier tech companies. The work itself was nothing special to speak of — although I wasn’t happy during that period, I achieved the goals I’d set, so there are no regrets.</p><p>For my next job change, I had three goals: significantly increase my income to close the gap with peers caused by my time at the state-owned enterprise; not work too much overtime; and have more relaxed interpersonal relationships. These three goals seem contradictory, don’t they? But with effort, I always managed to find suitable opportunities, and that’s how I ended up at my current company. Not all problems were solved perfectly, but they were addressed enough to make my work experience quite enjoyable.</p><p>Through my own experience, you can see that I always seemed to make contradictory demands — wanting both fish and bear paw — and fortunately, I managed to get both in the end. The truth is, there are so many companies in the world that no two conditions are necessarily mutually contradictory. <strong>There’s no need to give things up prematurely. For example, does being a programmer necessarily mean 996 work and giving up your life? Obviously not.</strong></p><p>I’d like to address two more specific questions that arise from the above.</p><p>First, whether to choose a Beijing hukou. Let me first say what I gained from it. I caught the wave of property price surges in 2015-2016, which significantly increased my family’s assets even though I had no financial planning awareness at the time. This no longer holds true today — under the policy of housing for living, not speculation, even Beijing properties can’t outperform the stock market. Second, some procedures became slightly more convenient to handle, which I consider negligible — I probably don’t deal with paperwork more than once a year, and an increasing number of procedures support remote processing. Third, I can rest assured about my children’s education in Beijing without worrying about future uncertainty. I say “uncertainty” because I think it’s highly uncertain whether non-Beijing hukou holders will still be barred from taking the gaokao in Beijing nearly twenty years from now, and equally uncertain how competitive Beijing’s gaokao will become. A Beijing hukou essentially fixes my future choices, but what those choices will actually amount to is hard to predict.</p><p>So currently, the value of a Beijing hukou is quite limited. If I could go back to 2015, I’d still choose a Beijing hukou without hesitation. But if I had to make this choice today in 2021, I’d most likely decline it.</p><p>Second, regarding big tech experience. My view is that you must have it, and the sooner the better, but there’s no need to stay at a big company for life. The value of big tech experience has been thoroughly discussed by many, so I won’t repeat it here. Feel free to reach out if you’re interested in discussing further.</p><h2 id="How-to-Choose"><a href="#How-to-Choose" class="headerlink" title="How to Choose"></a>How to Choose</h2><p>The above covers most of this article. One point I haven’t mentioned is the midlife crisis — some might wonder why. The reason is that I don’t think it’s a problem that can be solved through career choices alone. Some people think the public sector is stable — even if you coast every day, no one can touch you. But I don’t consider stability that depends on external conditions to be true stability. The public sector has had problems before in history. This path might give you a 99.99% chance of living peacefully for life, but if that 0.01% scenario occurs, what will you do? Being a programmer is different — perhaps only a 50% chance of staying comfortably at one company, but the capabilities developed through work give me the confidence to handle the other 50%. The only way to address the midlife crisis is self-improvement — whether building capabilities or accumulating resources, you must gain something from work to maintain your competitiveness.</p><p>I’m Liusha. You can connect with me through the WeChat public account (Mobility), <a href="https://lichuanyang.top/">my personal website</a>, and other channels.</p><p>Source: <a href="https://lichuanyang.top/en/posts/34931/">https://lichuanyang.top/en/posts/34931/</a></p>]]>
    </content>
    <id>https://lichuanyang.top/en/posts/34931/</id>
    <link href="https://lichuanyang.top/en/posts/34931/"/>
    <published>2021-03-30T10:36:15.000Z</published>
    <summary>Practical advice on city and company selection for fresh graduates, drawing from experiences at state-owned enterprises, big tech companies, and startups.</summary>
    <title>Hukou, Big Tech, High Salary, Life — Career Choices for Fresh Graduates</title>
    <updated>2026-06-27T03:48:25.554Z</updated>
  </entry>
  <entry>
    <author>
      <name>SandGrid</name>
    </author>
    <category term="Architecture Design" scheme="https://lichuanyang.top/en/categories/Architecture-Design/"/>
    <category term="design-pattern" scheme="https://lichuanyang.top/en/tags/design-pattern/"/>
    <content>
      <![CDATA[<h2 id="Problem-Statement"><a href="#Problem-Statement" class="headerlink" title="Problem Statement"></a>Problem Statement</h2><p>Regarding the inputs and outputs of RPC interfaces, there has long been a school of thought that advocates wrapping all inputs and outputs into individual request and response classes. In this article, we will analyze whether this approach constitutes a sound design.</p><h2 id="Input-Parameter-Encapsulation-Analysis"><a href="#Input-Parameter-Encapsulation-Analysis" class="headerlink" title="Input Parameter Encapsulation Analysis"></a>Input Parameter Encapsulation Analysis</h2><p>Let’s start with inputs. I’ll state the conclusion first: encapsulation should only be done when there are excessive input parameters. In all other cases, there is absolutely no justification for encapsulation.</p><p>The reasons given for wrapping input parameters into a request class typically boil down to the following:</p><ul><li>Maintaining compatibility when interface parameters are modified</li><li>Defining a generic abstract request class for unified management</li></ul><p>First, the first point is a complete fallacy. The first time I encountered this claim, I was almost misled by it as well. But upon careful reflection, it becomes clear that this argument doesn’t hold at all.</p><p>Even with a request body approach, if the interface provider adds a mandatory field, the caller still needs to include this parameter to make the call. Compared to passing parameters directly, this doesn’t solve any problem. Furthermore, this kind of issue is fundamentally unsolvable because such situations should not arise in the first place. Interface providers have an obligation to ensure that subsequent modifications are forward-compatible. As for adding non-mandatory fields, even without wrapping in a request class, you can implement the new interface and maintain backward compatibility through an adapter pattern, and later asynchronously deprecate the old interface when the opportunity arises.</p><p>The second point does have some merit.</p><ul><li>For example, adding unified request parameters in an abstract class. I believe this is better handled in the framework layer rather than being exposed in business code.</li><li>Another example is adding unified parameter validation. But if that’s the only purpose, using an overly abstract class is inappropriate. Instead, each related business domain should have its own abstract class, since only that domain would have such requirements. Moreover, even without wrapping into a request class, such requirements are not difficult to implement, as the difficulty itself doesn’t lie in non-uniform input structures.</li></ul><p>The biggest problem with wrapping input parameters into a request class is that it destroys code readability and creates semantic ambiguity. A method like <code>queryById(long id)</code> has very clear semantics — a glance at the interface definition reveals the inputs and outputs. But after forced encapsulation, what inputs the interface actually requires becomes unclear. Additionally, if a request contains three parameters, can any combination of them return a result? This is also something you cannot determine just by looking at the interface. With normal parameter definitions, you can define a series of interfaces using the adapter pattern, making it easier for callers to understand.</p><p>The only condition for encapsulating input parameters is when there are too many parameters, and you need to use other means such as comments or documentation to specify the legal input values and upgrade strategies for the interface.</p><h2 id="Output-Result-Encapsulation-Analysis"><a href="#Output-Result-Encapsulation-Analysis" class="headerlink" title="Output Result Encapsulation Analysis"></a>Output Result Encapsulation Analysis</h2><p>Now let’s discuss outputs. Wrapping output results into a response body is somewhat more reasonable than wrapping input parameters, but it still shouldn’t be used indiscriminately. The general purpose of wrapping output results is to add a return status code layer.</p><h2 id="Interface-Segregation-Principle-Perspective"><a href="#Interface-Segregation-Principle-Perspective" class="headerlink" title="Interface Segregation Principle Perspective"></a>Interface Segregation Principle Perspective</h2><p>This essentially forces callers to understand the error conditions of the interface. We know that one of the fundamental design principles is the “Interface Segregation Principle,” which states that clients should not be forced to depend upon interfaces that they do not use. The situation here is analogous: if callers genuinely don’t care about the reasons for request success or failure, they shouldn’t be forced to understand them. For example, if I just want to query a count, there are only two possibilities — found or not found — and I don’t want to care about why it wasn’t found.</p><p>Therefore, when designing interfaces, you need to carefully consider whether the error conditions of an interface should be information that is provided externally. Only on this basis can you determine whether wrapping in a response object is appropriate or not.</p><h2 id="Conclusion-and-Recommendations"><a href="#Conclusion-and-Recommendations" class="headerlink" title="Conclusion and Recommendations"></a>Conclusion and Recommendations</h2><p>In summary, blindly wrapping all input and output parameters is never advisable. You must carefully consider when encapsulation is appropriate and when it is not. If you have any thoughts on this topic, feel free to discuss them with me.</p><p>Source: <a href="https://lichuanyang.top/en/posts/20888/">https://lichuanyang.top/en/posts/20888/</a></p>]]>
    </content>
    <id>https://lichuanyang.top/en/posts/20888/</id>
    <link href="https://lichuanyang.top/en/posts/20888/"/>
    <published>2021-03-16T10:34:56.000Z</published>
    <summary>An in-depth analysis of wrapping RPC interface inputs and outputs into request/response classes, discussing when encapsulation is appropriate and when it becomes over-engineering.</summary>
    <title>Is Encapsulating All RPC Interface Inputs and Outputs into Classes a Sound Design?</title>
    <updated>2026-06-27T03:50:56.858Z</updated>
  </entry>
  <entry>
    <author>
      <name>SandGrid</name>
    </author>
    <category term="Tech Talk" scheme="https://lichuanyang.top/en/categories/Tech-Talk/"/>
    <category term="reflections" scheme="https://lichuanyang.top/en/tags/reflections/"/>
    <content>
      <![CDATA[<p>Let me share my personal experience. At my first company right after graduation, I was actually doing infrastructure middleware development. But during that work, I realized I didn’t enjoy the feeling of being so far removed from real business. So when I later changed jobs, I deliberately chose a business-oriented department. Today, I want to explain why I made this choice.</p><span id="more"></span><h2 id="Why-I-Don’t-Do-Infrastructure-Development"><a href="#Why-I-Don’t-Do-Infrastructure-Development" class="headerlink" title="Why I Don’t Do Infrastructure Development"></a>Why I Don’t Do Infrastructure Development</h2><p>First, I want to address a common misconception: that infrastructure development makes it easier to learn and grow faster. I believe this is nothing more than wishful thinking by business developers. Infrastructure development isn’t as conducive to learning as people think, and business development isn’t as detrimental to learning as people think — I’ll elaborate on the reasons below. The reason many people hold this belief is largely because infrastructure development positions typically have higher hiring standards, so these individuals naturally have better learning ability and motivation.</p><p>In reality, the daily work of infrastructure development isn’t that different from business development — both involve working on requirements. The only difference is that business requirements come from product managers, while infrastructure requirements come from other developers, or from the infrastructure developer’s own thinking and ideas. This brings me to my first reason for not wanting to do infrastructure development: the source of requirements is unscientific. If you only do infrastructure development and are detached from actual business scenarios, your understanding of technology is likely to be biased, and things you want to build may just be self-indulgent exercises with no practical application value. Even when collecting requirements from business developers, there are similar issues — their suggestions may reflect only shallow, unconsidered thoughts. Moreover, business developers may not accurately understand what infrastructure components should actually handle; they may not distinguish between what should be solved by infrastructure and what they should handle themselves.</p><p>This brings me to my second reason for not wanting to do infrastructure development: unclear boundaries of responsibility. This is closely tied to the capability of the business teams you work with. Sometimes, when something on your end isn’t done well, the business team finds a way to work around it. Other times, even when something has nothing to do with you, you’re forced to deal with some upper-layer logic.</p><p>As you can see, both issues above are extremely dependent on colleagues’ abilities. So if you insist on doing infrastructure development, you must go to a company with strong technical talent — this directly affects your work happiness. The company choice also impacts another aspect: the nature of the work. Simply put, it’s a difference between building wheels versus improving open-source tools — these two types of work are worlds apart. Building wheels requires deep familiarity with various fundamental knowledge and can indeed promote growth — this is the origin of the idea that “infrastructure development helps you grow.” But this kind of work only exists at a handful of big companies. The vast majority of infrastructure work — improving open-source tools — is no different from business development. You only need to learn the ins and outs of the open-source tools to complete most of the work, with neither depth nor breadth. This is why I said earlier that infrastructure development doesn’t help with learning.</p><p>So, to do infrastructure development, company selection is crucial — it must be large AND technically strong; both conditions are essential. You might ask: why not just go to such a company? That’s fair, but it brings its own problem — my third reason for not wanting to do infrastructure development: the career choice is extremely narrow. In China, there are only two companies doing Java infrastructure well, and everyone knows what their corporate cultures are like. So if you don’t want to go to those companies, does that mean you can’t do it at all? Even if you’re willing, the massive supply-demand imbalance would lead to insane involution, similar to algorithm positions. Do you have enough confidence to win that game? In contrast, for business development, big tech experience is just a necessary learning experience and a highlight on your resume. Once you’re familiar with the entire standardized workflow at a big company, you can freely choose wherever you want to go.</p><h2 id="The-Value-of-Business-Development"><a href="#The-Value-of-Business-Development" class="headerlink" title="The Value of Business Development"></a>The Value of Business Development</h2><p>I’ve talked too much about infrastructure development above. Let me now get to the main topic: the value of business development. In my view, business development has improved my capabilities primarily in three areas: accumulation of business knowledge, expansion of technical breadth, and improvement of comprehensive abilities.</p><p>First, business knowledge — business knowledge is essentially an understanding of the real world. For me personally, learning about how different industries operate in my spare time is first and foremost an interesting thing to do. Second, continuously learning industry knowledge across different domains has noticeably improved my cognitive abilities. Can this ability be monetized? Of course. For example, when investing in stocks, the accumulation from work has given me the ability to analyze a company’s business model — I can clearly understand what makes a good company and a good stock at a deeper level than most people. Of course, I’m getting ahead of myself here — take it as food for thought. The key is to follow your interest and see if you’re willing to learn about things beyond technology.</p><p>Next, technical breadth. The biggest difference between business development and infrastructure development is that infrastructure development demands more depth, while business development demands more breadth. Business development requires familiarity with a wide variety of frameworks and middleware — not just surface-level knowledge, but enough depth to understand what problems they solve, what problems they introduce, and which scenarios they’re suited for. You need to comprehensively understand complex business scenarios and complex systems, and be able to quickly locate problems when they occur. Beyond knowledge accumulation, this also requires experience gained from real-world scenarios.</p><p>Then there’s the improvement of comprehensive abilities. Compared to infrastructure development, business development exercises a person’s general foundational capabilities more — communication, problem analysis and problem solving, and the ability to weigh trade-offs. These abilities are related to technology, but not so closely. To be extreme, even if you change careers in the future, these universal capabilities will be very helpful. I often think about what the difference is between learning to use an open-source tool and playing Honor of Kings. As a beginner, following an example to build a demo is like encountering a game for the first time and just playing around. As you get more familiar, some people become emotionless tool-switching machines, while others gradually explore the underlying implementation. Just like in gaming — some people play thousands of matches without ever understanding the difference between Lightning Dagger and Infinity Edge, while others explore the numerical mechanics of damage. So I think we shouldn’t view programmers as some uniquely special profession — it’s fundamentally no different from other professions. Most professions are just solving problems in different ways: lawyers rely on law, fund managers rely on understanding markets and fundamentals, and programmers rely on code. What we continuously develop through work is our problem-solving ability, and no one requires the solution to be limited to technology alone.</p><p>In business development, we must continuously accumulate capabilities in these three areas, so that we can build momentum over time and confidently face the midlife crisis. Of course, I’m not writing this to say that business development is inherently better — I mainly want to help everyone clearly analyze the differences between business development and infrastructure development, so you can make a rational choice.</p><p>If you have any thoughts, feel free to reach out and discuss with me.</p><p>Source: <a href="https://lichuanyang.top/en/posts/40071/">https://lichuanyang.top/en/posts/40071/</a></p>]]>
    </content>
    <id>https://lichuanyang.top/en/posts/40071/</id>
    <link href="https://lichuanyang.top/en/posts/40071/"/>
    <published>2021-03-12T11:27:36.000Z</published>
    <summary>From personal experience, analyzing the real differences between business development and infrastructure development, and dispelling the misconception that 'infrastructure development is better for growth'.</summary>
    <title>The Value of Business Development for Programmers</title>
    <updated>2026-06-27T03:51:55.444Z</updated>
  </entry>
</feed>
