Tôi đã có một ngăn xếp ứng dụng web điển hình:

  • PostgreSQL cho dữ liệu bền vững
  • Redis để lưu đệm, pub/sub và các công việc nền

Hai cơ sở dữ liệu.
Hai thứ cần quản lý.
Hai điểm dễ hỏng.

Sau đó tôi nhận ra:PostgreSQL có thể làm mọi thứ Redis làm.

Tôi đã loại bỏ hoàn toàn Redis.
Đây là những gì đã xảy ra.


Nội dung chính

Thiết lập:
Tôi Đã Dùng Redis Để Làm Gì

Trước khi thay đổi, Redis xử lý ba việc:

1. Lưu đệm (70% mức sử dụng)

// Lưu đệm phản hồi APIawaitredis.set(`user:${id}`,JSON.stringify(user),'EX',3600);
Enter fullscreen modeExit fullscreen mode

2. Pub/Sub (20% mức sử dụng)

// Thông báo thời gian thựcredis.publish('notifications',JSON.stringify({userId,message}));
Enter fullscreen modeExit fullscreen mode

3. Hàng đợi Công việc Nền (10% mức sử dụng)

// Sử dụng Bull/BullMQqueue.add('send-email',{to,subject,body});
Enter fullscreen modeExit fullscreen mode

Những điểm đau:

  • Hai cơ sở dữ liệu cần sao lưu
  • Redis sử dụng RAM (tốn kém ở quy mô lớn)
  • Tính bền vững của Redis thì…
    phức tạp
  • Một bước nhảy mạng giữa Postgres và Redis

Tại Sao Tôi Cân Nhắc Thay Thế Redis

Lý do #1:
Chi phí

Thiết lập Redis của tôi:

  • AWS ElastiCache:
    $45/tháng (2GB)
  • Tăng lên 5GB sẽ tốn $110/tháng

PostgreSQL:

  • Đã trả tiền cho RDS:
    $50/tháng (20GB lưu trữ)
  • Thêm 5GB dữ liệu:
    $0.50/tháng

Khoản tiết kiệm tiềm năng:~$100/tháng

Lý do #2:
Độ phức tạp vận hành

Với Redis:

Sao lưu Postgres ✅ Sao lưu Redis ❓ (RDB? AOF? Cả hai?) Giám sát Postgres ✅ Giám sát Redis ❓ Chuyển đổi dự phòng Postgres ✅ Sentinel/Cluster Redis ❓ 
Enter fullscreen modeExit fullscreen mode

Không có Redis:

Sao lưu Postgres ✅ Giám sát Postgres ✅ Chuyển đổi dự phòng Postgres ✅ 
Enter fullscreen modeExit fullscreen mode

Bớt đi một thành phần động.

Lý do #3:
Tính nhất quán dữ liệu

Vấn đề kinh điển:

// Cập nhật cơ sở dữ liệuawaitdb.query('UPDATE users SET name = $1 WHERE id = $2',[name,id]);// Vô hiệu hóa bộ nhớ đệmawaitredis.del(`user:${id}`);// ⚠️ Nếu Redis bị sập thì sao?// ⚠️ Nếu việc này thất bại thì sao?// Giờ thì bộ nhớ đệm và DB không đồng bộ
Enter fullscreen modeExit fullscreen mode

Với mọi thứ trong Postgres:giao dịch giải quyết việc này.


Tính năng PostgreSQL #1:
Lưu đệm với Bảng UNLOGGED

Redis:

awaitredis.set('session:abc123',JSON.stringify(sessionData),'EX',3600);
Enter fullscreen modeExit fullscreen mode

PostgreSQL:

CREATEUNLOGGEDTABLEcache(keyTEXTPRIMARYKEY,valueJSONBNOTNULL,expires_atTIMESTAMPTZNOTNULL);CREATEINDEXidx_cache_expiresONcache(expires_at);
Enter fullscreen modeExit fullscreen mode

Chèn:

INSERTINTOcache(key,value,expires_at)VALUES($1,$2,NOW()+INTERVAL'1 hour')ONCONFLICT(key)DOUPDATESETvalue=EXCLUDED.value,expires_at=EXCLUDED.expires_at;
Enter fullscreen modeExit fullscreen mode

Đọc:

