← Về danh sách
Kỹ nghệ & Quản lý phần mềm
#모노레포#폴리레포#의존성그래프#빌드캐시#형상관리
Cập nhật lần cuối · 2026-11-08

Chiến lược Monorepo và so sánh với Polyrepo

1. Tổng quan

Định nghĩa: Monorepo là một chiến lược quản lý cấu hình hợp nhất mã nguồn của nhiều dự án, dịch vụ và thư viện vào một kho lưu trữ (repository) quản lý phiên bản duy nhất. Bên trong một kho, mã nguồn được chia sẻ, nhiều mô-đun được thay đổi một cách nguyên tử (atomically) trong một commit, và việc phát triển diễn ra trên một đồ thị build và phụ thuộc hợp nhất. Đây là bài toán về tổ chức mã nguồn (code organization), khác biệt với kiến trúc nguyên khối (monolithic) vốn chỉ một đơn vị triển khai duy nhất.

Khi một tổ chức phần mềm lớn lên, số lượng dịch vụ và thư viện tăng đến hàng trăm, hàng nghìn. Phương thức polyrepo (multi-repo) đặt mỗi sản phẩm vào một kho riêng ban đầu có vẻ tự nhiên, nhưng khi các thư viện dùng chung được nhiều kho tham chiếu theo phiên bản, việc theo dõi "kho nào dùng phiên bản nào" trở nên khó khăn. Để áp dụng một bản vá bảo mật duy nhất cho một mô-đun dùng chung, phải tạo PR nâng phiên bản ở từng kho trong hàng chục kho sử dụng nó — vấn đề được gọi là địa ngục phụ thuộc (dependency hell) và phụ thuộc kim cương (diamond dependency).

Monorepo giải quyết vấn đề này bằng một sự chuyển đổi góc nhìn. Thay vì "chia mã theo ranh giới kho và liên kết theo phiên bản", nó liên kết mã "ở cấp mã nguồn bên trong một kho duy nhất (source dependency)". Kết quả là commit sửa một thư viện dùng chung và các sửa đổi của tất cả bên tiêu thụ nó diễn ra một cách nguyên tử trong cùng một commit, cùng một PR. "Thay đổi tác động đến đâu" được trả lời ngay lập tức không phải bằng ma trận phiên bản mà bằng phân tích tĩnh trên đồ thị phụ thuộc.

Trên quan điểm bài thi Kỹ sư chuyên nghiệp, monorepo không đơn thuần là "một kho lớn" mà là một sự đánh đổi kỹ thuật (engineering trade-off), trong đó các giá trị thay đổi nguyên tử, nguồn chân lý duy nhất và khả năng nhìn thấy toàn cục đạt được để đổi lấy các chi phí hiệu năng build, kiểm soát truy cập và khả năng mở rộng, được bù đắp bằng công cụ chuyên dụng. Quan trọng hơn sự thật rằng các tổ chức siêu lớn như Google, Meta, Microsoft đã chọn monorepo chính là việc giải thích "tại sao monorepo không thể tồn tại nếu thiếu một hệ thống build chuyên dụng."

1.1 Bối cảnh ra đời và tính cần thiết

Thứ nhất, nhu cầu chia sẻ mã và loại bỏ trùng lặp. Khi microservice gia tăng, mã dùng chung như xác thực, ghi log, tuần tự hóa hoặc bị sao chép vào từng dịch vụ, hoặc được tách ra thành gói nội bộ và phân phối theo phiên bản. Sao chép phá vỡ tính nhất quán; phân phối theo phiên bản gây trễ nâng cấp. Monorepo giữ mã dùng chung như một nguồn duy nhất và để mọi bên tiêu thụ tham chiếu trực tiếp HEAD mới nhất, xóa bỏ cả hai vấn đề cùng lúc.

Thứ hai, tính an toàn của việc tái cấu trúc quy mô lớn và thay đổi API. Trong polyrepo, khi thay đổi chữ ký hàm của một thư viện dùng chung, các bên tiêu thụ vẫn tham chiếu phiên bản cũ nên không thể biết ngay thay đổi có "làm hỏng" gì không. Trong monorepo, thư viện và tất cả điểm gọi được sửa cùng nhau trong một commit và kiểm thử một lần, nên nếu biên dịch và kiểm thử đạt thì toàn tổ chức được bảo đảm không bị hỏng.

