Mở đầu — vì sao bài này quan trọng
Có một lỗi kiến trúc rất phổ biến mà tôi thấy đi thấy lại ở nhiều đội kỹ thuật Việt Nam: họ đổ mọi thứ vào một cơ sở dữ liệu duy nhất, rồi ngạc nhiên khi mọi thứ chậm lại. Website bán hàng đang chạy mượt, đến cuối tháng sếp yêu cầu một báo cáo doanh thu theo tỉnh, theo danh mục, theo 12 tháng gần nhất. Bạn viết một câu truy vấn GROUP BY khổng lồ chạy trực tiếp trên database sản xuất. Kết quả: trong lúc báo cáo chạy, khách hàng thật đang thanh toán bị treo giỏ hàng, đơn hàng timeout, và bộ phận vận hành gọi điện hỏi "server sao lại chậm thế?".
Gốc rễ của vấn đề không phải là database yếu hay server thiếu RAM. Gốc rễ là bạn đang bắt một hệ thống được thiết kế cho OLTP (xử lý giao dịch) phải gánh thêm một loại công việc hoàn toàn khác bản chất — OLAP (phân tích). Đây là hai loại workload (khối lượng công việc) có đặc điểm truy cập dữ liệu, mô hình lưu trữ, và mục tiêu tối ưu ngược nhau gần như hoàn toàn.
Là người ra quyết định kiến trúc, bạn không cần tự tay viết engine database, nhưng bạn bắt buộc phải nhận diện được: workload trước mặt là OLTP hay OLAP? Bởi vì câu trả lời đó quyết định bạn chọn loại database nào, tách hệ thống ra sao, và tiêu tiền vào đâu. Bài này giúp bạn phân biệt rạch ròi hai loại workload này — nền tảng để sau này bạn ra quyết định chọn database và thiết kế pipeline dữ liệu một cách tỉnh táo.
Khái niệm cốt lõi
OLTP — Online Transaction Processing
OLTP là loại workload phục vụ cho các giao dịch nghiệp vụ hàng ngày của hệ thống. Hãy hình dung một sàn thương mại điện tử: khách thêm sản phẩm vào giỏ, đặt hàng, thanh toán, cập nhật địa chỉ giao hàng; nhân viên kho trừ tồn kho; hệ thống ghi nhận một lượt đăng nhập. Mỗi thao tác đó là một transaction.
Đặc điểm của OLTP:
- Nhiều giao dịch nhỏ, tần suất cực cao. Một hệ thống bán hàng có thể xử lý hàng nghìn thao tác mỗi giây, nhưng mỗi thao tác chỉ đụng đến một vài bản ghi. "Trừ 1 khỏi tồn kho sản phẩm X", "chèn 1 đơn hàng mới" — dữ liệu chạm vào rất ít.
- Thao tác ở cấp độ dòng (row-level). Bạn đọc/ghi trọn vẹn một hoặc vài hàng dữ liệu: toàn bộ thông tin của một khách hàng, một đơn hàng cụ thể. Bạn hiếm khi cần quét cả bảng.
- ACID là bắt buộc. Atomicity, Consistency, Isolation, Durability. Khi khách trả tiền, bạn không thể để trạng thái "đã trừ tiền nhưng chưa tạo đơn". Tính đúng đắn của giao dịch quan trọng hơn mọi thứ. Đây là lý do OLTP gần như luôn gắn với database quan hệ hỗ trợ transaction mạnh.
- Nhạy cảm với độ trễ (latency-sensitive), tính bằng mili-giây. Khách hàng bấm "Thanh toán" và chờ phản hồi. Nếu mất 3 giây, họ bỏ giỏ. Mục tiêu là mỗi query trả về trong vài mili-giây.
- Dữ liệu mới nhất, đang sống. OLTP làm việc với trạng thái hiện tại của hệ thống: tồn kho bây giờ, số dư tài khoản ngay lúc này.
- Nhiều người ghi đồng thời (high concurrency writes). Hàng nghìn người dùng cùng đọc và ghi cùng lúc, nên khả năng xử lý tranh chấp (locking, isolation) rất quan trọng.
- Công cụ tiêu biểu: PostgreSQL, MySQL, SQL Server, Oracle — các database quan hệ; và các NoSQL hướng key-value/document như MongoDB, DynamoDB cho một số ca cụ thể.
OLAP — Online Analytical Processing
OLAP là loại workload phục vụ cho phân tích, báo cáo, ra quyết định kinh doanh. Đây là nơi câu hỏi "doanh thu theo tỉnh trong 12 tháng qua là bao nhiêu?", "khách hàng nào có giá trị vòng đời cao nhất?", "sản phẩm nào bán chạy theo mùa?" được trả lời.
Đặc điểm của OLAP:
- Ít truy vấn nhưng mỗi truy vấn rất nặng. Thay vì hàng nghìn query nhỏ mỗi giây, OLAP có thể chỉ chạy vài chục hoặc vài trăm query mỗi giờ, nhưng mỗi query quét qua hàng triệu, hàng tỷ bản ghi.
- Thao tác theo cột (column-level), tổng hợp (aggregation). Câu hỏi phân tích thường có dạng
SUM,AVG,COUNT,GROUP BYtrên vài cột nhưng trải qua toàn bộ lịch sử. Bạn không cần lấy trọn vẹn từng dòng — bạn cần tổng của cột doanh thu. - Đọc là chính, ghi theo lô (batch). Dữ liệu thường được nạp định kỳ (mỗi giờ, mỗi đêm) qua quy trình ETL/ELT, rồi được đọc đi đọc lại. Rất ít cập nhật lẻ tẻ.
- Chịu được độ trễ cao hơn. Một báo cáo chạy trong 10 giây, thậm chí vài phút, thường vẫn chấp nhận được. Analyst không bấm "Thanh toán" — họ chờ được.
- Dữ liệu lịch sử, đôi khi hơi cũ. OLAP làm việc với dữ liệu tích lũy theo thời gian. Nếu số liệu trễ vài giờ so với thực tế thì thường không sao.
- ACID không phải ưu tiên hàng đầu. Vì chủ yếu đọc và dữ liệu được nạp theo lô, bạn không cần isolation nghiêm ngặt như OLTP.
- Công cụ tiêu biểu: các data warehouse như Google BigQuery, Amazon Redshift, Snowflake, ClickHouse, Apache Druid.
SUM cột doanh thu, hệ thống chỉ đọc đúng cột đó thay vì lôi cả bảng lên; đồng thời dữ liệu cùng cột nén rất tốt, tiết kiệm I/O đáng kể.Bảng so sánh nhanh
| Tiêu chí | OLTP | OLAP |
|---|---|---|
| Mục đích | Vận hành nghiệp vụ | Phân tích, ra quyết định |
| Mẫu truy vấn | Nhiều query nhỏ | Ít query, quét lớn |
| Thao tác | Đọc/ghi theo dòng | Tổng hợp theo cột |
| Ghi | Liên tục, lẻ | Theo lô (batch) |
| Độ trễ mong muốn | Mili-giây | Giây đến phút |
| ACID | Bắt buộc | Không ưu tiên |
| Lưu trữ | Row-oriented | Column-oriented |
| Dữ liệu | Hiện tại, đang sống | Lịch sử, tích lũy |
| Ví dụ công cụ | PostgreSQL, MySQL | BigQuery, ClickHouse |
Tình huống thực tế
Ví dụ 1 — Sàn thương mại điện tử Việt Nam và báo cáo giết chết checkout
Một công ty thương mại điện tử tầm trung ở TP.HCM (gọi là ShopViet cho dễ) chạy toàn bộ trên một cụm PostgreSQL. Ban ngày mọi thứ ổn: khoảng 800 đơn/giờ, mỗi query đặt hàng trả về dưới 20ms. Đến khi phòng kinh doanh yêu cầu một dashboard cập nhật doanh thu theo danh mục và theo giờ, đội kỹ thuật viết một query chạy trực tiếp trên database chính, tự động refresh mỗi 5 phút.
Query đó JOIN bảng đơn hàng (12 triệu dòng) với bảng chi tiết đơn (48 triệu dòng), GROUP BY danh mục và giờ, quét toàn bộ 90 ngày. Mỗi lần chạy mất 8–12 giây và chiếm trọn I/O của đĩa. Hậu quả: cứ 5 phút một lần, tỷ lệ timeout ở bước thanh toán tăng vọt, khách phàn nàn "bấm thanh toán mãi không được".
Diễn giải: đây là kinh điển của việc trộn OLAP vào hệ thống OLTP. Query phân tích (quét lớn, aggregation) cạnh tranh tài nguyên trực tiếp với giao dịch thật (nhỏ, nhạy latency). Đội đã giải quyết bằng cách tách một read replica riêng chỉ để chạy báo cáo, và sau đó chuyển hẳn phần phân tích sang một kho column-oriented (họ chọn ClickHouse), nạp dữ liệu mỗi 15 phút. Sau khi tách, query báo cáo giảm còn dưới 1 giây nhờ lưu trữ theo cột, và quan trọng hơn: checkout không còn bị ảnh hưởng.
Bài học: đừng để workload phân tích chạy trên database phục vụ giao dịch thật. Ngay cả read replica cũng chỉ là giải pháp tạm; khi dữ liệu lớn lên, một hệ column-oriented mới thực sự phù hợp với OLAP.
Ví dụ 2 — Ngân hàng số và ranh giới sống còn của ACID
Một ngân hàng số ở Đông Nam Á tách rất rõ hai thế giới. Hệ thống core banking xử lý chuyển tiền, thanh toán, ghi sổ — thuần OLTP trên Oracle với ACID nghiêm ngặt. Khi khách chuyển 5 triệu đồng, không có chuyện "trừ ở tài khoản A nhưng chưa cộng vào B". Mỗi giao dịch phải nguyên tử, cô lập, bền vững — vì đây là tiền thật.
Song song đó, họ có một bộ phận phân tích rủi ro tín dụng và phát hiện gian lận, cần phân tích hành vi giao dịch của hàng triệu khách qua nhiều năm. Nếu chạy phân tích này trên core banking, hai điều tồi tệ xảy ra: một là làm chậm giao dịch thật (rủi ro vận hành nghiêm trọng với ngân hàng), hai là các query nặng có thể giữ lock lâu gây nghẽn. Vì thế họ đẩy dữ liệu sang một data warehouse (Snowflake), nơi analyst tự do quét hàng tỷ dòng mà không đụng đến một xu nào của hệ thống sống.
Diễn giải: ranh giới ở đây không chỉ về hiệu năng mà còn về tính đúng đắn và an toàn. OLTP của ngân hàng đặt ACID lên trên hết; OLAP chấp nhận dữ liệu trễ vài giờ để đổi lấy khả năng phân tích quy mô lớn mà không đe dọa hệ thống giao dịch.
Bài học: khi workload đụng đến tiền hoặc trạng thái không được phép sai, đó là dấu hiệu mạnh của OLTP cần ACID. Tách phân tích ra khỏi nó không phải để "cho đẹp kiến trúc" mà là yêu cầu về quản trị rủi ro.
Ví dụ 3 — Startup logistics và cái bẫy "một database cho tất cả"
Một startup giao vận ở Hà Nội khi mới ra mắt dùng đúng một MongoDB cho mọi thứ: theo dõi đơn giao (OLTP — cập nhật trạng thái shipper, vị trí đơn hàng liên tục) lẫn báo cáo vận hành cho khách hàng doanh nghiệp (OLAP — "tỷ lệ giao đúng hẹn theo quận trong quý"). Ban đầu quy mô nhỏ nên ổn. Đến khi lên 50.000 đơn/ngày, các báo cáo cho khách doanh nghiệp bắt đầu mất 30–60 giây và làm chậm luôn app của shipper.
Họ mắc kẹt vì đã coi "database" là một quyết định duy nhất, thay vì nhận ra mình có hai workload khác bản chất. Giải pháp: giữ MongoDB cho OLTP (cập nhật trạng thái đơn theo thời gian thực), và dựng thêm một pipeline nạp dữ liệu hằng đêm sang BigQuery cho báo cáo. Chi phí phân tích giảm mạnh, báo cáo chạy trong vài giây, và app shipper trở lại mượt.
Diễn giải: cái bẫy phổ biến ở startup là tối ưu hóa quá sớm cho sự đơn giản — "một database cho tất cả" — mà không lường trước rằng hai workload sẽ xung đột khi quy mô tăng.
Bài học: giai đoạn đầu dùng chung một database là hợp lý, nhưng phải nhận diện sớm rằng bạn đang gánh hai loại workload, để khi tín hiệu xung đột xuất hiện (báo cáo chậm dần, ảnh hưởng vận hành) thì bạn biết ngay hướng tách, thay vì hoảng loạn nâng cấp phần cứng vô ích.
Hướng dẫn từng bước
Khi đứng trước một yêu cầu và cần xác định workload thuộc loại nào, hãy đi theo các bước sau:
- Đặt câu hỏi "ai dùng và để làm gì?" Nếu là người dùng cuối/hệ thống thực hiện thao tác nghiệp vụ (đặt hàng, thanh toán, cập nhật hồ sơ) → nghiêng về OLTP. Nếu là analyst, lãnh đạo, dashboard tổng hợp → nghiêng về OLAP.
- Nhìn vào mẫu truy cập dữ liệu. Query chạm ít bản ghi và cần trọn vẹn một dòng → OLTP. Query quét nhiều bản ghi và tổng hợp trên vài cột (
SUM,GROUP BY) → OLAP.
- Xác định yêu cầu độ trễ. Phản hồi phải trong mili-giây, người dùng đang chờ ngay → OLTP. Chấp nhận vài giây đến vài phút → OLAP.
- Đánh giá yêu cầu về tính đúng đắn (ACID). Sai một giao dịch là mất tiền/hỏng trạng thái → OLTP cần ACID mạnh. Dữ liệu trễ vài giờ vẫn dùng được, chỉ đọc là chính → OLAP.
- Xem mô hình ghi. Ghi liên tục, lẻ tẻ, nhiều người đồng thời → OLTP. Nạp theo lô định kỳ → OLAP.
- Kết luận và chọn hướng lưu trữ. OLTP → row-oriented (PostgreSQL, MySQL...). OLAP → column-oriented / data warehouse (BigQuery, ClickHouse, Redshift...).
- Nếu có cả hai (rất phổ biến), tách chúng ra. Giữ OLTP làm hệ thống nguồn sự thật (source of truth), rồi dựng một pipeline nạp dữ liệu sang hệ OLAP. Không bắt một hệ gánh cả hai khi quy mô đã lớn.
Lỗi thường gặp & mẹo
Lỗi 1 — Chạy báo cáo phân tích trực tiếp trên database sản xuất. Đây là lỗi số một. Query OLAP nặng sẽ tranh tài nguyên với giao dịch thật, làm chậm hoặc treo trải nghiệm người dùng. Mẹo: tối thiểu hãy dùng read replica; lý tưởng là đẩy sang hệ OLAP riêng.
Lỗi 2 — Nghĩ rằng "chọn database" là một quyết định duy nhất. Nhiều đội tranh cãi "PostgreSQL hay MongoDB" mà quên hỏi "workload của tôi là OLTP hay OLAP, hay cả hai?". Mẹo: luôn phân loại workload trước, rồi mới chọn công cụ.
Lỗi 3 — Nhồi OLAP vào NoSQL vì "NoSQL nhanh". NoSQL nhanh cho truy cập điểm (point lookup) kiểu OLTP, nhưng nhiều loại lại rất tệ với aggregation quét lớn. Mẹo: đừng để tiếng "nhanh" đánh lừa; hãy khớp công cụ với mẫu truy cập thật.
Lỗi 4 — Trì hoãn việc tách đến khi hệ thống đã sập. Chờ đến lúc báo cáo làm chết checkout mới xử lý là quá muộn. Mẹo: theo dõi thời gian query báo cáo; khi nó tăng dần và bắt đầu ảnh hưởng OLTP, đó là tín hiệu tách.
Lỗi 5 — Nghĩ column-oriented luôn tốt hơn. Nếu bạn cần lấy trọn vẹn một bản ghi thường xuyên (OLTP), lưu theo cột lại chậm hơn vì phải ghép các cột rời rạc lại. Mẹo: row-oriented cho OLTP, column-oriented cho OLAP — mỗi bên có chỗ đứng của nó.
Mẹo bổ sung: ở quy mô nhỏ, việc dùng chung một database cho cả hai là hoàn toàn hợp lý và tiết kiệm. Đừng over-engineer từ ngày đầu. Cái quan trọng là bạn biết mình đang gánh hai workload, để nhận ra đúng thời điểm cần tách.
Bài tập thực hành
- Phân loại workload. Với mỗi tình huống sau, xác định OLTP hay OLAP và giải thích ngắn gọn dựa trên mẫu truy cập, độ trễ, và yêu cầu ACID:
- Chẩn đoán một hệ thống thật. Lấy một hệ thống bạn đang làm (hoặc từng dùng). Liệt kê 5 loại truy vấn thường chạy, phân loại từng cái là OLTP hay OLAP, rồi trả lời: hệ thống đó có đang bắt một database gánh cả hai không? Nếu có, dấu hiệu xung đột nào đã hoặc sẽ xuất hiện?
- Đề xuất kiến trúc tách. Với một cửa hàng online giả định có 20.000 đơn/ngày và cần dashboard doanh thu cập nhật mỗi 30 phút, hãy phác một sơ đồ đơn giản: database OLTP nào cho vận hành, hệ OLAP nào cho phân tích, và cơ chế nạp dữ liệu giữa hai bên (tần suất, batch hay stream). Ghi lại lý do chọn.
Tóm tắt
- OLTP phục vụ giao dịch nghiệp vụ hàng ngày: nhiều query nhỏ, thao tác theo dòng, ACID bắt buộc, độ trễ mili-giây, ghi liên tục, dữ liệu đang sống, lưu row-oriented (PostgreSQL, MySQL...).
- OLAP phục vụ phân tích và ra quyết định: ít query nhưng quét lớn, tổng hợp theo cột, ghi theo lô, chịu được độ trễ cao, ACID không ưu tiên, dữ liệu lịch sử, lưu column-oriented (BigQuery, ClickHouse, Redshift...).
- Cùng một dữ liệu doanh nghiệp, nhưng cách truy cập quyết định workload thuộc loại nào.
- Lỗi kinh điển là bắt một database phục vụ cả hai — query phân tích nặng sẽ giết chết trải nghiệm giao dịch. Ở quy mô nhỏ dùng chung là ổn; khi lớn lên, hãy tách và dựng pipeline nạp dữ liệu từ OLTP sang OLAP.
- Quy trình ra quyết định: hỏi ai dùng để làm gì → nhìn mẫu truy cập → xác định độ trễ → đánh giá ACID → xem mô hình ghi → chọn loại lưu trữ → tách nếu có cả hai.