---
title: "Phân vùng cơ sở dữ liệu quan hệ ở quy mô lớn"
description: "Bài học từ cách GitHub phân vùng MySQL: schema domain, linter ranh giới, kiểm tra consistency, cơ chế cutover và rollout production."
canonical: "https://tmtuan.dev/vi/articles/partitioning-relational-databases-at-scale"
language: "vi"
image: "https://tmtuan.dev/_img/articles/partitioning-relational-databases-at-scale/partitioning-relational-databases-at-scale-cover.3f6638ad-og.jpg"
updated: "2026-10-09"
tokens: 2723
---

# Phân vùng cơ sở dữ liệu quan hệ trước khi chuyển dữ liệu

## Bối cảnh

Một cơ sở dữ liệu quan hệ lớn hiếm khi trở nên khó xử lý chỉ vì một bảng đột nhiên không còn vừa trên một máy. Vấn đề khó hơn là coupling. Nhiều năm tích luỹ join, transaction, association và background job biến một database thành failure domain dùng chung. Khi đó, việc chuyển một bảng có thể phá vỡ giả định ẩn trong application code, dù thao tác sao chép dữ liệu không phức tạp.

GitHub mô tả quá trình này trong một bài kỹ thuật năm 2021. GitHub.com khởi đầu là ứng dụng Ruby on Rails với phần lớn dữ liệu nằm trong một MySQL database. Theo thời gian, họ thêm database riêng cho một số tính năng, read replica và ProxySQL, nhưng cluster chính `mysql1` vẫn phục vụ dữ liệu lõi như user, repository, issue và pull request. Để scale, họ liên tục phải nâng cấp máy lớn hơn; một sự cố trên cluster này có thể ảnh hưởng nhiều tính năng không liên quan.\[1\]

Bài học có thể tái sử dụng không nằm ở một sản phẩm sharding cụ thể. GitHub làm rõ ranh giới database trong application trước, rồi mới chuyển dữ liệu. Thứ tự này biến một dự án hạ tầng rủi ro cao thành chuỗi contract có thể lint, đo lường và migrate độc lập.

## Bài gốc nói gì

GitHub đưa ra khái niệm **schema domain** như một lớp phân vùng ảo. Mỗi domain gom các bảng thường xuyên được join hoặc cập nhật trong cùng transaction. Mọi bảng phải thuộc đúng một domain và cấu hình YAML ghi lại quyền sở hữu đó. Một schema linter kiểm tra cấu hình có khớp với database schema thật hay không.\[1\]

Hai linter khác enforce ranh giới này.

Query linter chạy ở môi trường development và test. Query tham chiếu bảng từ nhiều domain sẽ báo lỗi, nhờ đó developer thấy coupling mới trước khi code lên production. CI ngăn regression ngoài ý muốn. Vi phạm cũ có thể được đánh dấu exemption, giúp dependency legacy hiện rõ mà không buộc nhóm migration sửa toàn bộ cùng lúc.\[1\]

Transaction linter xử lý rủi ro tinh vi hơn. Một transaction cập nhật hai domain vẫn an toàn khi cả hai còn nằm trên cùng MySQL cluster, nhưng atomicity đó sẽ biến mất sau khi tách cluster. GitHub lấy mẫu linter này trên production để tìm transaction xuyên domain với overhead thấp. Đội ngũ sau đó sửa code path hoặc điều chỉnh data model. Khi consistency là bắt buộc, dữ liệu được tách sang bảng mới thuộc cùng domain.\[1\]

Khi một domain đã độc lập về mặt logic, GitHub có thể chuyển nó sang cluster khác. Bài gốc mô tả hai cơ chế:

- **Vertical sharding bằng Vitess.** Ứng dụng kết nối qua VTGate, thành phần route traffic theo MySQL protocol. VTTablet quản lý database instance, còn VReplication sao chép bảng giữa các cluster.\[1\]\[2\]
- **Write cutover tự xây dựng.** MySQL replication sao chép dữ liệu sang cluster đích trong khi ProxySQL vẫn route write về primary cũ. Lúc cutover, script chuyển primary cũ sang read-only, lấy GTID cuối, đợi cluster đích thực thi tới GTID đó, dừng replication, đổi routing rồi bật write trở lại.\[1\]\[3\]

