Khi team còn nhỏ, merge vào main thường là chuyện khá đơn giản: PR xanh, review xong, bấm merge. Nhưng khi monorepo lớn dần, số team tăng lên, commit rate dày hơn, và CI bắt đầu kéo dài 20-40 phút, một vấn đề rất khó chịu xuất hiện: PR nào cũng xanh ở thời điểm review, nhưng lại không xanh khi thực sự chạm vào main mới nhất.
Đó là lúc nhiều tổ chức bắt đầu nghĩ đến merge queue hoặc merge train. Ý tưởng nghe có vẻ thuần workflow, nhưng thực ra đây là một bài toán production engineering rất rõ: làm sao giữ main luôn releasable, không bắt developer rebase liên tục, và không để integration risk bị dồn hết vào người merge cuối cùng.
Vấn đề là merge queue không phải viên đạn bạc. Nếu CI signal còn bẩn, queue policy ngây thơ, batching sai chỗ, hoặc workflow không hiểu merge_group, hệ thống sẽ nhanh chóng rơi vào cảnh mới: build lặp vô ích, hàng chờ dài, PR quan trọng bị starve, và cả team mất niềm tin rằng “xanh trong queue” thật sự có nghĩa gì.

Merge queue thực ra giải bài toán gì?
Trong repo bận rộn, có ba failure mode rất quen thuộc.
1. PR xanh theo snapshot cũ
Một PR pass toàn bộ required checks ở commit main cách đây 30 phút. Trong lúc đó, ba PR khác đã merge. Nếu merge thẳng, PR này đang vào một trạng thái codebase khác với thứ nó đã được kiểm tra.
2. Rebase storm
Nếu branch protection buộc mọi PR luôn up to date trước khi merge, developer phải liên tục rebase hoặc update branch rồi chờ CI chạy lại. Mỗi vòng như vậy ăn thêm compute, kéo dài lead time và tạo cảm giác “mình đang trả thuế chỉ vì người khác cũng commit”.
3. Integration debt dồn về cuối
Nếu team bỏ qua chuyện up-to-date checks để đỡ chờ, integration risk sẽ trôi tới main. Khi đó lỗi không còn nằm ở PR riêng lẻ nữa mà ở sự kết hợp giữa nhiều PR pass riêng lẻ nhưng không tương thích khi ghép lại.
Merge queue sinh ra để xử lý đúng chỗ căng này. Queue chuyển câu hỏi từ “PR này có xanh một mình không?” thành “PR này có còn xanh khi áp vào main mới nhất và những PR đang đứng trước nó không?”.