Thứ ba, trải nghiệm nhà phát triển và quá trình nhập môn (onboarding). Một nhà phát triển mới chỉ cần clone một kho là có thể duyệt, tìm kiếm và thay đổi toàn bộ cơ sở mã, đồng thời có thể áp dụng đồng loạt phong cách mã, lint và quy tắc CI hợp nhất. Tuy nhiên ưu điểm này đảo chiều thành vấn đề hiệu năng clone và build một khi kho đạt quy mô hàng chục triệu tệp, nên luôn phải bàn luận cùng với công cụ chuyên dụng.

2. Cấu trúc tổng thể và mô hình phụ thuộc

Bản chất của monorepo không nằm ở bố cục kho mà ở việc biểu diễn phụ thuộc giữa các dự án dưới dạng đồ thị ở cấp mã nguồn. Sơ đồ cấu trúc dưới đây cho thấy nhiều ứng dụng và thư viện dùng chung bên trong một kho duy nhất được gắn kết thành một đồ thị phụ thuộc, hoạt động trên một tầng build, CI và công cụ chung.

graph TD
  subgraph REPO["Kho duy nhất (Monorepo)"]
    subgraph APPS["Tầng ứng dụng"]
      WEB["web-app"]
      API["api-service"]
      BATCH["batch-worker"]
    end
    subgraph LIBS["Tầng thư viện dùng chung"]
      AUTH["lib-auth"]
      LOG["lib-logging"]
      MODEL["lib-domain-model"]
    end
    WEB --> AUTH
    WEB --> LOG
    API --> AUTH
    API --> MODEL
    BATCH --> MODEL
    BATCH --> LOG
    AUTH --> LOG
  end
  TOOL["Hệ thống build / CI (nhận biết đồ thị)"] -.phân tích.-> REPO

Trong đồ thị trên, việc sửa lib-logging đưa lib-auth, web-app, api-service, batch-worker — vốn tham chiếu nó (trực tiếp hoặc gián tiếp) — vào phạm vi ảnh hưởng (affected set). Năng lực cốt lõi của công cụ monorepo chuyên dụng chính là tính toán chính xác đồ thị ảnh hưởng này và, đối với một commit đã thay đổi, chọn "chỉ những dự án phải build và kiểm thử lại." Việc này được gọi là phân tích affected/impacted, và là bí quyết hiệu năng giữ được tính an toàn mà không phải build toàn bộ kho mỗi lần.

2.1 Phụ thuộc nguồn và commit nguyên tử

Trong khi polyrepo tham chiếu sản phẩm theo số phiên bản (ví dụ lib-auth@1.4.2) như một phụ thuộc nhị phân (binary dependency), monorepo mặc định dùng phụ thuộc nguồn (source dependency) tham chiếu trực tiếp đường dẫn bên trong cây mã nguồn. Sự khác biệt này phái sinh gần như toàn bộ ưu và nhược điểm của monorepo.

Với phụ thuộc nguồn, "HEAD hiện tại" của thư viện là phiên bản duy nhất, nên các xung đột phụ thuộc kim cương phát sinh do nhiều phiên bản cùng tồn tại biến mất tận gốc. Đồng thời commit nguyên tử (atomic commit) trở nên khả thi — ví dụ, một thay đổi đổi tên trường trong lib-domain-model và các sửa đổi của api-service, batch-worker sử dụng trường đó được gộp vào một commit, nên kho luôn giữ "trạng thái nhất quán, có thể build được." Việc rollback cũng quy về một lệnh git revert commit đó, khôi phục tất cả cùng nhau.

Ngược lại, điều này cũng có nghĩa là rủi ro gắn kết chặt (tight coupling) — "ai đó có thể thay đổi thư viện dùng chung mà tôi không hay biết, làm hỏng dịch vụ của tôi." Vì vậy monorepo bắt buộc phải đi kèm CODEOWNERS cưỡng chế ranh giới sở hữu dưới dạng mã, các quy tắc khả kiến (visibility), và một hệ thống chạy kiểm thử ảnh hưởng toàn cục trong CI trước khi thay đổi.

2.2 Cách vận hành của đường ống build và CI

Trong monorepo, cách tiếp cận ngây thơ "build tất cả" sụp đổ ngay khoảnh khắc kho lớn lên. Build và kiểm thử hàng nghìn dự án ở mỗi commit khiến thời gian CI bùng nổ lên hàng giờ. Do đó công cụ monorepo chỉ thực hiện đúng công việc cần thiết qua đường ống sau.

flowchart LR
  COMMIT["Tệp thay đổi của Commit/PR"] --> GRAPH["Tính đồ thị phụ thuộc"]
  GRAPH --> AFFECTED["Chọn dự án bị ảnh hưởng (affected)"]
  AFFECTED --> CACHE{"Cache build trúng?"}
  CACHE -->|"trúng (hit)"| REUSE["Tái dùng sản phẩm từ cache"]
  CACHE -->|"trượt (miss)"| RUN["Chỉ build/test công việc đó"]
  RUN --> STORE["Lưu vào cache từ xa"]
  REUSE --> RESULT["Kết quả CI"]
  STORE --> RESULT