GitHub cho biết sáu bước vận hành của cutover hoàn thành trong vài chục mili giây với các bảng bận nhất khi chạy ở khung giờ traffic thấp. Con số này chỉ mô tả lúc chuyển quyền ghi đã được chuẩn bị trước, không bao gồm thời gian replication, validation hay toàn bộ migration. Họ đã chuyển 130 bảng phục vụ repository, issue và pull request. Từ 2019 đến 2021, tổng query rate tăng từ trung bình 950.000 query mỗi giây trên `mysql1` lên 1,2 triệu trên nhiều cluster, trong khi tải trung bình trên mỗi host giảm một nửa.\[1\]

Các kết quả vận hành trên đến từ retrospective do chính GitHub công bố, không phải benchmark được tái lập độc lập. Chúng cho thấy tổng traffic tăng trong khi tải trung bình mỗi host giảm, nhưng không chứng minh partitioning tạo ra mức tăng traffic hoặc định lượng số incident đã giảm.\[1\]

## Những gì cần kiểm chứng trước khi áp dụng

### 1\. Ranh giới domain phải theo invariant, không theo sơ đồ tổ chức

Sơ đồ ownership của các team không tự động trở thành cách chia database an toàn. Các bảng nên ở cùng nhau khi một correctness rule cần local join hoặc transaction, hoặc khi access pattern khiến việc di chuyển độc lập trở nên không thực tế. Trong ví dụ của GitHub, những bảng của cùng tính năng và có coupling chặt được giữ trong một schema domain.\[1\]

Tôi sẽ lập bản đồ từ query log, ORM association, foreign key, transaction trace và batch job trước khi đề xuất domain. Bản đồ ban đầu chỉ là giả thuyết. Transaction linter trên production có thể phát hiện dependency mà static code inspection bỏ sót.

### 2\. Exemption phải là technical debt có owner

Annotation giúp migration diễn ra từng bước, nhưng cũng có thể bình thường hoá vi phạm tồn tại vĩnh viễn. Mỗi exemption cần ghi rõ các domain liên quan, owner, lưu lượng, yêu cầu consistency và cách dự kiến loại bỏ. CI phải từ chối exemption mới chưa được duyệt, còn dashboard theo dõi backlog hiện có.

Cách xử lý phụ thuộc vào semantics. Cross-domain join có thể thành hai read rồi ghép trong application memory. ORM association có thể dùng nhiều query riêng thay vì SQL join. GitHub đã đóng góp hỗ trợ `disable_joins` cho Rails association vì lý do này. Ở trường hợp khác, sao chép một phần dữ liệu nhỏ vào domain đích an toàn hơn vì vẫn giữ được local transaction.\[1\]

Các lựa chọn này không tương đương. Application-side join có thể tăng round trip và memory. Duplicate data tạo thêm việc đồng bộ và repair. Quyết định đúng phải dựa trên cardinality, độ mới dữ liệu cần thiết và hành vi khi lỗi, thay vì cấm join một cách máy móc.

### 3\. Phân tích transaction quan trọng hơn phân tích từng query

Query linter chứng minh một statement chỉ chạm một domain. Nó không chứng minh toàn bộ business operation cũng vậy. Một request có thể chạy nhiều statement hợp lệ riêng lẻ trên các domain khác nhau, bên trong thứ mà application vẫn coi là một transaction.

Trước khi chuyển dữ liệu, tôi sẽ phân loại từng cross-domain transaction:

1. **Bắt buộc atomic:** đặt state liên quan cùng chỗ hoặc thiết kế lại ranh giới domain.
2. **Chấp nhận eventual consistency:** dùng outbox, consumer idempotent và reconciliation process.
3. **Có thể đổi thứ tự an toàn:** tách operation và định nghĩa retry semantics rõ ràng.

Việc phân loại phải bao gồm tình huống lỗi. Điều gì xảy ra nếu domain đầu đã commit còn domain thứ hai timeout? Operation nào retry an toàn? Hệ thống phát hiện và sửa partial completion bằng cách nào?

### 4\. Replication bắt kịp là điều kiện cần, chưa phải điều kiện đủ

GTID cuối trùng nhau chứng minh cluster đích đã apply lịch sử transaction được replicate từ nguồn tới điểm cutover.\[3\] Nó không tự chứng minh routing, permission, schema, sequence, background worker hay read-after-write behaviour đều đúng.

Tôi sẽ diễn tập cutover với traffic có hình dạng giống production, rồi kiểm row count, checksum, replication lag, write error rate, connection draining và rollback routing. Runbook cần điều kiện huỷ rõ ràng trước khi chuyển write. Sau khi đổi routing, synthetic write phải xác nhận primary mới nhận traffic và replica trả về kết quả mong đợi.