Merge queue khác gì với require branches to be up to date?
Hai cơ chế có cùng mục tiêu: tránh merge một PR đã pass trên snapshot cũ. Nhưng cost model khác nhau.
- Require up to date: mỗi tác giả PR tự cập nhật branch, tự chạy lại CI, tự chờ.
- Merge queue: hệ thống chịu trách nhiệm xếp hàng, tạo merge group, chạy checks và merge theo policy.
Require up to date đơn giản về mặt khái niệm, nhưng khi repo đủ đông thì rebase và rebuild lặp lại trên từng PR trở nên rất đắt. Merge queue giảm ma sát này, nhưng đổi lại anh phải vận hành queue semantics, batching policy, timeout, retry budget và fairness.
Merge queue không chỉ là tính năng GitHub, mà là một hàng đợi production
Merge queue có đủ các bài toán quen thuộc của distributed systems:
- throughput: mỗi giờ merge được bao nhiêu PR;
- latency: một PR phải chờ bao lâu từ lúc ready đến lúc vào main;
- fairness: PR nhỏ có bị PR lớn chặn vô lý không;
- head-of-line blocking: một PR fail ở đầu queue có làm những PR sau đứng im không;
- retry policy: queue nên thử lại bao nhiêu lần khi check fail do flaky infrastructure;
- admission control: PR nào đủ điều kiện mới được vào queue;
- backpressure: lúc CI quá tải thì nên giảm concurrency hay giảm batch size.
Nếu nhìn merge queue như một checkbox workflow, team rất dễ cấu hình theo mặc định rồi bất ngờ khi throughput tụt mạnh hơn trước.
Khi nào monorepo thật sự cần merge queue?
Merge queue trở nên đáng giá khi có từ ba dấu hiệu sau:
- main merge rất thường xuyên và mỗi ngày có hàng chục PR trở lên;
- CI tương đối dài, ví dụ required checks mất 15-40 phút;
- monorepo có coupling cao giữa shared libraries, codegen, build tooling, schema hoặc release pipeline;
- team đang đi theo trunk-based development hoặc muốn tiến gần tới trạng thái đó.
Ở quy mô lớn, merge queue rất hợp với trunk-based development, miễn là đừng biến nó thành nơi tích tụ branch dài ngày theo kiểu mới.
CI phải hiểu merge_group, nếu không queue chỉ là ảo giác
Nếu dùng GitHub Actions, workflow required checks phải trigger cả pull_request lẫn merge_group. Với third-party CI, hệ thống cũng phải nhận biết SHA integration thật mà queue tạo ra. Nếu thiếu bước này, queue chỉ đẹp ở UI nhưng rỗng về mặt signal.
Serialize hay batch nhiều PR một lúc?
Đây là trade-off cốt lõi.
Serialize từng PR
- dễ reason;
- blame rõ khi fail;
- ít khó khăn trong triage.
Nhược điểm là throughput thấp hơn và CI capacity bị dùng khá kém nếu build time dài.
Batch hoặc merge group nhiều PR
- tăng throughput nếu workload ổn định;
- ít lần build lại cho cùng một base state.
Nhưng khi fail, cần tách xem PR nào gây lỗi; interaction bug giữa các PR cũng khó triage hơn. Lane nhiều flaky tests hoặc dependency nhạy cảm thường nên serialize. Lane có signal sạch và PR nhỏ có thể batch nhẹ.

Flaky test là kẻ giết queue âm thầm nhất
Flaky test trong CI thường đã khó chịu. Trong merge queue, nó nguy hiểm hơn vì nó không chỉ làm khổ một PR, mà còn phá throughput của cả hàng.
Một test fail ngẫu nhiên ở đầu queue có thể gây retry tốn compute, PR phía sau chờ vô ích, engineer nghi ngờ queue bị kẹt, rồi cả team bắt đầu re-run cho tới khi xanh. Nếu tín hiệu CI chưa đủ sạch, merge queue chỉ đang tự động hóa sự hỗn loạn.
Trước khi tối ưu queue strategy, hãy xử lý test signal: quarantine test flaky có bằng chứng, tách infra flakes khỏi application failures, thêm retry budget có kiểm soát và đo false negative rate theo từng lane. Tư duy này rất gần với bài Flaky Test trong CI/CD, nếu team anh đã có bài hoặc tài liệu nội bộ tương tự thì nên coi đó là tiền đề chứ không phải hậu quả của merge queue.
Merge queue không thay thế selective testing hay test impact analysis
Queue và test impact analysis giải hai bài toán khác nhau:
- TIA trả lời: thay đổi này cần lane nào để có confidence hợp lý.
- Merge queue trả lời: tập thay đổi sắp chạm main có còn pass trên trạng thái mới nhất không.
Nếu không có confidence tiers, queue sẽ phải chạy quá nhiều thứ và biến thành choke point. Nếu có TIA nhưng không có queue ở repo rất bận, integration risk vẫn dồn về cuối.
Merge queue cần risk classes, không chỉ FIFO
FIFO nghe công bằng, nhưng repo production thật không luôn nên đối xử mọi PR như nhau. Shared auth middleware, build toolchain, reusable CI workflow, protobuf/OpenAPI/AsyncAPI schema hoặc release tooling đều có blast radius rộng hơn PR thường.
Các thay đổi này có thể cần queue riêng hoặc lane riêng, không được batch với PR thường, required checks nặng hơn và approval gate cao hơn trước khi vào queue.

