Nhảy đến nội dung

Chiến Lược Đánh Index Cơ Sở Dữ Liệu Cho Hệ Thống CRM Multi-Tenant

Submitted by Ẩn danh (chưa được kiểm chứng) on

Khám phá cách đánh index theo composite key, sử dụng index filter và giám sát query execution plan với EXPLAIN ANALYZE trên MySQL/MariaDB.

Tối ưu hóa chỉ mục B-Tree hỗn hợp và chiến lược phân vùng bảng (partitioning) khi quy mô dữ liệu vượt qua hàng triệu bản ghi.

Xây Dựng Nền Tảng Giao Dịch Bất Động Sản Nexus-Real

Submitted by Ẩn danh (chưa được kiểm chứng) on
Thiết kế nền tảng giao dịch bất động sản với bài toán cốt lõi: định giá tất định (deterministic pricing), khóa tài sản nhất quán dưới concurrency cao và kiến trúc Modular Monolith linh hoạt.

Tổng Quan Dự Án & Vai Trò (My Role)

  • Dự án: Nexus-Real-QNH — Nền tảng lõi điều phối giao dịch, bảng giá sơ cấp (Primary) và sàn ký gửi thứ cấp (Resale) cho doanh nghiệp kinh doanh BĐS.
  • Vai trò cá nhân: Lead Fullstack Engineer / Solution Architect
  • Trách nhiệm chính:
    • Khảo sát bài toán nghiệp vụ, phân tích domain boundaries và thiết kế kiến trúc DDD 3 tầng.
    • Thiết kế và trực tiếp hiện thực hóa Pricing Pipeline Engine và cơ chế Deterministic Rounding.
    • Thiết kế quy trình xử lý concurrency cho giỏ hàng: Redis Distributed Lock kết hợp Database State Machine & Transactional Outbox.
    • Thiết kế Data Schema (không cross-module DB Foreign Keys) và Dynamic Permission Registry.
    • Xây dựng Frontend SPA (React 19, TypeScript, Vite) và thiết lập môi trường triển khai Production (Linux, Nginx, PHP-FPM, Supervisor).

01. Bối Cảnh & Vấn Đề (The Problem)

Tại các sàn giao dịch BĐS quy mô hàng trăm môi giới, bài toán bán hàng sơ cấp và thứ cấp phát sinh những xung đột dữ liệu mang tính rủi ro pháp lý và tài chính cao:

  1. Xung đột giữ chỗ (Double Booking): Khi một block căn đẹp được mở bán, 3–5 môi giới có thể cùng báo khách đã cọc căn đó qua nhóm chat hoặc bảng tính Excel dùng chung.
  2. Sai lệch dòng tiền và báo giá: Việc tính tay trên bảng tính dễ dẫn đến sai lệch do quy tắc làm tròn (rounding), thứ tự ưu tiên áp chiết khấu không thống nhất.
  3. Phân mảnh thực thể (Domain fragmentation): Các hệ thống CRM generic quản lý theo bảng customers phẳng không thể biểu diễn chính xác vòng đời và vai trò đa dạng của một cá nhân (vừa là người mua, vừa là chủ nhà, hoặc môi giới).

02. Thách Thức Kỹ Thuật (The Core Engineering Challenge)

Hệ thống không chỉ là một ứng dụng CRUD đơn thuần, mà phải bảo đảm 3 Business Invariants bất biến:

  • Invariant 1 — Deterministic Financial Output: Cùng một căn hộ, cùng một phiên bản bảng giá, kết quả tính dòng tiền phải luôn tái lập chính xác 100%, sai số luôn triệt tiêu về 0 VNĐ.
  • Invariant 2 — Single-Owner Asset State: Một căn hộ tại một thời điểm chỉ có thể ở đúng một trạng thái và thuộc về duy nhất một phiên giao dịch hợp lệ.
  • Invariant 3 — Legal Snapshot Immutability: Báo giá và hợp đồng đã được phê duyệt không được phép thay đổi khi bảng giá gốc hoặc chính sách trong tương lai có biến động.

03. Thiết Kế Miền Nghiệp Vụ (DDD)

Hệ thống chuẩn hóa toàn bộ ngôn ngữ giao tiếp và mô hình hóa thành các Bounded Contexts độc lập. Sử dụng Party đại diện cho cá nhân/pháp nhân đa vai trò, và MarketAsset cho các tài sản có thể giao dịch. Quote và QuoteSnapshot đại diện cho đề xuất thương mại và bản ghi đóng băng pháp lý.