GitHub chấp nhận một số ít write thất bại trong cửa sổ read-only rất ngắn.\[1\] Trade-off đó có thể phù hợp với sản phẩm này nhưng không phù hợp với sản phẩm khác. Error budget và retry behaviour phải được quyết định trước khi chọn kiểu cutover này.

## Cách tôi sẽ áp dụng vào production

Tôi sẽ triển khai migration qua bốn gate thay vì xem đây là một dự án database duy nhất.

**Gate 1: lập bản đồ coupling.** Gán mọi bảng vào domain dự kiến. Thu thập cross-domain query và transaction mà chưa chặn traffic. Xếp hạng vi phạm theo request volume, write frequency và consistency risk.

**Gate 2: enforce virtual partition.** Biến query violation thành lỗi ở development và CI. Lấy mẫu transaction violation trên production. Exemption legacy phải tạm thời và qua review. Một domain chỉ sẵn sàng khi số violation chưa được duyệt bằng không và mọi exemption còn lại đều có migration plan.

**Gate 3: shadow quá trình chuyển vật lý.** Replicate domain sang đích. So sánh count và checksum, replay read đại diện, đo capacity của cluster mới. Diễn tập cutover và rollback nhiều lần. Đóng băng schema change trên các bảng liên quan trong cửa sổ cuối, hoặc chuẩn bị cách đã kiểm thử để apply thay đổi lên cả hai phía.

**Gate 4: cutover và quan sát.** Chọn proxy-driven write cutover hoặc migration layer như Vitess dựa trên topology và kinh nghiệm vận hành. Giữ nguyên nguồn để rollback cho tới khi đích vượt qua health check đã định nghĩa. Theo dõi write error, replication position, latency, connection saturation và business invariant của domain thay vì chỉ nhìn CPU.

Kết quả kiến trúc quan trọng không chỉ là nhiều database nhỏ hơn. Đó là một hệ thống có ranh giới thực thi được: ownership được khai báo, truy cập sai bị chặn sớm, coupling legacy đo được, và mỗi lần chuyển vật lý đều có consistency model rõ ràng. Khi các thuộc tính này tồn tại, partitioning trở thành quy trình lặp lại được thay vì một lần giải cứu đầy rủi ro.

## Sources

\[1\] [https://github.blog/engineering/infrastructure/partitioning-githubs-relational-databases-scale](https://github.blog/engineering/infrastructure/partitioning-githubs-relational-databases-scale) — Partitioning GitHub's relational databases to handle scale \[2\] [https://vitess.io/docs/reference/features/sharding](https://vitess.io/docs/reference/features/sharding) — Vitess sharding reference \[3\] [https://dev.mysql.com/doc/refman/8.0/en/replication-gtids-concepts.html](https://dev.mysql.com/doc/refman/8.0/en/replication-gtids-concepts.html) — MySQL Reference Manual: GTID concepts

## Structured data (JSON-LD)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Article","@id":"https://tmtuan.dev/vi/articles/partitioning-relational-databases-at-scale#article","headline":"Phân vùng cơ sở dữ liệu quan hệ trước khi chuyển dữ liệu","description":"Bài học từ cách GitHub phân vùng MySQL: schema domain, linter ranh giới, kiểm tra consistency, cơ chế cutover và rollout production.","datePublished":"2026-10-09","dateModified":"2026-10-09","inLanguage":"vi","url":"https://tmtuan.dev/vi/articles/partitioning-relational-databases-at-scale","mainEntityOfPage":"https://tmtuan.dev/vi/articles/partitioning-relational-databases-at-scale","image":"https://tmtuan.dev/_img/articles/partitioning-relational-databases-at-scale/partitioning-relational-databases-at-scale-cover.3f6638ad-og.jpg","author":{"@type":"Person","@id":"https://tmtuan.dev/#person","name":"Tran Manh Tuan","url":"https://tmtuan.dev/en"}},{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Trang chủ","item":"https://tmtuan.dev/vi"},{"@type":"ListItem","position":2,"name":"Bài viết","item":"https://tmtuan.dev/vi/articles"},{"@type":"ListItem","position":3,"name":"Phân vùng cơ sở dữ liệu quan hệ trước khi chuyển dữ liệu","item":"https://tmtuan.dev/vi/articles/partitioning-relational-databases-at-scale"}]}]}
```