Cốt lõi nằm ở hai tối ưu hóa. Thứ nhất, chọn lọc dựa trên ảnh hưởng: lần ngược đồ thị phụ thuộc từ các tệp thay đổi để chọn chỉ những dự án phải tái kiểm chứng. Thứ hai, cache định địa chỉ theo nội dung (content-addressed cache): lấy vân tay đầu vào của công việc (mã nguồn, phụ thuộc, lệnh, chuỗi công cụ) bằng hàm băm, và nếu đầu vào giống nhau thì tải về và tái dùng sản phẩm đã tính từ cache từ xa (remote cache). Việc chia sẻ cache phân tán này — nơi kết quả build cục bộ của nhà phát triển A được nhà phát triển B và CI tái dùng — quyết định năng suất của một monorepo quy mô lớn.

3. Phân loại và công cụ tiêu biểu

Các hệ thống build nâng đỡ monorepo chia đại thể, theo triết lý thiết kế, thành bộ chạy tác vụ dựa trên đồ thị và hệ thống build kín (hermetic) nghiêm ngặt. Loại trước là cách tiếp cận nhẹ, đặt phân tích ảnh hưởng và cache lên trên các hệ sinh thái hiện có (npm, Gradle...), còn loại sau là cách tiếp cận nặng, kiểm soát hoàn toàn đầu vào và đầu ra của build để bảo đảm khả năng tái lập và cả thực thi từ xa.

Phân loại Bộ chạy dựa trên đồ thị Hệ thống build kín (hermetic)
Công cụ tiêu biểu Nx, Turborepo, Lerna Bazel, Buck2, Pants
Triết lý Điều phối trên trình quản lý gói hiện có Kiểm soát hoàn toàn đầu vào/ra, build tái lập được
Cache Cache cục bộ/từ xa theo đơn vị tác vụ Cache định địa chỉ nội dung theo đơn vị action + thực thi từ xa
Độ khó triển khai Thấp (dễ áp dụng dần) Cao (mô tả lại toàn bộ bằng tệp BUILD)
Quy mô phù hợp Vừa và nhỏ đến lớn Siêu lớn, đa ngôn ngữ, duy trì dài hạn

Điểm cần lưu ý ở đây là lựa chọn công cụ không phải câu hỏi "cái nào tốt hơn" mà là bài toán về sự phù hợp với quy mô tổ chức và mức độ yêu cầu khả năng tái lập. Nếu một tổ chức vài trăm người lấy web làm trung tâm triển khai Bazel toàn diện, chi phí viết và duy trì tệp BUILD có thể áp đảo năng suất; ngược lại, nếu một tổ chức hàng nghìn người, đa ngôn ngữ chỉ dùng bộ chạy đồ thị, nó sẽ va phải giới hạn của khả năng tái lập build và thực thi từ xa.

3.1 Build kín và khả năng tái lập

Build kín cô lập một tác vụ build sao cho nó chỉ thấy được các đầu vào đã khai báo (mã nguồn, phụ thuộc, phiên bản chuỗi công cụ) và không thể phụ thuộc vào các đầu vào chưa khai báo như trạng thái mạng hay hệ thống. Nhờ đó "cùng đầu vào → luôn cùng đầu ra" thành lập, tối đa hóa tỷ lệ trúng cache và cho phép phân tán build trên hàng trăm máy chủ làm việc từ xa (remote execution). Bí quyết để hệ thống build nội bộ Blaze của Google (bản mã nguồn mở là Bazel) gánh được một kho duy nhất quy mô hàng tỷ dòng chính là sự kết hợp giữa tính kín này với cache và thực thi từ xa.

Mặt khác, tính kín đòi hỏi khai báo tường minh mọi phụ thuộc, nên chi phí chuyển đổi ban đầu lớn. Vì mọi thư viện hệ thống hay công cụ toàn cục trước đây được tham chiếu ngầm đều phải được kéo vào quy tắc BUILD, nên việc làm kín một cơ sở mã cũ trong một lần là bất khả thi trên thực tế, và cần di trú dần dần.

4. So sánh với Polyrepo — Tại sao khác biệt nảy sinh

Sự đối lập giữa monorepo và polyrepo không phải "chia thành bao nhiêu kho" mà là lựa chọn kéo thời điểm liên kết về phía mã nguồn, hay trì hoãn sang nhị phân. Từ khác biệt căn bản này, tất cả các khác biệt thực tiễn dưới đây phái sinh.

