How to Build a Fast, Reliable, and Cost‑Effective Like System
This article breaks down the design of a high‑traffic like service, detailing four core requirements—speed, accuracy, stability, and cost control—while recommending Redis, a message queue, and MySQL, and illustrating the full architecture from frontend to persistence with practical code examples and optimization tips.
Introduction
Users are extremely sensitive to the responsiveness of a like button; if the button does not change color within three seconds, 90% of users abandon the action, and duplicate likes are perceived as data manipulation.
Core Requirements
1. Fast – sub‑second response
The end‑to‑end latency from click to UI feedback must stay below 500 ms, ideally under 200 ms, because the request traverses the frontend, network, service, cache, and database.
2. Accurate – data consistency
Likes must be "no loss, no duplication" with eventual consistency: the cache reflects the latest state immediately, while the database persists the final state.
3. Stable – resilience under traffic spikes
A popular post can receive 1 million likes in five minutes (≈3 333 req/s, peak up to 10 000 req/s). The system must handle such bursts without crashes, data loss, or noticeable latency.
4. Cost‑effective – resource‑aware storage
Use tiered storage: hot data (real‑time counts) in Redis, warm data (30‑day history) in MySQL, and cold data (older than three months) in low‑cost archival storage.
Technical Selection – Three Core Tools
Redis: the caching champion
Redis provides rich data structures, atomic operations, Lua scripting, and persistence (RDB+AOF), making it ideal for high‑frequency read/write scenarios.
Message Queue: Kafka or RabbitMQ
Asynchronous processing decouples like count updates from persistence. Kafka suits massive traffic (tens of thousands of msgs/s), while RabbitMQ offers easier routing for medium‑scale platforms.
MySQL: the durable store
MySQL holds the full like record (user_id, content_id, type, timestamps) to support features such as "my likes" lists and historical analysis.
Architecture Overview
Frontend Layer
Immediately provide visual feedback (color change, animation) and send an asynchronous request. On network failure, cache the request locally (e.g., localStorage) and retry later.
Service Layer
Validate parameters, check like status in Redis, perform atomic increment/decrement, and publish a message to the queue. Ensure idempotency using a request‑ID and a distributed lock.
Cache Layer
Store two key types: user‑to‑content (who liked what) using Set or Hash, and content‑like count using a simple String with INCR/DECR. Apply expiration policies (hot keys 24 h, cold keys 1 h) and protect against cache penetration with Bloom filters or empty‑value caching.
Persistence Layer
Consume queue messages to insert or logically delete records in MySQL (is_canceled flag). Use a unique index (user_id, content_id, content_type) to prevent duplicate likes.
CREATE TABLE `like_records` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'Primary key',
`user_id` BIGINT(20) NOT NULL COMMENT 'User ID',
`content_id` BIGINT(20) NOT NULL COMMENT 'Content ID',
`content_type` VARCHAR(20) NOT NULL COMMENT 'Content type',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Like time',
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Update time',
`is_canceled` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'Logical delete flag',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_content` (`user_id`,`content_id`,`content_type`) COMMENT 'Prevent duplicate likes',
KEY `idx_content` (`content_id`,`content_type`) COMMENT 'Query by content'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='User like records';Redis Commands Example
// Like: add user ID to a set
Redis.sAdd("like:status:123", "456");
// Check if user has liked
Redis.sIsMember("like:status:123", "456");
// Record with timestamp using a hash
Redis.hSet("like:status:123", "456", "1629260800000");
Redis.hExists("like:status:123", "456");
Redis.hGetAll("like:status:123");
// Increment/decrement like count
Redis.incr("like:count:123");
Redis.decr("like:count:123");Lua Script for Atomic Cancel
-- Check if user has liked
local isLiked = redis.call('HEXISTS', 'like:status:123', '456')
-- If liked, remove status and decrement count atomically
if isLiked == 1 then
redis.call('HDEL', 'like:status:123', '456')
redis.call('DECR', 'like:count:123')
end
return 1Advanced Optimizations
Hierarchical caching: local in‑process cache + Redis to offload 80% of reads.
Read‑write splitting and sharding: MySQL master for writes, slaves for reads; shard like_records by content_id modulo N.
Rate limiting & circuit breaking: token‑bucket via Redis+Lua or libraries like Sentinel/Hystrix; fallback to "like succeeded, will display later" when cache is down.
Conclusion
Designing a like system requires balancing speed, accuracy, stability, and cost. By using Redis for hot data, a message queue for async persistence, and MySQL for durability—combined with caching tiers, sharding, and protective mechanisms—you can build a service that scales to massive traffic while keeping user experience smooth.
NiuNiu MaTe
Joined Tencent (nicknamed "Goose Factory") through campus recruitment at a second‑tier university. Career path: Tencent → foreign firm → ByteDance → Tencent. Started as an interviewer at the foreign firm and hopes to help others.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