05. Pricing Engine: Phép Tính Tài Chính Tất Định

Sử dụng pattern Pipeline 10 bước độc lập. Giải quyết sai số bằng cách:

  1. Áp dụng chiết khấu lũy tiến theo thứ tự ưu tiên trên giá trước thuế.
  2. Sử dụng thư viện Brick\Money\Money và DECIMAL(18,4) dưới DB, từ chối kiểu float/double.
  3. Triệt tiêu sai số ở đợt thanh toán cuối bằng phép trừ:

    AmountN = GrandTotal - (Amount1 + Amount2 + ... + AmountN-1)

06. Chống Double Booking Dưới Concurrency Cao

Kết hợp Redis Lock làm lớp giảm tải (Throttle/Contention Reducer) (15m TTL) và Database Transaction + State Machine làm nguồn chân lý duy nhất (Single Source of Truth) với luồng xử lý: AVAILABLE -> LOCKED -> RESERVED -> SOLD.

07. Tính Bất Biến Của Báo Giá

Dữ liệu tại thời điểm chốt giá được Serialize thành JSON, tính toán chuỗi băm SHA-256 và lưu vào quote_snapshots (immutable). Báo giá PDF render từ bản ghi này để chống gian lận.

08. Đồng Bộ Bất Đồng Bộ (Transactional Outbox)

Sự kiện (Event) và Dữ liệu nghiệp vụ cùng nằm trong một DB Transaction, đẩy vào Outbox Table, sau đó Worker Daemon mới Dispatch vào Redis Queue để đảm bảo tính nhất quán (At-Least-Once Delivery).

04. Kiến Trúc Hệ Thống: Modular Monolith Từ chối chia nhỏ thành Microservices ngay từ đầu. Áp dụng kiến trúc 3-Tier Modular Monolith:

  • Platform/: Tầng nền tảng (Kernel DDD, Messaging, Storage).
  • Products/: Bounded Contexts độc lập (CRM, Primary, Resale, Finance).
  • Integration/: Lớp biên giao tiếp.

Chiến Lược "No Cross-Module Foreign Keys": Không tạo Foreign Key vật lý giữa các module (VD: bảng quotes chỉ lưu party_id). Tính nhất quán được bảo đảm qua Application Services và Domain Events thay vì ON DELETE CASCADE. Bounded context được cách ly triệt để, sẵn sàng tách microservice khi cần.

10. Những Thứ Chúng Tôi Chủ Động Không Xây Dựng

  • Không dùng Microservices: Quy mô đội ngũ chưa cần đến chi phí vận hành phức tạp của phân tán.
  • Không dùng Apache Kafka: Sự kết hợp giữa MySQL Outbox Table + Redis Queue Workers là đủ tin cậy.
  • Không dùng Elasticsearch: Cơ chế indexing tối ưu của MySQL kết hợp Redis Cache giải quyết tốt 95% bài toán tra cứu.

 

11. Kết Quả Thực Tế & Số Liệu Đo Lường

  • Hiệu Quả Vận Hành: Thời gian lập báo giá dòng tiền giảm từ 45 phút xuống < 1 phút. Thời gian xác nhận giao dịch rút từ 24 giờ xuống < 6 giờ. Tranh chấp giữ chỗ (Double Booking) được loại bỏ hoàn toàn. Hoa hồng môi giới được phân bổ tức thì.
  • Chỉ Số Kỹ Thuật:
    • Hỗ trợ 500 – 1.500 môi giới đồng thời.
    • Quote Calculation: Target < 500ms (p95).
    • Unit Lock API: Target < 1s.
    • Đạt chuẩn Deterministic Output (Sai số tiền mặt = 0 VNĐ).

12. Bài Học Kinh Nghiệm

  1. Domain Logic phải độc lập với Framework: Giữ cho các thực thể và phép tính toán tài chính (Domain Layer) thuần PHP giúp viết Unit Test nhanh và tái sử dụng logic an toàn.
  2. Khóa phân tán (Distributed Lock) không thay thế được Database Integrity: Redis Lock phân luồng truy cập tốt, nhưng thay đổi trạng thái cuối cùng bắt buộc phải được bảo vệ bởi Database Transaction và Atomic State Transitions.
  3. Giá trị của việc "Chậm lại để mô hình hóa domain": Dành thời gian thiết kế các khái niệm lõi (Party, MarketAsset) giúp dễ dàng mở rộng sang các module khác (như Resale) mà không phải đập bỏ kiến trúc cũ.
Subscribe to MariaDB