SELECTvalueFROMcacheWHEREkey=$1ANDexpires_at>NOW();
Enter fullscreen modeExit fullscreen mode

Dọn dẹp (chạy định kỳ):

DELETEFROMcacheWHEREexpires_at<NOW();
Enter fullscreen modeExit fullscreen mode

UNLOGGED là gì?

Bảng UNLOGGED:

  • Bỏ qua Nhật ký Ghi trước (WAL)
  • Ghi nhanh hơn nhiều
  • Không tồn tại sau sự cố (hoàn hảo cho bộ nhớ đệm!)

Hiệu suất:

Redis SET: 0.05ms Postgres UNLOGGED INSERT: 0.08ms 
Vào chế độ toàn màn hìnhThoát chế độ toàn màn hình

Đủ gần để dùng cho bộ nhớ đệm.


Tính năng PostgreSQL #2:
Pub/Sub với LISTEN/NOTIFY

Đây là lúc mọi thứ trở nên thú vị.

PostgreSQL cópub/sub gốcmà hầu hết nhà phát triển không biết.

Redis Pub/Sub

// Nhà xuất bảnredis.publish('notifications',JSON.stringify({userId:123,msg:'Xin chào'}));// Người đăng kýredis.subscribe('notifications');redis.on('message',(channel,message)=>{console.log(message);});
Vào chế độ toàn màn hìnhThoát chế độ toàn màn hình

PostgreSQL Pub/Sub

-
- Nhà xuất bảnNOTIFYnotifications,'{"userId":
123, "msg":
"Xin chào"}';
Vào chế độ toàn màn hìnhThoát chế độ toàn màn hình
// Người đăng ký (Node.js với pg)constclient=newClient({connectionString:process.env.DATABASE_URL});awaitclient.connect();awaitclient.query('LISTEN notifications');client.on('notification',(msg)=>{constpayload=JSON.parse(msg.payload);console.log(payload);});
Vào chế độ toàn màn hìnhThoát chế độ toàn màn hình

So sánh hiệu suất:

Độ trễ Redis pub/sub: 1-2ms Độ trễ Postgres NOTIFY: 2-5ms 
Vào chế độ toàn màn hìnhThoát chế độ toàn màn hình

Hơi chậm hơn, nhưng:

  • Không cần thêm cơ sở hạ tầng
  • Có thể sử dụng trong giao dịch
  • Có thể kết hợp với truy vấn

Ví dụ Thực tế:
Live Tail

Trong ứng dụng quản lý nhật ký của tôi, tôi cầntruyền phát nhật ký thời gian thực.

Với Redis:

// Khi có nhật ký mới đếnawaitdb.query('INSERT INTO logs ...');awaitredis.publish('logs:new',JSON.stringify(log));// Frontend lắng ngheredis.subscribe('logs:new');
Vào chế độ toàn màn hìnhThoát chế độ toàn màn hình

Vấn đề:Hai thao tác.
Nếu việc xuất bản thất bại thì sao?

Với PostgreSQL:

CREATEFUNCTIONnotify_new_log()RETURNSTRIGGERAS$$BEGINPERFORMpg_notify('logs_new',row_to_json(NEW)::text);RETURNNEW;END;$$LANGUAGEplpgsql;CREATETRIGGERlog_insertedAFTERINSERTONlogsFOREACHROWEXECUTEFUNCTIONnotify_new_log();
Vào chế độ toàn màn hìnhThoát chế độ toàn màn hình

Giờ đây nó lànguyên tử.
Việc chèn và thông báo xảy ra cùng nhau hoặc không xảy ra gì cả.

// Frontend (qua SSE)app.get('/logs/stream',async(req,res)=>{constclient=awaitpool.connect();res.writeHead(200,{'Content-Type':'text/event-stream','Cache-Control':'no-cache',});awaitclient.query('LISTEN logs_new');client.on('notification',(msg)=>{res.write(`data:${msg.payload}\n\n`);});});
Vào chế độ toàn màn hìnhThoát chế độ toàn màn hình

Kết quả:Truyền phát nhật ký thời gian thực mà không cần Redis.


Tính năng PostgreSQL #3:
Hàng đợi Công việc với SKIP LOCKED

Redis (sử dụng Bull/BullMQ):