Những anti-pattern làm merge queue phản tác dụng
- Queue che giấu branch dài ngày: PR drift nhiều rồi mới vào queue thì bản thân queue không cứu được integration complexity.
- Một queue cho mọi loại thay đổi: build tooling, backend core, docs và infra modules cùng chung policy thường là công thức cho head-of-line blocking.
- Không có observability: nếu team không đo queue wait time, merge success rate, fail reason taxonomy, batch size effectiveness và PRs bị starve, mọi tranh luận sẽ chỉ là cảm giác.
- Bắt full e2e cho mọi merge group: nghe an toàn nhưng rất dễ đốt throughput.
- Không có timeout và eviction policy rõ: merge group treo sẽ tạo hàng chờ ma.
Một kiến trúc queue thực dụng cho monorepo lớn
- Admission gate trước queue: review policy tối thiểu, affected test lanes, ownership checks, policy check cho schema/build/auth changes.
- Queue lane theo risk: docs/UI lane nhẹ, app/backend lane chuẩn, shared infra/schema/tooling lane serialize.
- Merge-group-aware CI: workflow phải chạy trên SHA integration thật.
- Retry budget hữu hạn: infra flake có thể retry 1-2 lần, quá ngưỡng phải fail rõ.
- Queue observability dashboard: p50/p95 queue wait time, build duration theo lane, failure rate, flake suspicion score, PR age khi vào queue.

Merge queue và trunk-based development gặp nhau ở đâu?
Trunk-based development không phải “không dùng PR”. Cốt lõi của nó là tích hợp thường xuyên vào mainline, branch ngắn và main luôn releasable. Ở quy mô lớn, merge queue là cách giữ tinh thần đó mà không đòi từng developer manual rebase cả ngày.
Nhưng queue chỉ hợp với trunk-based development nếu team vẫn giữ các nguyên tắc nền: branch sống ngắn, thay đổi nhỏ, feature flag hoặc branch by abstraction cho rollout phức tạp, và không dùng queue như bãi đỗ cho những PR khổng lồ. Nếu thay đổi vẫn quá lớn và tồn tại quá lâu, merge queue sẽ trở thành bộ máy xác nhận cho một workflow vốn đã có integration debt cao.
Checklist trước khi bật merge queue cho monorepo
- CI đã hiểu
merge_grouphay queue branch chưa? - Required checks có đáng tin, hay flaky rate vẫn cao?
- Đã phân risk class cho shared tooling/schema/auth changes chưa?
- Đã có timeout, retry budget và eviction policy chưa?
- Có số đo queue latency và failure taxonomy chưa?
- Có cần nhiều lane thay vì một queue duy nhất không?
- Tổ chức đã đủ kỷ luật branch ngắn và merge thường xuyên chưa?
Nếu chưa trả lời tốt bảy câu này, bật merge queue rất dễ chỉ chuyển nỗi đau từ rebase storm sang queue storm.
Kết luận
Merge queue giải đúng một bài toán rất thật của monorepo hiện đại: giữ main xanh trên trạng thái mới nhất mà không buộc từng developer tự gánh toàn bộ chi phí rebase và rebuild. Nhưng nó chỉ phát huy tác dụng khi được nhìn như một hệ thống điều tiết integration, không phải một toggle trong branch protection.
Khi CI signal sạch, lane rõ, risk class hợp lý và queue có observability, merge queue có thể tăng velocity đáng kể mà vẫn giữ branch production an toàn. Còn nếu test còn flaky, workflow chưa hiểu merge_group, và mọi PR đều bị ném vào cùng một hàng, anh chỉ đang tự động hóa thêm một nút cổ chai mới.
Nếu team anh cũng đang dọn reliability cho branch chính, có thể đọc thêm Contract Testing cho Microservices, AsyncAPI và Event Contract Governance, Retry Storm và Timeout Budget và Incident Postmortem để nối queue policy với release safety và learning loop sau sự cố.