Góc nhìn Monorepo Polyrepo
Liên kết phụ thuộc Nguồn (HEAD mới nhất) Nhị phân (ghim phiên bản)
Chia sẻ mã Tham chiếu trực tiếp, phản ánh tức thì Cần phát hành gói và nâng phiên bản
Thay đổi nguyên tử Thay đổi toàn cục trong một commit Cần phối hợp giữa nhiều kho
Kiểm soát truy cập Khó chi tiết hóa (cần công cụ riêng) Rõ ràng theo từng kho
Build/CI Bùng nổ nếu không có công cụ chuyên dụng Độc lập theo từng kho, đơn giản
Quyền tự chủ của nhóm Cưỡng chế quy tắc chung, tự chủ↓ Tự do chọn công nghệ theo nhóm↑

Lý do thay đổi nguyên tử khả thi là phụ thuộc nguồn. Trong polyrepo, thay đổi một API dùng chung đòi hỏi phối hợp nhiều giai đoạn — "phát hành thư viện → mỗi bên tiêu thụ nâng phiên bản → mỗi bên tái triển khai" — và trong quá trình này phiên bản cũ và mới cùng tồn tại, tạo gánh nặng tương thích. Monorepo thay thế sự phối hợp này bằng trình biên dịch và kiểm thử, hoàn tất trong một lần.

Lý do kiểm soát truy cập khó khăn là một kho chính là một ranh giới quyền hạn. Ở các tổ chức như tài chính, quốc phòng vốn phải phân tách nghiêm ngặt quyền truy cập mã nguồn theo nhóm và cấp bậc, polyrepo với quyền rõ ràng theo từng kho có lợi cho việc tuân thủ quy định. Để thực hiện kiểm soát truy cập chi tiết trong monorepo cần các cơ chế bổ sung như checkout thưa (sparse checkout), quyền trên cây con, làm tăng độ phức tạp vận hành.

Về hàm ý thực tiễn, nhiều tổ chức chọn phương án lai (hybrid). Họ đặt monorepo theo dòng sản phẩm hoặc miền (ví dụ monorepo frontend, monorepo nền tảng dữ liệu) trong khi tách thành các kho riêng những vùng chịu quy định, công bố ra bên ngoài, hoặc có vòng đời hoàn toàn khác biệt. Tức là "một kho khổng lồ duy nhất" không phải mục tiêu; sự tích hợp trong một ranh giới có độ cố kết cao mới là định hướng thực tiễn.

5. Chuyên sâu — Kỹ thuật mở rộng và các trường hợp cho monorepo siêu lớn

Thách thức kỹ thuật lớn nhất của monorepo là giới hạn mở rộng của chính Git. Git là mô hình phân tán nhân bản toàn bộ lịch sử và cây làm việc về cục bộ, nên một khi tệp đạt hàng triệu đến hàng chục triệu, git status, clone, checkout trở nên không thể kham nổi. Để giải quyết điều này, các kỹ thuật sau đã phát triển.

Thứ nhất, clone một phần (partial clone) và checkout thưa (sparse checkout). git clone --filter=blob:none tải lười các blob, và chỉ những thư mục cần cho công việc được bung ra cây làm việc để giảm gánh nặng cục bộ. Khi Microsoft chuyển mã nguồn Windows (khoảng 300GB, hơn 3,5 triệu tệp) sang Git, hãng đã dùng VFS for Git (sau là Scalar) để nhận tệp qua hệ thống tệp ảo vào thời điểm yêu cầu, cho phép làm việc mà không cần clone toàn bộ.

Thứ hai, hệ thống tệp ảo và trình giám sát tệp. Trong monorepo nền Mercurial, Meta kết hợp Watchman (giám sát thay đổi tệp), EdenFS (hệ thống tệp ảo tải theo yêu cầu) và công cụ build Buck2 để giữ truy vấn trạng thái gần thời gian hằng số ngay cả trong kho hàng chục triệu tệp. Google thậm chí không dùng git, mà vận hành một kho duy nhất nơi hàng tỷ dòng và khoảng hai vạn nhà phát triển làm việc đồng thời, qua VCS tập trung Piper, hệ thống tệp ảo CitC và hệ thống build Blaze (theo công bố năm 2016, khoảng 86TB với hàng chục nghìn commit mỗi ngày).