hàng đợi.thêm('gửi-email',{đến,chủ đề,nội dung});hàng đợi.xử lý('gửi-email',async(công việc)=>{awaitgửiEmail(công việc.dữ liệu);});
Vào chế độ toàn màn hìnhThoát chế độ toàn màn hình

PostgreSQL:

TẠOBẢNGcông_việc(idBIGSERIALKHÓA CHÍNH,hàng_đợiVĂN BẢNKHÔNG NULL,tải_trọngJSONBKHÔNG NULL,lần_thửSỐ NGUYÊNMẶC ĐỊNH0,lần_thử_tối_đaSỐ NGUYÊNMẶC ĐỊNH3,lên_lịch_vàoTHỜI GIAN CÓ MÚI GIỜMẶC ĐỊNHBÂY GIỜ(),tạo_vàoTHỜI GIAN CÓ MÚI GIỜMẶC ĐỊNHBÂY GIỜ());TẠOCHỈ MỤCidx_công_việc_hàng_đợiTRÊNcông_việc(hàng_đợi,lên_lịch_vào)KHIlần_thử<lần_thử_tối_đa;
Vào chế độ toàn màn hìnhThoát chế độ toàn màn hình

Xếp hàng:

CHÈNVÀOcông_việc(hàng_đợi,tải_trọng)GIÁ TRỊ('gửi-email','{"đến":
"user@example.com", "chủ đề":
"Xin chào"}');
Vào chế độ toàn màn hìnhThoát chế độ toàn màn hình

Worker (lấy ra khỏi hàng đợi):

VỚIcông_việc_tiếp_theoNHƯ(CHỌNidTỪcông_việcNƠIhàng_đợi=$1lần_thử<lần_thử_tối_đalên_lịch_vào<=BÂY GIỜ()SẮP XẾP THEOlên_lịch_vàoGIỚI HẠN1CHOCẬP NHẬTBỎ QUAĐÃ KHÓA)CẬP NHẬTcông_việcĐẶTlần_thử=lần_thử+1TỪcông_việc_tiếp_theoNƠIcông_việc.id=công_việc_tiếp_theo.idTRẢ VỀ*;
Vào chế độ toàn màn hìnhThoát chế độ toàn màn hình

Phép màu:CHO CẬP NHẬT BỎ QUA ĐÃ KHÓA

Điều này biến PostgreSQL thành mộthàng đợi không khóa:

  • Nhiều worker có thể lấy công việc đồng thời
  • Không có công việc nào được xử lý hai lần
  • Nếu một worker bị sập, công việc sẽ trở nên khả dụng lại

Hiệu suất:

Redis BRPOP: 0.1ms Postgres BỎ QUA ĐÃ KHÓA: 0.3ms 
Vào chế độ toàn màn hìnhThoát chế độ toàn màn hình

Khác biệt không đáng kể cho hầu hết khối lượng công việc.


Tính năng PostgreSQL #4:
Giới hạn tốc độ

Redis (bộ giới hạn tốc độ cổ điển):

