• Visit the Chinese site here, or click 'Switch to Chinese' at the left sidebar.
  • See all the posts here, or click 'Archives' at the left sidebar.

Introduction

Since March, GitHub Copilot has gone through multiple updates that have essentially made it unusable. Both domestic and foreign model providers introduced subscription plans (also called "coding plans") with aggressive rate limiting and reduced quotas. Watching all the complaints about domestic providers on Bilibili, along with the high prices and difficult payment methods of foreign providers, it seemed like the AI coding bubble was about to burst—the cost had become unaffordable for ordinary people. But at the end of April, DeepSeek V4 arrived and shattered that perception. DeepSeek V4's capabilities are near top-tier, yet its price is dozens to over a hundred times lower than the most advanced models. Its extremely strong infrastructure capabilities also deliver a great user experience. So I started using Claude Code connected to DeepSeek V4 for AI programming.

This post covers why I stopped using GitHub Copilot, some problems I encountered previously with AI coding and how I solved them, as well as my initial experience and impressions of using Claude Code with DeepSeek V4.

Read more »

Introduction

Readers may first want to read the previous article on this problem to understand the problem statement, the reasoning, and the solution.

Although I look down on a lot of my past code, the solution I wrote two years ago for this problem (Solution 1) seems to be the first one that genuinely made me "unbengable" (speechless). Today, when I came across this problem again, I only remembered that I had solved it before and even written a blog post about it, but I had completely forgotten the specific approach, so I had to start from scratch. In hindsight, that turned out to be a lucky thing.

The idea behind this problem is simple: given a postorder sequence $s_1, s_2, \cdots, s_a, \cdots, s_b, \cdots, s_n$, if $s_a$ appears before $s_b$, then $s_b$ can never be a child of $s_a$. The tricky part is how to translate that into code. In fact, the deeper question is: since the code for "Preorder and Inorder" and "Postorder and Inorder" is highly similar, why must the code for this problem ("Preorder and Postorder") be fundamentally so different?

This blog post presents Solution 2, which makes the code for all three binary-tree construction problems highly consistent, differing only in local details, and significantly improves readability.

Read more »

Introduction

As an instant messaging system, LineChat naturally relies on WebSocket to ensure real-time message delivery. For an architecture with a single backend instance, development is fairly straightforward, but that would ultimately be just a toy project with little challenge. To serve a large number of concurrent users, we must design a distributed architecture that supports horizontal scaling, capable of handling user connections, message delivery, and automatic failover. This post covers my exploration, thoughts, and implementation of such an architecture.

New Problems

If the backend is deployed as a single instance, when the user numbers surge, the simplest approach is vertical scaling, for example, upgrading from a VM with 16 cores and 32 GB of RAM to one with 512 cores and 2 TB of RAM. Leaving aside hardware costs and whether such a product exists, the biggest bottleneck at that point is the software itself. Unless the architecture is deliberately designed from the start with HPC-like considerations (i.e., accounting for low-level details and optimizations), typical architectures will inevitably suffer from significant resource contention and fail to fully utilize the hardware. And perhaps most critically, what happens when a failure occurs? Do we simply expect the millions of users connected to that VM to wait and reconnect later? But we cannot guarantee that a replacement service can be brought online within an extremely short time frame.

Read more »

Introduction

Recently my computer is running out of disk space. Looking at every frontend project with a node_modules folder of several hundred MB, I decided to look into migrating from npm to pnpm. The reason is that pnpm uses a global local store to host dependencies, similar to Go Modules and Maven, and then manages each project's node_modules folder through a combination of symlinks and hard links. This can save a lot of disk space by eliminating duplicate dependencies.

Beyond this motivation, pnpm has many other benefits: (1) faster – though I feel the main reason for slowness is Windows Defender; (2) used by many well-known open source projects, such as Vue, which proves its reliability; (3) more secure, e.g. blocking post-install scripts by default, which are vulnerable to supply chain attacks.

After reading the pnpm documentation once, I started the migration, which is the main content of this blog post. I migrated a total of 6 projects, including Vue, React, a mini-program, and my personal blog. The overall migration process went smoothly – after all, I'm using well-known open source projects.

Read more »

Introduction

Readers may have noticed that compared to the previous blog post, the title of this post does not use quotation marks.

In early February, I had already used up a large portion of my GitHub Copilot quota. Hoping to save quota, and since Alibaba Cloud had gifted some free tokens, I decided to give Claude Code a try. I spent a few days reading the official documentation, focusing on permission management, security, and other aspects, then watched a few tutorials, and started experimenting. The result was "the higher the expectation, the greater the disappointment" — it was not useful at all.

Although Claude Code wasn't great, the VS Code release notes in mid-February mentioned agent orchestration, which caught my attention because I had previously read about agent teams and subagents in the Claude Code documentation. These three concepts actually refer to similar things, differing only in the level of autonomy. Over the past few weeks, I have used this feature multiple times and found it to be cost-effective and efficient, hence this blog post.

Claude Code Isn't useful

The reasons I mentioned in the introduction why Claude Code isn't useful are as follows:

Read more »

Introduction

My project uses Redis as a cache. For user module endpoints, except for login and registration which require a phone number, all other endpoints operate using an ID. The initial code stored both an ID-to-user mapping and a phone-number-to-user mapping in the cache. Storing the same user object twice is obviously unreasonable, not to mention the potential additional consistency problems that could arise.

Since one of the purposes of a cache is to protect the database, naturally we can achieve this by other means, such as rate limiting. If we only apply rate limiting to the login and registration endpoints, then we can store only the ID-to-user mapping in the cache. This is also reasonable from a business perspective, as these two endpoints are not frequently called.

There are at least three specific implementations of rate limiting: the leaky bucket algorithm, the token bucket algorithm, and message queues. I don't want to introduce a message queue just for rate limiting, especially since that component is difficult to operate. The token bucket algorithm is a superior alternative to the leaky bucket algorithm, as it better handles burst traffic. Therefore, the leaky bucket algorithm is rarely used nowadays.

Read more »

Introduction

I first learned about Bloom filters two or three years ago while studying common interview topics, specifically as a solution for cache penetration. Of course, I only knew it as a concept back then and never implemented it in any project. Recently, after reading the Bigtable paper and discovering that it also uses Bloom filters, I realized their practical utility.

When it came to technology selection, I considered Guava, Redis' RedisBloom module, and Redisson. Guava offers a standalone Bloom filter with high performance, but it's not suitable for cluster environments. The RedisBloom module isn't built into Redis and requires separate installation, which I preferred to avoid. Ultimately, I chose the Bloom filter functionality provided by Redisson, a Redis client, and successfully applied it in my own project.

The principle behind Bloom filters is simple. One might assume that Redisson implements Bloom filters using Lua scripts to ensure atomicity. However, I wasn't sure exactly how Redisson did it, so I decided to investigate. This blog post leverages an LLM to read the source code of the RedissonBloomFilter class and analyze its implementation.

Read more »
0%