Thứ ba, cache từ xa và thực thi từ xa. Nếu cache định địa chỉ nội dung mô tả ở trên được chia sẻ trên toàn tổ chức, thì ngay cả lần build đầu tiên của một nhà phát triển mới cũng nhận hầu hết sản phẩm từ cache, rút ngắn một bản build mất hàng chục phút xuống còn vài phút. Như một trường hợp đo thực tế, các tổ chức triển khai Nx hoặc Turborepo báo cáo thời gian CI giảm 50–90% khi trúng cache từ xa (vì độ dao động lớn tùy phạm vi thay đổi và tỷ lệ trúng, nên hiểu như một xu hướng hơn là khẳng định).

Bài học chung của các trường hợp này rất rõ ràng — monorepo không phải chiến lược mà là khoản đầu tư. Nó chỉ thành lập khi hệ thống build chuyên dụng, hệ thống tệp ảo, hạ tầng cache từ xa và quản trị sở hữu/khả kiến cùng được trang bị; chỉ gộp các kho lại mà thiếu hạ tầng này sẽ ngược lại làm năng suất sụt giảm.

6. Cân nhắc và hàm ý (góc nhìn Kỹ sư chuyên nghiệp)

  • Chiến lược áp dụng (tiệm tiến): Thay vì nhắm tới một kho duy nhất toàn công ty ngay từ đầu, trước hết hãy gộp các miền có độ cố kết cao thường thay đổi cùng nhau vào một monorepo, đưa vào phân tích ảnh hưởng và cache từ xa bằng bộ chạy dựa trên đồ thị (Nx, Turborepo), rồi khi quy mô lớn lên mới mở rộng sang build kín và hệ thống tệp ảo — lộ trình trưởng thành theo giai đoạn này là an toàn. "Vạch ranh giới thế nào" đi trước "việc gộp lại."
  • Đánh đổi (khả kiến vs tự chủ/hiệu năng): Monorepo trao các giá trị khả kiến toàn cục, thay đổi nguyên tử và công cụ nhất quán, nhưng ràng buộc tự do chọn công nghệ theo nhóm và gây chi phí mở rộng Git/CI. Với tổ chức nhỏ thiếu năng lực đầu tư công cụ chuyên dụng, hay sản phẩm có vòng đời hoàn toàn độc lập, polyrepo có thể hợp lý hơn. Câu trả lời đúng thay đổi tùy quy mô tổ chức, độ gắn kết và yêu cầu quy định.
  • Bảo mật và quản trị: Vì một kho duy nhất mở rộng ranh giới quyền hạn, phải kiểm soát rủi ro gắn kết chặt bằng cách cưỡng chế review dựa trên CODEOWNERS, quy tắc khả kiến theo từng thư mục, giới hạn phạm vi truy cập qua checkout thưa, và kết hợp bảo mật chuỗi cung ứng (SBOM, ký commit) vào CI. Vì bản thân kho trở thành mục tiêu tấn công hàng đầu và điểm lỗi đơn (single point of failure), nên sao lưu, kiểm soát truy cập và nhánh được bảo vệ là bắt buộc.
  • Tổ chức và văn hóa: Monorepo đòi hỏi chuyển đổi văn hóa cộng tác — "mọi người cùng chịu trách nhiệm cho một cơ sở mã." Các chính sách lint, kiểm thử, phiên bản chung phải được thiết lập bằng đồng thuận toàn công ty, và hiệu quả đạt tối đa khi kết hợp với phát triển dựa trên trunk (trunk-based development) và văn hóa tái cấu trúc tự động quy mô lớn (codemod).
  • Triển vọng và liên kết: Monorepo đang định hình như nền tảng của kỹ thuật nền tảng (platform engineering), DevSecOps và CI/CD, còn thực thi từ xa và cache phân tán đang được thương mại hóa thành dịch vụ build trên đám mây. Hơn nữa, ở điểm trợ lý mã AI có thể tham chiếu bối cảnh của toàn bộ cơ sở mã từ một kho, giá trị của nó như một nguồn tri thức tổ chức duy nhất được dự báo sẽ còn lớn hơn.

Tài liệu tham khảo


Tóm tắt một câu: Monorepo là chiến lược hợp nhất nhiều dự án trong một kho duy nhất qua phụ thuộc nguồn để đạt thay đổi nguyên tử, khả kiến toàn cục và chia sẻ mã; nó chỉ mở rộng được khi có hạ tầng chuyên dụng như build chọn lọc dựa trên ảnh hưởng, cache từ xa và hệ thống tệp ảo hậu thuẫn, và các đánh đổi về kiểm soát truy cập, tự chủ nhóm và chi phí mở rộng Git phải được thiết kế phù hợp với quy mô tổ chức và độ gắn kết.