constkhóa=`giới_hạn_tốc_độ:${id_người_dùng}`;constsố_lượng=awaitredis.tăng(khóa);if(số_lượng===1){awaitredis.hết_hạn(khóa,60);// 60 giây}if(số_lượng>100){thrownewLỗi('Đã vượt quá giới hạn tốc độ');}
Vào chế độ toàn màn hìnhThoát chế độ toàn màn hình

PostgreSQL:

TẠOBẢNGgiới_hạn_tốc_độ(id_người_dùngSỐ NGUYÊNKHÓA CHÍNH,số_lượng_yêu_cầuSỐ NGUYÊNMẶC ĐỊNH0,bắt_đầu_cửa_sổTHỜI GIAN CÓ MÚI GIỜMẶC ĐỊNHBÂY GIỜ());-
- Kiểm tra và tăngVỚIhiện_tạiNHƯ(CHỌNsố_lượng_yêu_cầu,TRƯỜNG HỢPKHIbắt_đầu_cửa_sổ<BÂY GIỜ()-KHOẢNG THỜI GIAN'1 phút'THÌ1-
- Đặt lại bộ đếmKHÁCsố_lượng_yêu_cầu+1KẾT THÚCNHƯsố_lượng_mớiTỪgiới_hạn_tốc_độNƠIid_người_dùng=$1CHOCẬP NHẬT)CẬP NHẬTgiới_hạn_tốc_độĐẶTsố_lượng_yêu_cầu=(CHỌNsố_lượng_mớiTỪhiện_tại),bắt_đầu_cửa_sổ=TRƯỜNG HỢPKHIbắt_đầu_cửa_sổ<BÂY GIỜ()-KHOẢNG THỜI GIAN'1 phút'THÌBÂY GIỜ()KHÁCbắt_đầu_cửa_sổKẾT THÚCNƠIid_người_dùng=$1TRẢ VỀsố_lượng_yêu_cầu;
Vào chế độ toàn màn hìnhThoát chế độ toàn màn hình

Hoặc đơn giản hơn với một hàm cửa sổ:

TẠOBẢNGapi_requests(user_idSỐ NGUYÊNKHÔNG ĐƯỢC NULL,created_atTHỜI GIAN CÓ MÚI GIỜMẶC ĐỊNHBÂY GIỜ());-
- Kiểm tra giới hạn tốc độCHỌNĐẾM(*)TỪapi_requestsNƠIuser_id=$1created_at>BÂY GIỜ()-KHOẢNG'1 phút';-
- Nếu dưới giới hạn, chèn vàoCHÈN VÀOapi_requests(user_id)GIÁ TRỊ($1);-
- Dọn dẹp các yêu cầu cũ định kỳXÓATỪapi_requestsNƠIcreated_at<BÂY GIỜ()-KHOẢNG'5 phút';
Vào chế độ toàn màn hìnhThoát chế độ toàn màn hình

Khi Postgres tốt hơn:

  • Cần giới hạn tốc độ dựa trên logic phức tạp (không chỉ đếm)
  • Muốn dữ liệu giới hạn tốc độ trong cùng giao dịch với logic nghiệp vụ

Khi Redis tốt hơn:

  • Cần giới hạn tốc độ dưới mili giây
  • Thông lượng cực cao (hàng triệu yêu cầu/giây)

Tính năng PostgreSQL #5:
Phiên với JSONB

Redis:

đợiredis.đặt(`phiên:${sessionId}`,JSON.chuỗi hóa(sessionData),'EX',86400);
Vào chế độ toàn màn hìnhThoát chế độ toàn màn hình

PostgreSQL:

TẠOBẢNGsessions(idVĂN BẢNKHÓA CHÍNH,dataJSONBKHÔNG ĐƯỢC NULL,expires_atTHỜI GIAN CÓ MÚI GIỜKHÔNG ĐƯỢC NULL);TẠOCHỈ MỤCidx_sessions_expiresTRÊNsessions(expires_at);-
- Chèn/Cập nhậtCHÈN VÀOsessions(id,data,expires_at)GIÁ TRỊ($1,$2,BÂY GIỜ()+KHOẢNG'24 giờ')KHI XUNG ĐỘT(id)THỰC HIỆN CẬP NHẬTĐẶTdata=ĐÃ LOẠI TRỪ.data,expires_at=ĐÃ LOẠI TRỪ.expires_at;-
- ĐọcCHỌNdataTỪsessionsNƠIid=$1expires_at>BÂY GIỜ();
Vào chế độ toàn màn hìnhThoát chế độ toàn màn hình

Thêm:
Toán tử JSONB

Bạn có thể truy vấn bên trong phiên:

-
- Tìm tất cả phiên cho một người dùng cụ thểCHỌN*TỪsessionsNƠIdata->>'userId'='123';-
- Tìm phiên có vai trò cụ thểCHỌN*TỪsessionsNƠIdata->'user'->>'role'='admin';
Vào chế độ toàn màn hìnhThoát chế độ toàn màn hình

Bạn không thể làm điều này với Redis!


Điểm chuẩn Thực tế

Tôi đã chạy điểm chuẩn trên tập dữ liệu sản xuất của mình:

Thiết lập Kiểm tra

  • Phần cứng:AWS RDS db.t3.medium (2 vCPU, 4GB RAM)
  • Tập dữ liệu:1 triệu mục bộ nhớ đệm, 10k phiên
  • Công cụ:pgbench (tập lệnh tùy chỉnh)

Kết quả

Thao tác Redis PostgreSQL Chênh lệch
Bộ nhớ đệm SET 0.05ms 0.08ms +60% chậm hơn
Bộ nhớ đệm GET 0.04ms 0.06ms +50% chậm hơn
Pub/Sub 1.2ms 3.1ms +158% chậm hơn
Hàng đợi đẩy 0.08ms 0.15ms +87% chậm hơn
Hàng đợi lấy 0.12ms 0.31ms +158% chậm hơn

PostgreSQL chậm hơn…
nhưng:

  • Tất cả thao tác vẫn dưới 1ms
  • Loại bỏ bước nhảy mạng đến Redis
  • Giảm độ phức tạp hạ tầng

Thao tác Kết hợp (Lợi ích Thực sự)

Kịch bản:Chèn dữ liệu + vô hiệu hóa bộ nhớ đệm + thông báo cho người đăng ký

Với Redis:

đợidb.truy vấn('CHÈN VÀO posts ...');// 2msđợiredis.xóa('posts:latest');// 1ms (bước nhảy mạng)đợiredis.công bố('posts:new',data);// 1ms (bước nhảy mạng)// Tổng cộng:
~4ms
Vào chế độ toàn màn hìnhThoát chế độ toàn màn hình

Với PostgreSQL:

BẮT ĐẦU;CHÈN VÀObài_viết...;-
- 2msXÓA TỪbộ_nhớ_đệmWHEREkhóa='bài_viết:mới_nhất';-
- 0.1ms (cùng kết nối)THÔNG BÁObài_viết_mới,'...';-
- 0.1ms (cùng kết nối)CAM KẾT;-
- Tổng cộng:
~2.2ms
Vào chế độ toàn màn hìnhThoát chế độ toàn màn hình

PostgreSQL nhanh hơn khi các thao tác được kết hợp.


Khi Nào Nên Giữ Redis

Đừng thay thế Redis nếu:

1. Bạn Cần Hiệu Suất Cực Cao

Redis: 100.000+ thao tác/giây (một phiên bản) PostgreSQL: 10.000-50.000 thao tác/giây 
Vào chế độ toàn màn hìnhThoát chế độ toàn màn hình

Nếu bạn đang thực hiện hàng triệu lượt đọc bộ nhớ đệm/giây, hãy giữ Redis.

2. Bạn Đang Sử Dụng Các Cấu Trúc Dữ Liệu Đặc Thù Của Redis

Redis có:

  • Tập hợp sắp xếp (bảng xếp hạng)
  • HyperLogLog (ước tính số lượng duy nhất)
  • Chỉ mục địa lý
  • Luồng (pub/sub nâng cao)

PostgreSQL có các tính năng tương đương nhưng cồng kềnh hơn:

-
- Bảng xếp hạng trong PostgreSQL (chậm hơn)SELECTuser_id,scoreFROMleaderboardORDERBYscoreDESCLIMIT10;-
- so với RedisZREVRANGEleaderboard09WITHSCORES
Vào chế độ toàn màn hìnhThoát chế độ toàn màn hình

3. Bạn Có Yêu Cầu Về Lớp Bộ Nhớ Đệm Riêng Biệt

Nếu kiến trúc của bạn bắt buộc một tầng bộ nhớ đệm riêng biệt (ví dụ:
kiến trúc microservices), hãy giữ Redis.


Chiến Lược Di Chuyển

Đừng gỡ bỏ Redis ngay lập tức.Đây là cách tôi đã làm:

Giai đoạn 1:
Song Song (Tuần 1)

// Ghi vào cả haiawaitredis.set(key,value);awaitpg.query('INSERT INTO cache ...');// Đọc từ Redis (vẫn là chính)letdata=awaitredis.get(key);
Vào chế độ toàn màn hìnhThoát chế độ toàn màn hình

Giám sát:So sánh tỷ lệ truy cập, độ trễ.

Giai đoạn 2:
Đọc từ PostgreSQL (Tuần 2)

// Thử PostgreSQL trướcletdata=awaitpg.query('SELECT value FROM cache WHERE key = $1',[key]);// Dự phòng bằng Redisif(!data){data=awaitredis.get(key);}
Vào chế độ toàn màn hìnhThoát chế độ toàn màn hình

Giám sát:Tỷ lệ lỗi, hiệu suất.

Giai đoạn 3:
Chỉ Ghi vào PostgreSQL (Tuần 3)

// Chỉ ghi vào PostgreSQLawaitpg.query('INSERT INTO cache ...');
Vào chế độ toàn màn hìnhThoát chế độ toàn màn hình

Giám sát:Mọi thứ vẫn hoạt động?

Giai đoạn 4:
Gỡ Bỏ Redis (Tuần 4)

# Tắt Redis# Theo dõi lỗi# Không có gì hỏng?
Thành công!
Vào chế độ toàn màn hìnhThoát chế độ toàn màn hình

Ví Dụ Mã:
Triển Khai Hoàn Chỉnh

Mô-đun Bộ Nhớ Đệm (PostgreSQL)

// cache.jsclassPostgresCache{constructor(pool){this.pool=pool;}asyncget(key){constresult=awaitthis.pool.query('SELECT value FROM cache WHERE key = $1 AND expires_at > NOW()',[key]);returnresult.rows[0]?.value;}asyncset(key,value,ttlSeconds=3600){awaitthis.pool.query(`INSERT INTO cache (key, value, expires_at) VALUES ($1, $2, NOW() + INTERVAL '${ttlSeconds}seconds') ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, expires_at = EXCLUDED.expires_at`,[key,value]);}asyncdelete(key){awaitthis.pool.query('DELETE FROM cache WHERE key = $1',[key]);}asynccleanup(){awaitthis.pool.query('DELETE FROM cache WHERE expires_at < NOW()');}}module.exports=PostgresCache;
Vào chế độ toàn màn hìnhThoát chế độ toàn màn hình

Mô-đun Pub/Sub

// pubsub.jsclassPostgresPubSub{constructor(pool){this.pool=pool;this.listeners=newMap();}asyncpublish(channel,message){constpayload=JSON.stringify(message);awaitthis.pool.query('SELECT pg_notify($1, $2)',[channel,payload]);}asyncsubscribe(channel,callback){constclient=awaitthis.pool.connect();awaitclient.query(`LISTEN${channel}`);client.on('notification',(msg)=>{if(msg.channel===channel){callback(JSON.parse(msg.payload));}});this.listeners.set(channel,client);}asyncunsubscribe(channel){constclient=this.listeners.get(channel);if(client){awaitclient.query(`UNLISTEN${channel}`);client.release();this.listeners.delete(channel);}}}module.exports=PostgresPubSub;
Enter fullscreen modeExit fullscreen mode

Mô-đun Hàng đợi Công việc

// queue.jsclassPostgresQueue{constructor(pool){this.pool=pool;}asyncenqueue(queue,payload,scheduledAt=newDate()){awaitthis.pool.query('INSERT INTO jobs (queue, payload, scheduled_at) VALUES ($1, $2, $3)',[queue,payload,scheduledAt]);}asyncdequeue(queue){constresult=awaitthis.pool.query(`WITH next_job AS ( SELECT id FROM jobs WHERE queue = $1 AND attempts < max_attempts AND scheduled_at <= NOW() ORDER BY scheduled_at LIMIT 1 FOR UPDATE SKIP LOCKED ) UPDATE jobs SET attempts = attempts + 1 FROM next_job WHERE jobs.id = next_job.id RETURNING jobs.*`,[queue]);returnresult.rows[0];}asynccomplete(jobId){awaitthis.pool.query('DELETE FROM jobs WHERE id = $1',[jobId]);}asyncfail(jobId,error){awaitthis.pool.query(`UPDATE jobs SET attempts = max_attempts, payload = payload || jsonb_build_object('error', $2) WHERE id = $1`,[jobId,error.message]);}}module.exports=PostgresQueue;
Enter fullscreen modeExit fullscreen mode

Mẹo Tối ưu Hiệu suất

1. Sử dụng Pool Kết nối

const{Pool}=require('pg');constpool=newPool({max:20,// Max connectionsidleTimeoutMillis:30000,connectionTimeoutMillis:2000,});
Enter fullscreen modeExit fullscreen mode

2. Thêm Chỉ mục Phù hợp

CREATEINDEXCONCURRENTLYidx_cache_keyONcache(key)WHEREexpires_at>NOW();CREATEINDEXCONCURRENTLYidx_jobs_pendingONjobs(queue,scheduled_at)WHEREattempts<max_attempts;
Enter fullscreen modeExit fullscreen mode

3. Điều Chỉnh Cấu Hình PostgreSQL

# postgresql.conf shared_buffers = 2GB # 25% of RAM effective_cache_size = 6GB # 75% of RAM work_mem = 50MB # For complex queries maintenance_work_mem = 512MB # For VACUUM 
Enter fullscreen modeExit fullscreen mode

4. Bảo Trì Định Kỳ

-
- Run dailyVACUUMANALYZEcache;VACUUMANALYZEjobs;-
- Or enable autovacuum (recommended)ALTERTABLEcacheSET(autovacuum_vacuum_scale_factor=0.1);
Enter fullscreen modeExit fullscreen mode

Kết Quả:
Sau 3 Tháng

Những gì tôi tiết kiệm được:

  • ✅ $100/tháng (không còn ElastiCache)
  • ✅ Giảm 50% độ phức tạp sao lưu
  • ✅ Bớt một dịch vụ cần giám sát
  • ✅ Triển khai đơn giản hơn (bớt một phụ thuộc)

Những gì tôi mất đi:

  • ❌ Độ trễ tăng ~0.5ms cho thao tác cache
  • ❌ Các cấu trúc dữ liệu đặc biệt của Redis (không cần dùng)

Tôi có làm lại không?Có, cho trường hợp sử dụng này.

Tôi có khuyến nghị áp dụng phổ biến không?Không.


Ma Trận Quyết Định

Thay thế Redis bằng Postgres nếu:

  • ✅ Bạn dùng Redis cho cache/phiên đơn giản
  • ✅ Tỷ lệ cache hit < 95% (nhiều thao tác ghi)
  • ✅ Bạn cần tính nhất quán giao dịch
  • ✅ Bạn chấp nhận thao tác chậm hơn 0.1-1ms
  • ✅ Bạn là nhóm nhỏ với nguồn lực vận hành hạn chế

Giữ lại Redis nếu:

  • ❌ Bạn cần 100k+ thao tác/giây
  • ❌ Bạn dùng cấu trúc dữ liệu của Redis (sorted sets, v.v.)
  • ❌ Bạn có đội ngũ vận hành chuyên trách
  • ❌ Độ trễ dưới mili-giây là yếu tố sống còn
  • ❌ Bạn đang thực hiện sao chép địa lý

Tài Nguyên

Tính năng PostgreSQL:

Công cụ:

Giải pháp thay thế:

  • Graphile Worker
    – Hàng đợi công việc dựa trên Postgres
  • pg-boss
    – Một hàng đợi Postgres khác

Tóm tắt

Tôi đã thay thế Redis bằng PostgreSQL cho:

  1. Bộ nhớ đệm → Bảng UNLOGGED
  2. Pub/Sub → LISTEN/NOTIFY
  3. Hàng đợi công việc → SKIP LOCKED
  4. Phiên làm việc → Bảng JSONB

Kết quả:

  • Tiết kiệm $100/tháng
  • Giảm độ phức tạp vận hành
  • Hơi chậm hơn (0.1-1ms) nhưng chấp nhận được
  • Đảm bảo tính nhất quán giao dịch

Khi nào nên làm điều này:

  • Ứng dụng nhỏ đến trung bình
  • Nhu cầu bộ nhớ đệm đơn giản
  • Muốn giảm thiểu các thành phần phức tạp

Khi nào KHÔNG nên làm điều này:

  • Yêu cầu hiệu năng cao (100k+ thao tác/giây)
  • Sử dụng tính năng đặc thù của Redis
  • Có đội ngũ vận hành chuyên trách

Bạn đã từng thay thế Redis bằng Postgres (hoặc ngược lại)?Kinh nghiệm của bạn là gì?
Hãy chia sẻ điểm chuẩn của bạn trong phần bình luận!
👇

P.S.

– Bạn muốn một bài viết tiếp theo về “Tính Năng Ẩn Của PostgreSQL” hay “Khi Nào Redis Thực Sự Tốt Hơn”?
Hãy cho tôi biết!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!