Data - 'Máu' Của Hệ Thống
Bắt Đầu Từ Đâu Khi Đảm Nhận Một Hệ Thống Lớn?
Gần đây, mình gặp một sự trùng hợp khá thú vị. Chỉ trong một thời gian ngắn, hai người ở hai hoàn cảnh hoàn toàn khác biệt lại đặt cho mình cùng một câu hỏi.
Lần đầu tiên, mình “được hỏi” trong một bối cảnh khá formal. Vài hôm sau, mình lại bắt gặp nó trong một cuộc trò chuyện cởi mở hơn. Dù ngữ cảnh có khác nhau, đại ý câu hỏi vẫn chỉ là một: “Khi cần tiếp cận và tìm hiểu một hệ thống mới, đặc biệt là một hệ thống lớn và phức tạp, ta nên bắt đầu từ đâu?”
Sự lặp lại ngẫu nhiên này làm mình cũng nhớ lại những lần trò chuyện với các bạn trẻ trong đội ngũ. Rất nhiều bạn cũng từng thắc mắc điều tương tự. Đứng trước một mớ bòng bong hàng chục microservices, hàng trăm ngàn dòng code, ta nên nắm lấy sợi chỉ đỏ nào để gỡ rối?
Với mình, câu trả lời luôn nằm ở Dữ liệu. Nếu kiến trúc, code hay hạ tầng là bộ khung xương và cơ bắp, thì Data chính là “máu” của hệ thống. Hiểu được cách máu lưu thông, bạn sẽ hiểu được hệ thống đang sống như thế nào.
1. Data Structure: Bản đồ gen của nghiệp vụ
Fred Brooks, một tượng đài trong ngành phần mềm, từng có một câu nói rất nổi tiếng: “Show me your flowchart and conceal your tables, and I shall continue to be mystified. Show me your tables, and I won’t usually need your flowchart; it’ll be obvious.” (Tạm dịch: Hãy cho tôi xem luồng xử lý mà giấu đi các bảng dữ liệu, tôi sẽ vẫn thấy mơ hồ. Nhưng chỉ cần cho tôi xem các bảng dữ liệu, tôi sẽ chẳng cần đến luồng xử lý nữa; mọi thứ đã quá rõ ràng).
Cần nói thêm cho công bằng: câu này ra đời ở thời mô hình quan hệ thống trị, khi “tables” gần như đồng nghĩa với toàn bộ trạng thái của hệ thống. Trong thế giới event-sourcing hay schema-on-read ngày nay, “tables” không còn là nơi duy nhất chứa sự thật. Nhưng tinh thần của Brooks thì vẫn nguyên giá trị: hãy chỉ cho tôi nơi dữ liệu được định hình, tôi sẽ hiểu hệ thống làm gì.
Khi bạn đọc code của một ai đó, bạn đang đọc “cách” họ giải quyết vấn đề (How). Nhưng ngôn ngữ lập trình, design pattern hay framework đôi khi làm cho lớp vỏ bọc trở nên cồng kềnh. Một đoạn logic tính toán giỏ hàng có thể được viết bằng hàng tá IF/ELSE phức tạp hay những Pattern bay bướm, rất khó để nắm bắt ngay lập tức.
Ngược lại, khi nhìn vào Data Structure – cấu trúc của các bảng, các entities và mối quan hệ giữa chúng, bạn đang nhìn thẳng vào “bản chất” của vấn đề (What). Cấu trúc dữ liệu phản ánh chính xác nhất bài toán nghiệp vụ.
Nếu cấu trúc dữ liệu tồi, code của bạn sớm muộn cũng sẽ trở thành một mớ rác vì phải liên tục viết các đoạn logic “workaround” để bù đắp cho sự thiếu hụt của thiết kế ban đầu. Bắt đầu từ cấu trúc dữ liệu chính là cách đi tìm bản đồ gen của hệ thống, giúp bạn không bị lạc lối trong rừng code.
Còn một lợi ích nữa, ít người nói tới nhưng với mình là quan trọng nhất khi quản lý một đội: cấu trúc dữ liệu rõ ràng là tài sản giao tiếp của cả team. Một schema mạch lạc giúp một thành viên mới onboarding nhanh hơn nhiều so với việc đọc hàng vạn dòng code. Nó cũng giảm hiểu lầm giữa các service do những team khác nhau sở hữu, vì mọi người cùng nhìn vào một định nghĩa chung về sự thật, thay vì mỗi người tự suy diễn.
2. Data Flow: Dòng lưu thông của sự sống và nghệ thuật kiến trúc
Máu trong cơ thể chỉ có ý nghĩa khi nó không ngừng tuần hoàn. Dữ liệu trong hệ thống cũng vậy. Việc nắm được Data Flow – cách dữ liệu được sinh ra, biến đổi, đi qua những service nào và dừng lại ở đâu, chính là chìa khóa để làm chủ hệ thống.
Thực tế vận hành các hệ thống phân tán quy mô lớn cho thấy, những vấn đề đau đầu nhất, tốn nhiều đêm thức trắng nhất thường không đến từ bản thân logic của một service đơn lẻ, những lỗi có thể bắt được khi review code hay viết unit test, mà đến từ Production Issue liên quan đến luồng dữ liệu:
- Dữ liệu stale: Thông tin cũ ở cache vẫn còn tồn tại gây sai lệch logic hiển thị cho người dùng.
- Duplicate event: Một sự kiện thanh toán bị đẩy vào message queue và xử lý hai lần làm sai lệch số dư.
- Race condition: Hai request đồng thời cùng “giành giật” thay đổi trạng thái của một đơn hàng.
- Inconsistency: Service A đã trừ tiền thành công, nhưng Service B lại gặp lỗi mạng và chưa cộng điểm thưởng, dẫn đến dữ liệu không nhất quán.
- Transaction chưa complete: Gây ra trạng thái lửng lơ cho nghiệp vụ, hệ thống không biết nên tiến hay lùi.
- Retry gây ghi đè: Cơ chế tự thử lại rất cần thiết với hệ thống phân tán, nhưng khi thao tác không Idempotent, nó vô tình làm mất hoặc nhân bản dữ liệu.
Bề ngoài, chúng trông như lỗi code. Nhưng sâu bên dưới, đó là sự thất bại trong việc kiểm soát luồng dữ liệu.
Chính vì vậy, khi chúng ta nói về việc áp dụng Clean Architecture hay Domain-Driven Design (DDD), đó không chỉ là việc chia project thành mấy folder (Domain, Application, Infrastructure…). Bản chất sâu xa của chúng cũng là thiết lập các màng lọc và vách ngăn để bảo vệ tính toàn vẹn của dữ liệu cốt lõi, không cho phép các logic ngoại lai làm “nhiễm bẩn” nó.
3. Database: Nơi máu được lưu giữ và bài toán di cư
Công nghệ luôn thay đổi. Hôm nay chúng ta dùng framework này, ngày mai có thể sẽ chuyển sang một công nghệ mới ưu việt hơn. Frontend có thể đập đi xây lại trong vài tháng. Các microservices có thể được viết lại bằng một ngôn ngữ khác có hiệu năng tốt hơn.
Nhưng Dữ liệu thì luôn ở lại.
Nếu Data Flow là dòng máu đang tuần hoàn, thì Database là nơi máu luôn quay về, kho lưu trữ và là điểm tựa cho cả cơ thể. Bạn có thể refactor một module code trong một tuần, nhưng để migrate (di cư) hàng tỷ bản ghi dữ liệu từ hệ thống cũ sang hệ thống mới mà không làm gián đoạn dịch vụ (zero-downtime) là một bài toán tốn hàng tháng trời với vô vàn rủi ro. Dữ liệu là tài sản quý giá nhất, là lịch sử, là giá trị cốt lõi của một sản phẩm.
Chính vì tính chất “khó thay đổi” này, khi thiết kế hệ thống, thay vì cố gắng vẽ ra một kiến trúc trông hào nhoáng, mình ưu tiên tập trung vào việc làm sao để tổ chức dữ liệu một cách toàn diện nhất. Một cơ chế lưu trữ và quản lý dữ liệu trọn vẹn, thấu hiểu từ cấu trúc index, các đánh đổi giữa consistency và availability, cho đến các chiến lược sharding/partition, sẽ quyết định hệ thống có đứng vững được hay không khi scale lên.
Và đây cũng là chỗ vai trò của một người quản lý kỹ thuật lộ rõ. Tính “khó thay đổi” của dữ liệu không chỉ là vấn đề kỹ thuật, nó là vấn đề quyết định:
- Nó định hình cách ta phân chia ownership giữa các team, ai sở hữu nguồn sự thật nào (source of truth), ai được phép ghi, ai chỉ được đọc.
- Nó là yếu tố nặng ký trong mọi lựa chọn build-vs-buy: chọn một giải pháp ngoài đồng nghĩa với việc giao một phần dữ liệu cốt lõi cho bên thứ ba và lấy nó về sau này luôn đắt hơn ta tưởng.
- Và khó nhất: nó buộc ta phải thuyết phục stakeholder đầu tư vào một khoản mà họ không nhìn thấy ngay, một đợt cải tổ schema hay một dự án migrate chẳng tạo ra tính năng mới nào để demo, nhưng lại quyết định việc hệ thống có sống nổi qua hai năm tới hay không.
4. Nhưng không phải lúc nào cũng “data-first”
Tất nhiên, lấy dữ liệu làm điểm tựa không có nghĩa là mọi hệ thống đều phải lấy một kho lưu trữ làm trung tâm. Thế giới công nghệ đã mở rộng với nhiều mô hình kiến trúc hiện đại:
- Event-driven architecture: Nơi các sự kiện là trung tâm của mọi giao tiếp và bản thân sự kiện chính là dữ liệu.
- Streaming system: Dòng chảy dữ liệu liên tục không ngừng nghỉ, nơi giá trị nằm ở tốc độ xử lý trên pipeline chứ không chỉ ở nơi lưu trữ cuối cùng.
- AI-native system: Nơi vector embedding, context và dữ liệu retrieval định hình hành vi của hệ thống và bản thân model cũng tiến hóa theo dữ liệu phản hồi nó nhận được.
- Realtime platform: Ưu tiên độ trễ cực thấp, thường dựa vào in-memory store và cơ chế push/pub-sub để đẩy dữ liệu tới người dùng tức thì.
Trong những hệ thống này, event stream, workflow state hay message bus đôi khi mới là source of truth chính yếu. Nhưng dù biểu hiện dưới dạng nào, bản chất vẫn không đổi: hệ thống sinh ra là để tạo ra, xử lý, lưu trữ và vận chuyển dữ liệu. Hình hài có thể phi truyền thống, nhưng vòng đời của dữ liệu thì vẫn vậy, vẫn được sinh ra, biến đổi, lưu lại và vẫn có thể sai lệch hay thất lạc nếu ta lơ là. Càng hiểu rõ dòng chảy này, ta càng hiểu được hệ thống.
Và ngay cả khi data-first là đúng hướng, mình cũng học được rằng không nguyên lý nào đúng trong mọi hoàn cảnh, kể cả nguyên lý này. Có những lúc mình phải tự nhắc mình buông nó ra.
Một là khi nghiệp vụ còn chưa định hình. Trong giai đoạn đi tìm product-market fit, thứ đắt giá nhất không phải một schema chuẩn mực, mà là tốc độ thử và sai. Cố thiết kế một mô hình dữ liệu hoàn hảo cho một nghiệp vụ mà ngày mai có thể đổi hoàn toàn là một dạng lãng phí “tinh vi”. Những lúc ấy, bắt đầu từ hành vi, từ API, từ trải nghiệm người dùng rồi để dữ liệu định hình sau lại là lựa chọn hợp lý hơn. “Đúng” về data mà “trễ” về thị trường thì vẫn là thua.
Hai là khi data-first biến thành over-modeling. Mình từng chứng kiến và từng tự mình gây ra, những thiết kế cố dự đoán mọi tình huống tương lai, để rồi có một schema phức tạp đến mức chẳng ai dám động vào, kiểu dùng dao mổ trâu để mổ một con gà. Mô hình hóa quá sớm cho những thứ chưa chắc xảy ra cũng phiền không kém việc thiết kế cẩu thả.
Nên phần khó hơn không nằm ở việc tin vào một nguyên tắc, mà ở chỗ nhận ra khi nào nên dựa vào nó và khi nào nên gác nó sang một bên. Đó là thứ mình vẫn đang học.
Nhưng giữa mọi đổi thay và đánh đổi đó, có một điều mình luôn chắc chắn:
Code có thể được rewrite.
Framework có thể bị thay thế.
Nhưng dữ liệu luôn mang theo lịch sử và bản chất thật của hệ thống.
Lần tới, nếu bạn cần tìm hiểu xem một hệ thống xa lạ đang vận hành ra sao, đừng vội đắm chìm vào những dòng code. Hãy thử “bắt mạch” để xem “máu” của nó đang lưu thông như thế nào nhé.
