Ứng dụng giao dịch chứng khoán
Hiểu đề
Yêu cầu chức năng
- Người dùng có thể xem bảng giá các mã đang theo dõi, cập nhật theo thời gian thực
- Người dùng có thể đặt lệnh mua/bán (lệnh giới hạn, lệnh thị trường) và hủy lệnh chưa khớp
- Người dùng thấy trạng thái lệnh (chờ khớp, khớp một phần, khớp hết, đã hủy, bị từ chối) ngay khi sàn báo
- Người dùng có thể xem tiền mặt, sức mua và danh mục cổ phiếu đang nắm giữ
Yêu cầu phi chức năng
- Giá từ lúc nhận feed của sàn đến màn hình p95 < 500 ms
- Đặt lệnh: từ lúc bấm đến khi lệnh được gửi lên sàn p95 < 300 ms (phần trong hệ thống)
- 0 lệnh gửi trùng, 0 lệnh mất; trạng thái mọi lệnh khớp với sàn sau đối soát cuối ngày
- Sức mua không bao giờ âm dù người dùng đặt nhiều lệnh song song
- Độ sẵn sàng 99,95% trong giờ giao dịch; đỉnh tải dồn vào phiên ATO/ATC
Ngoài phạm vi: Khớp lệnh (sàn làm); Giao dịch ký quỹ (margin), phái sinh, trái phiếu; Nạp/rút tiền qua ngân hàng và mở tài khoản eKYC; Biểu đồ kỹ thuật và dữ liệu lịch sử nhiều năm.
Thực thể và API
| Method | Endpoint | Ghi chú |
|---|---|---|
| GET | /v1/quotes?symbols=FPT,VNM,HPG | Snapshot giá hiện tại kèm seq, đọc từ Redis. |
| WS | /v1/stream | Gửi {subscribe: [mã]}; nhận frame giá đã gộp mỗi 250 ms và sự kiện lệnh của chính mình. |
| POST | /v1/orders | Header Idempotency-Key; body {symbol, side, type, qty, price}. Trả 201 với orderId, status PENDING_NEW; 422 nếu vượt sức mua hoặc sai bước giá. |
| DELETE | /v1/orders/{id} | Yêu cầu hủy: chuyển PENDING_CANCEL và gửi lệnh hủy lên sàn; chỉ CANCELED khi sàn xác nhận. |
| GET | /v1/orders?status=open&cursor= | Sổ lệnh trong ngày và lệnh đang mở. |
| GET | /v1/accounts/me/portfolio | Tiền mặt, sức mua, danh mục, lãi/lỗ tạm tính theo giá hiện tại. |
Thiết kế tổng thể
Đi lần lượt từng yêu cầu chức năng. Mục tiêu ở đây là chạy đúng, việc tối ưu để dành cho phần đào sâu.
1) Người dùng có thể xem bảng giá real-time
Công ty nhận feed giá từ sàn hoặc nhà cung cấp dữ liệu. Ingest chuẩn hóa, gộp theo mã rồi phát qua pub/sub tới các WebSocket gateway, gateway chỉ đẩy mã mà từng client đang xem.
- 1Ingest nhận tick từ feed, cập nhật snapshot của mã trong Redis kèm seq
- 2Mỗi 250 ms ingest phát một lô gồm các mã vừa đổi lên kênh pub/sub
- 3WS gateway nhận lô, lọc theo danh sách mã mỗi client subscribe, gửi một frame cho mỗi client
- 4Client mới kết nối hoặc thấy seq bị hụt thì gọi GET /quotes để lấy snapshot
Nguồn: Grokking Modern System Design · ch.18 Pub-sub ↗ · bài "Introduction to Pub-sub", mục "Use cases of pub-sub" (push thay vì polling) ; Redis in Action ↗ · ch.3 · §3.6 Publish/subscribe (tr. 54–57) ; Grokking Modern System Design · ch.36 WhatsApp ↗ · bài "Detailed Design of WhatsApp", mục "Connection with a WebSocket server" ; Chưa có nguồn trong thư viện: giao thức FIX và quy tắc thị trường chứng khoán (ATO/ATC, T+2, lô 100) không có trong sách nào của thư viện; chỉ nêu khái niệm cơ bản
2) Người dùng có thể đặt và hủy lệnh
Order API kiểm tra lệnh và giữ tiền trong cùng một transaction với việc tạo Order. Lệnh chỉ được gửi lên sàn sau khi đã lưu bền, qua outbox và một order gateway giữ phiên kết nối FIX với sàn.
- 1Client gửi POST /v1/orders kèm Idempotency-Key
- 2Order API kiểm tra giờ giao dịch, giá trần/sàn, bước giá, lô 100
- 3Một transaction: giữ tiền (UPDATE có điều kiện sức mua), tạo Order PENDING_NEW, ghi outbox
- 4Order gateway đọc outbox, gửi NewOrderSingle lên sàn với ClOrdID = order_id
- 5Sàn trả ack thì Order sang NEW; bị từ chối thì REJECTED và nhả tiền
Nguồn: Microservices Patterns ↗ (.zip) · ch.3 · §3.3.7 Transactional messaging (tr. 97–100) ; Microservices Patterns ↗ (.zip) · ch.4 · §4.3.2 Countermeasures for handling the lack of isolation (trạng thái *_PENDING, tr. 128–132) ; Chưa có nguồn trong thư viện: giao thức FIX và quy tắc thị trường chứng khoán (ATO/ATC, T+2, lô 100) không có trong sách nào của thư viện; chỉ nêu khái niệm cơ bản
3) Người dùng thấy trạng thái lệnh ngay khi sàn báo
Sàn gửi execution report cho mỗi lần xác nhận, khớp hoặc hủy. Gateway ghi report và chuyển trạng thái theo state machine, cập nhật số dư, rồi đẩy sự kiện tới đúng người dùng.
- 1Order gateway nhận execution report, lưu bản tin gốc
- 2Ghi Execution theo exec_id (bỏ qua nếu đã có), cập nhật Order theo state machine
- 3Cùng transaction: bút toán trừ tiền theo giá khớp, cộng cổ phiếu chờ về
- 4Phát sự kiện order_updated, WS gateway đẩy tới các phiên của người đó
Nguồn: Microservices Patterns ↗ (.zip) · ch.3 · §3.3.6 Handling duplicate messages (tr. 95–97) ; Microservices Patterns ↗ (.zip) · ch.4 · §4.3.2 Countermeasures for handling the lack of isolation (tr. 128–132) ; Grokking Modern System Design · ch.18 Pub-sub ↗ · bài "Design of a Pub-sub System" (đẩy sự kiện tới subscriber) ; Chưa có nguồn trong thư viện: giao thức FIX và quy tắc thị trường chứng khoán (ATO/ATC, T+2, lô 100) không có trong sách nào của thư viện; chỉ nêu khái niệm cơ bản
4) Người dùng có thể xem số dư và danh mục
Số dư được tính từ sổ cái bút toán và có bảng tổng hợp cập nhật cùng transaction. Lãi/lỗ tạm tính ghép danh mục với giá hiện tại trong Redis.
- 1Client gọi GET /portfolio
- 2API đọc bảng Account/Position của tài khoản
- 3Ghép với giá hiện tại từ Redis để tính giá trị và lãi/lỗ
- 4Khi có lệnh khớp, app nhận sự kiện qua WebSocket và tải lại phần thay đổi
Nguồn: Redis in Action ↗ · ch.3 · §3.4 Hashes (tr. 48–50) ; Grokking Modern System Design · ch.9 Databases ↗ · bài "Types of Databases", mục "Relational databases" (concurrency, tính toàn vẹn) ; Chưa có nguồn trong thư viện: mô hình sổ cái bút toán không có trong sách; giữ lại như thiết kế của khoá
Hạ tầng theo quy mô
Cùng một sản phẩm, ba mức người dùng. Chọn từng mức để xem sơ đồ, phép tính và cấu hình server thay đổi thế nào.
Tải chỉ dồn vào khoảng 5 giờ giao dịch. Hai instance API, hai WS gateway, một Redis nhỏ cho giá và pub/sub, Postgres HA từ ngày đầu vì đây là tiền.
Ước lượng
- Giờ giao dịch tính tròn 5 h = 18.000 s; lệnh: 10.000 × 2 / 18.000 ≈ 1,1 lệnh/s, đỉnh ATO/ATC ×10 ≈ 11 lệnh/s
- Kết nối: 40% DAU mở app trong phiên = 4.000 WebSocket → ceil(4.000 / 20.000) = 1 → 2 gateway để dự phòng
- Đẩy giá: 4.000 × 4 frame/s (gộp 250 ms) = 16.000 frame/s × 300 B = 4,8 MB/s ≈ 38 Mbps
- REST: 10.000 × 50 req / 18.000 ≈ 28 req/s, đỉnh ×5 ≈ 139 req/s → ceil(139 / 600) = 1 → 2 instance
- Lưu trữ: 20.000 lệnh × 3 bản ghi (lệnh, khớp, bút toán) × 0,5 KB = 30 MB/ngày × 250 ngày ≈ 7,5 GB/năm
Nguồn con số: Grokking Modern System Design · ch.5 Back-of-the-envelope Calculations ↗ · bài "Examples of Resource Estimation", mục "Number of servers required", "Bandwidth requirements" ; Quy tắc ước lượng của khoá học · /hoc/chon-server (500 req/s mỗi vCPU, chạy 60% CPU, Redis ×1,3; số frame/s, kết nối mỗi gateway và hạn mức phiên FIX là giả định của khoá học)
Bảng hạ tầng
| Thành phần | Chọn | Số lượng | Cấu hình | Vì sao |
|---|---|---|---|---|
| API | Node.js (NestJS), Docker | 2 | 2 vCPU · 4 GB RAM | 139 req/s lúc đỉnh chỉ cần 1 instance, chạy 2 để không gián đoạn trong phiên. |
| WS gateway | Node.js + ws | 2 | 2 vCPU · 4 GB RAM | Mỗi instance 2.000 kết nối và 8.000 frame/s; giả sử ≈ 10.000 frame/s mỗi vCPU khi frame đã serialize sẵn. |
| Ingest giá | Node.js, nhận feed từ sàn/nhà cung cấp dữ liệu | 1 active + 1 standby | 2 vCPU · 4 GB RAM | Khoảng 1.600 mã, vài nghìn tick/s là nhẹ; standby nhận feed song song để chuyển nhanh. |
| Redis | Redis managed | 1 | 1 vCPU · 2 GB | Snapshot giá, pub/sub giá và sự kiện lệnh (Redis in Action §3.6), idempotency key. |
| Database | PostgreSQL managed (HA) | 1 primary + 1 standby | 2 vCPU · 8 GB · 100 GB SSD | 11 lệnh/s và 7,5 GB/năm là nhỏ; standby đồng bộ để không mất bút toán khi primary chết. |
| Order gateway | Node.js, phiên FIX tới sàn | 1 active + 1 passive | 2 vCPU · 4 GB RAM | Giả sử sàn cho mỗi phiên 500 lệnh/s; 11 lệnh/s chỉ cần 1 phiên. |
- Điểm xuất phát: API và WS gateway tách riêng vì tải và cách scale khác nhau
- Postgres HA ngay từ đầu, giữ tiền bằng UPDATE có điều kiện
- Giá đã gộp 250 ms ngay ở ingest nên gateway nhẹ
Khi lên hàng trăm nghìn người mở app trong phiên, WS gateway bị giới hạn bởi CPU đẩy frame, và phiên ATC đưa hàng nghìn lệnh/s vào một Postgres nhỏ. Cần nhiều gateway, DB lớn hơn và nhiều phiên FIX.
Cấu hình và chi phí là ước lượng để học và trình bày trong phỏng vấn. Xem cách tính ở bài Chọn server.
So sánh nhanh ba mức
| Thành phần | Mức 1 | Mức 2 | Mức 3 |
|---|---|---|---|
| Người dùng | 10.000 DAU (công ty chứng khoán nhỏ) | 1 triệu DAU | 10 triệu DAU (giữ ở 10 triệu vì cả thị trường chỉ vài triệu tài khoản hoạt động; tải dồn vào 5 giờ và vài phút ATO/ATC) |
| Chi phí | ≈ 400–1.000 USD/tháng (ước lượng, chưa tính phí kết nối sàn và phí feed giá) | ≈ 20.000–40.000 USD/tháng (ước lượng, egress đẩy giá chiếm phần lớn) | ≈ 200.000–400.000 USD/tháng (ước lượng, egress đẩy giá chiếm gần một nửa) |
| API | Node.js (NestJS), Docker 2 × 2 vCPU · 4 GB RAM | Node.js (NestJS), autoscale 2–24 (autoscale, scale sẵn trước ATO) × 2 vCPU · 4 GB RAM | Node.js trên Kubernetes, 3 AZ 232 pod (HPA, scale theo lịch trước ATO/ATC) × 2 vCPU · 4 GB mỗi pod |
| WS gateway | Node.js + ws 2 × 2 vCPU · 4 GB RAM | Node.js + ws, autoscale 40 × 4 vCPU · 8 GB RAM | Node.js trên Kubernetes 400 pod (chia 2 region) × 4 vCPU · 8 GB mỗi pod |
| Ingest giá | Node.js, nhận feed từ sàn/nhà cung cấp dữ liệu 1 active + 1 standby × 2 vCPU · 4 GB RAM | Chưa cần | Chưa cần |
| Redis | Redis managed 1 × 1 vCPU · 2 GB | ElastiCache / Memorystore 1 primary + 1 replica × 2 vCPU · 4 GB | Chưa cần |
| Database | PostgreSQL managed (HA) 1 primary + 1 standby × 2 vCPU · 8 GB · 100 GB SSD | PostgreSQL managed (HA), partition theo ngày 1 primary + 1 standby + 1 read replica × 16 vCPU · 64 GB · 1 TB SSD | PostgreSQL shard theo account_id (Citus hoặc tự chia) 16 shard × (1 primary + 1 standby đồng bộ) × 8 vCPU · 32 GB · 500 GB SSD mỗi node |
| Order gateway | Node.js, phiên FIX tới sàn 1 active + 1 passive × 2 vCPU · 4 GB RAM | Node.js, phiên FIX chia theo hash(account) 3 phiên, mỗi phiên active + passive × 2 vCPU · 4 GB RAM | Node.js/Go, đặt gần sàn 24 phiên FIX, mỗi phiên active + passive × 2 vCPU · 4 GB mỗi pod |
| Load balancer | Chưa cần | ALB / Cloud Load Balancing 1 × Managed, hỗ trợ WebSocket | Chưa cần |
| Queue + worker | Chưa cần | SQS / Pub/Sub + Node.js consumer 1 queue, 4 worker × 1 vCPU · 2 GB mỗi worker | Chưa cần |
| Global LB | Chưa cần | Chưa cần | Cloudflare / Global Load Balancer, 2 region (HN, HCM) 1 × Định tuyến theo vị trí, có WAF |
| Kafka | Chưa cần | Chưa cần | MSK / Confluent 3 broker (1 mỗi AZ) × Topic order-events 32 partition theo account_id, RF 3 |
| Redis Cluster | Chưa cần | Chưa cần | ElastiCache Redis Cluster 3 shard × (1 primary + 1 replica) × 2 vCPU · 8 GB mỗi node |
| Worker đối soát | Chưa cần | Chưa cần | Node.js consumer trên Kubernetes 8 pod × 1 vCPU · 2 GB mỗi pod |
Đào sâu
Mỗi câu hỏi đi từ cách đơn giản đến cách người phỏng vấn mong đợi ở cấp senior.
1) Đẩy giá real-time tới hàng trăm nghìn người đang mở app thế nào?
Mỗi người một request mỗi giây, đa số trả về dữ liệu không đổi. 400.000 người là 400.000 req/s chỉ để đọc giá.
Đánh đổi: Dễ làm nhưng tốn tài nguyên nhất và vẫn trễ tới 1 s.
Nguồn: Grokking Modern System Design · ch.18 Pub-sub ↗ · bài "Introduction to Pub-sub", mục "Use cases of pub-sub" (Improved performance: push, không cần hỏi lại)
Gateway subscribe pub/sub (Grokking ch.18) và gửi từng tick xuống client. Trễ thấp, không có request rỗng.
Đánh đổi: Lúc ATC một mã có thể nhảy hàng chục lần mỗi giây; gửi từng tick làm CPU gateway và mạng di động của client quá tải.
Nguồn: Grokking Modern System Design · ch.18 Pub-sub ↗ · bài "Introduction to Pub-sub", mục "Use cases of pub-sub" ; Grokking Modern System Design · ch.36 WhatsApp ↗ · bài "Detailed Design of WhatsApp", mục "Connection with a WebSocket server"
Ingest giữ giá mới nhất mỗi mã, cứ 250 ms phát một lô các mã vừa đổi. Gateway ghép các mã một client đang xem thành một frame duy nhất, serialize một lần dùng chung. Mỗi mã có seq tăng dần; client thấy hụt seq thì lấy lại snapshot. Client chậm có buffer giới hạn, đầy thì bỏ frame cũ vì chỉ giá mới nhất có ý nghĩa.
Đánh đổi: Người dùng không thấy từng tick trung gian; với nhà đầu tư cá nhân điều này chấp nhận được vì lệnh vẫn khớp trên sàn theo giá thật.
Nguồn: Chưa có nguồn trong thư viện: conflation (gộp tick) và backpressure theo client không được sách nào mô tả; giữ lại vì là kỹ thuật cốt lõi của bảng giá ; Grokking Modern System Design · ch.18 Pub-sub ↗ · bài "Design of a Pub-sub System", mục "Broker" (broker phân phối tới subscriber)
2) Gửi lệnh lên sàn bên ngoài thế nào để không mất và không trùng?
API gửi lệnh lên sàn rồi mới ghi Order. Process chết giữa hai bước thì có lệnh sống trên sàn mà hệ thống không biết; client timeout bấm lại thì có thêm một lệnh nữa.
Đánh đổi: Khách mua gấp đôi khối lượng, công ty phải chịu phần chênh lệch.
Nguồn: Microservices Patterns ↗ (.zip) · ch.3 · §3.3.7 Transactional messaging, mục "Using a database table as a message queue" (cập nhật DB và gửi message phải nguyên tử, tr. 97–98)
Order được tạo PENDING_NEW trước khi gửi, unique theo (account, idempotency_key) nên bấm lại chỉ trả về lệnh cũ. Gửi lại lên sàn luôn dùng cùng ClOrdID = order_id để sàn nhận ra lệnh trùng.
Đánh đổi: Vẫn còn khoảng hở giữa lúc commit DB và lúc gửi; cần cơ chế chắc chắn mọi lệnh đã commit đều được gửi đúng một lần.
Nguồn: Microservices Patterns ↗ (.zip) · ch.3 · §3.3.6 Handling duplicate messages (tr. 95–97), mục "Writing idempotent message handlers" (tạo order với ID do client cấp là idempotent, tr. 96) ; Building Microservices ↗ (.zip) · ch.11 Microservices at Scale · mục "Idempotency" (tr. 215)
Transaction tạo Order ghi luôn bản tin outbox (Microservices Patterns §3.3.7); gateway đọc outbox và gửi qua phiên FIX, mỗi phiên chạy active-passive với sequence number liên tục. Trạng thái chỉ đổi bằng UPDATE ... WHERE status IN (trạng thái hợp lệ) AND version = ?, ví dụ PENDING_NEW → NEW → PARTIALLY_FILLED → FILLED, hoặc → PENDING_CANCEL → CANCELED. Gửi xong mà không nhận phản hồi thì chuyển UNKNOWN và hỏi trạng thái lệnh từ sàn, không gửi lệnh mới.
Đánh đổi: Nhiều thành phần hơn và mỗi phiên FIX là một điểm tuần tự; số phiên bị giới hạn bởi hạn mức sàn cấp cho công ty.
Nguồn: Microservices Patterns ↗ (.zip) · ch.3 · §3.3.7 Transactional messaging (tr. 97–100), mục "Using a database table as a message queue" (transactional outbox) ; Microservices Patterns ↗ (.zip) · ch.4 · §4.3.2 Countermeasures for handling the lack of isolation (semantic lock, *_PENDING) ; Chưa có nguồn trong thư viện: giao thức FIX và quy tắc thị trường chứng khoán (ATO/ATC, T+2, lô 100) không có trong sách nào của thư viện; chỉ nêu khái niệm cơ bản
3) Giữ trạng thái lệnh đồng bộ với sàn khi bản tin có thể đến trễ, trùng hoặc mất?
Nhận bản tin nào áp bản tin đó. Mất kết nối vài giây là mất luôn các lần khớp trong khoảng đó; bản tin đến hai lần thì trừ tiền hai lần.
Đánh đổi: Số dư sai mà không ai phát hiện cho đến khi khách khiếu nại.
Nguồn: Microservices Patterns ↗ (.zip) · ch.3 · §3.3.6 Handling duplicate messages (khi lỗi, bản tin có thể bị giao nhiều lần hoặc sai thứ tự, tr. 95–96)
Bảng executions có unique exec_id, bản tin trùng bị bỏ qua (Microservices Patterns §3.3.6). Chỉ cập nhật Order khi cum_qty mới lớn hơn giá trị đang lưu, nên bản tin đến sai thứ tự không làm lùi trạng thái.
Đánh đổi: Chặn được trùng và sai thứ tự nhưng vẫn không biết mình đã mất bản tin nào.
Nguồn: Microservices Patterns ↗ (.zip) · ch.3 · §3.3.6 Handling duplicate messages (tr. 95–97), mục "Tracking messages and discarding duplicates" (lưu id đã xử lý, INSERT fail khi trùng, tr. 96–97)
Phiên FIX đánh số mọi bản tin; thấy hụt seq thì yêu cầu sàn gửi lại đoạn thiếu. Mỗi 1 phút worker hỏi sàn trạng thái các lệnh đang mở quá lâu hoặc UNKNOWN. Cuối ngày đối chiếu toàn bộ lệnh và khớp với file kết quả từ sàn; chênh lệch nào cũng thành ticket và bút toán điều chỉnh có kiểm duyệt.
Đánh đổi: Tốn công xây quy trình và luôn cần người xử lý ngoại lệ; giữa các lần đối soát trạng thái có thể sai trong thời gian ngắn.
Nguồn: Chưa có nguồn trong thư viện: giao thức FIX và quy tắc thị trường chứng khoán (ATO/ATC, T+2, lô 100) không có trong sách nào của thư viện; chỉ nêu khái niệm cơ bản ; Grokking Modern System Design · ch.23 Distributed Task Scheduler ↗ · bài "Design Considerations", mục "Task idempotency" ; Chưa có nguồn trong thư viện: quy trình đối soát với sàn không có trong sách
4) Làm sao sức mua không âm khi người dùng đặt nhiều lệnh cùng lúc từ nhiều thiết bị?
Hai lệnh đến cùng lúc đều đọc thấy đủ tiền và đều được chấp nhận. Lỗi race kinh điển, rất dễ xảy ra vào phiên ATC.
Đánh đổi: Tài khoản âm tiền, công ty chứng khoán phải ứng trước.
Nguồn: Microservices Patterns ↗ (.zip) · ch.4 · §4.3.1 Overview of anomalies (lost updates, tr. 127–128)
UPDATE accounts SET reserved = reserved + :amt WHERE id = :id AND cash - reserved >= :amt, trong cùng transaction tạo Order. Câu lệnh nguyên tử nên chỉ lệnh nào còn đủ tiền mới qua.
Đánh đổi: Đúng và nhanh nhưng chỉ lưu con số cuối; khó giải thích vì sao số dư thay đổi và khó đối soát.
Nguồn: Grokking Modern System Design · ch.9 Databases ↗ · bài "Types of Databases", mục "Relational databases" › "Concurrency"
Mọi thay đổi là một bút toán: giữ tiền khi đặt, nhả khi hủy, trừ theo giá khớp và hoàn phần chênh, phí, thuế; mỗi bút toán có idempotency key theo order_id hoặc exec_id. Bảng số dư cập nhật bằng UPDATE có điều kiện trong cùng transaction với bút toán. Toàn bộ dữ liệu một tài khoản nằm trên một shard nên mọi transaction là single-shard, không cần transaction phân tán. Cổ phiếu mua về tách cột chờ về T+2 để không bán được trước hạn.
Đánh đổi: Thêm bảng và phải đối soát tổng bút toán với số dư hằng ngày, nhưng audit được từng đồng.
Nguồn: Microservices Patterns ↗ (.zip) · ch.4 · §4.1.2 The trouble with distributed transactions (tr. 112–114) ; Grokking Modern System Design · ch.9 Databases ↗ · bài "Data Partitioning", mục "Horizontal sharding" ; Chưa có nguồn trong thư viện: giao thức FIX và quy tắc thị trường chứng khoán (ATO/ATC, T+2, lô 100) không có trong sách nào của thư viện; chỉ nêu khái niệm cơ bản
Trong phỏng vấn
- “Hệ thống không khớp lệnh; phần khó là gửi lệnh tới sàn đúng một lần và giữ trạng thái khớp với sàn.”
- “Tôi lưu Order và outbox trong cùng transaction với việc giữ tiền, gửi lên sàn với ClOrdID = order_id, và khi không chắc kết quả thì hỏi lại sàn thay vì gửi lại.”
- “Giá được gộp 250 ms theo mã, mỗi client nhận một frame; CPU đẩy frame quyết định số gateway.”
- “Tiền dùng sổ cái bút toán chỉ ghi thêm, shard theo tài khoản để mọi transaction nằm trên một shard.”
- Gọi sàn trước rồi mới lưu lệnh vào DB.
- Kiểm tra số dư bằng đọc rồi ghi, để hai lệnh song song cùng lọt.
- Áp execution report không idempotent, trừ tiền hai lần khi bản tin đến trùng.
- Đẩy từng tick giá tới mọi client, không gộp và không xử lý client chậm.
Đọc thêm
Sách trong thư viện của bạn. Số chương có thể lệch nhẹ so với bản PDF.