By Đức Hiếu
1. Tổng quan
Connection Multiplexing là một trong những tính năng nổi bật nhất của ProxySQL, giúp giải quyết vấn đề bottleneck về kết nối với hệ thống Database. Khác với connection pooling truyền thống, multiplexing cho phép nhiều frontend connections chia sẻ các backend connections thông qua tỷ lệ N:M thay vì 1:1, từ đó giảm đáng kể áp lực lên Database layer.
2. Vấn đề với mô hình Thread-per-Connection của MySQL
Trong mô hình truyền thống, MySQL sử dụng thread-per-connection model - tức là mỗi connection tạo ra một software thread riêng biệt. Điều này dẫn đến các vấn đề sau khi hệ thống scale lên:
2.1. Vấn đề về tài nguyên
- RAM/CPU tăng cao: Mỗi thread/connection sẽ tiêu thụ một lượng RAM nhất định. số lượng connection càng lớn dẫn đến tổng RAM chiếm càng lớn - có thể nhanh chóng vượt quá RAM server nếu không kiểm soát.
- Context switching overhead: Càng nhiều thread, CPU càng phải context switching nhiều giữa các thread
- Connection limit: MySQL có giới hạn max_connections (mặc định 151)
3. Kiến trúc hiện tại với Kiến trúc kết hợp ProxySQL
3.1. Trường hợp thông thường (không có ProxySQL)
- Chú thích:
- Frontend connection: connection trên tầng Application
- Backend connection: connection trên tầng Database
Ở hình trên chúng chúng ta có thể thấy các App instances sẽ tạo kết nối trực tiếp tới Database. Giả sử rằng tổng số lượng kết nối từ các App instances tạo ra là 3000 connection thì sẽ tạo ra 3000 threads trên Database
=> Tỷ lệ 1:1 (N frontend : N backend)
3.2 Với ProxySQL Multiplexing
Với hình trên chúng ta có thể thấy tất cả các kết nối từ các App instances sẽ đi tới Database thông qua ProxySQL. Giả sử rằng tổng số lượng kết nối từ các App instances tạo ra là 3000 connection thì lúc này ProxySQL sẽ gom nhóm chia sẻ 3000 connection này đi vào 100 connection mà ProxySQL tạo để đi tới Database. Lúc này thì Database chỉ tạo ra 100 threads.
=> Tỷ lệ N:M (3000 frontend : 100 backend)
Từ đây chúng ta có thể thấy. Multiplexing giúp giải quyết:
- Giảm connection trực tiếp ở Database. ProxySQL đứng giữa sẽ gom các frontend connections, tái sử dụng và chia sẻ chúng trên một số lượng backend connections cố định. Giúp cho Database không bị “ngộp” trong hàng ngàn connection, đảm bảo ổn định hiệu năng và tận dụng tốt tài nguyên server.
- Giúp giữ một tập backend connections ổn định và tái sử dụng chúng, thay vì liên tục đóng/mở connection mới. Đảm bảo Database luôn hoạt động trong ngưỡng tối ưu về số connection.
- Cho phép nhận và quản lý hàng ngàn frontend connections mà không bắt Database phải chịu tải trực tiếp. khả năng “horizontal scaling” tốt hơn. Vì Application không còn lo giới hạn về connection của Database.
4. Connection Multiplexing vs Connection Pooling
5. Cơ chế hoạt động của Multiplexing
5.1. Query Processing Flow
- Client connection: Application kết nối tới ProxySQL
- Query analysis: ProxySQL phân tích query và session state
- Connection selection: Chọn idle backend connection phù hợp
- Query execution: Gửi query qua backend connection
- Result routing: Route kết quả về đúng client connection
- Connection release: Trả backend connection về pool (nếu có thể) ngay lập tức và có thể được sử dụng ngay lập tức bởi một frontend connection khác
Trường hợp 1 frontend connection gửi nhiều câu query liên tiếp. ProxySQL vẫn sẽ đảm bảo thứ tự thực thi nhưng có khả năng nằm ở các backend connection khác nhau.
Ví dụ: Mình có 2 backend connection có id là 1 và 2. Giả sử ở application mình tạo một connection và thực thi 2 câu query liên tiếp. Lúc này có khả năng rằng câu query đầu tiên được thực thi ở backend connection có id là 1, và câu query thứ 2 có thể được thực thi ở backend connection có id là 2. Mặc dù 2 cầu query đều được gửi từ 1 frontend connection
5.2. Vô hiệu hóa multiplexing
Trước khi đi vào phần dưới đây chúng ta cùng làm rõ mối liên hệ giữa transaction và connection trong Database (MySQL InnoDB). Connection của mình trong ngữ cảnh này là backend connection.
- Một transaction luôn phải gắn liền với một connection đến Database. Tức là khi chúng ta START TRANSACTION T1 trên connection C1, tất cả query trong transaction T1 cần được gửi trong connection C1.
- Tại sao vậy? vì nó để đảm bảo tính tuần tự của các query trong 1 transaction.
- MySQL server quản lý transaction state của T1 tương ứng với C1.
Ở phần 5.1 chúng ta có thể thấy, 1 frontend connection (từ Application) có thể gửi tuần tự nhiều câu query. Các câu query này có thể được ProxySQL phân phối sang nhiều backend connection khác nhau.
Vấn đề đưa ra:
"Vậy khi code của mình dùng transaction thì sao? Liệu ProxySQL có thể gửi một phần transaction sang backend connection này, phần còn lại sang backend connection khác không?".
Giả sử trong code Application có luồng sau:
- Step 1: Start transaction.
- Step 2: Insert thông tin student.
- Step 3: Update thông tin class.
- Step 4: Commit
Nếu áp dụng multiplexing thì có khả năng: step 1 nằm ở backend connection #1 nhưng step 2 lại nằm ở backend connection #2 và thậm chí step 4 sẽ nằm ở backend connection nào đó khác nữa.
Tóm lại nếu áp dụng multiplexing trong trường hợp trên, các câu query trong 1 transaction có thể chạy trên các backend connection khác nhau. Như vậy, điều này vi phạm nguyên tắc “tất cả query trong transaction cần được gửi trong một connection”.
Nhưng thực tế ProxySQL không làm như vậy.
Khi phát hiện một kết nối bắt đầu transaction, ProxySQL sẽ tự động disable multiplexing và gắn backend connection vừa thực thi query `Start transaction` với frontend connection. Nghĩa là:
- Toàn bộ các câu lệnh trong cùng transaction (từ `Start transaction` cho đến khi `Commit` hoặc `Rollback`) sẽ luôn được gửi đến cùng một backend connection duy nhất.
- Chỉ sau khi transaction kết thúc, ProxySQL mới cho phép multiplexing quay lại sử dụng backend connection đó.
Cơ chế này đảm bảo tính ACID của transaction và giúp Application hoạt động đúng logic như khi kết nối trực tiếp với Database.
Ngoài active transaction, còn có những tình huống khác cũng khiến ProxySQL tạm thời disable multiplexing (ví dụ khi sử dụng user-defined variables, temporary tables…), để luôn giữ an toàn cho logic của application.
6. Các trường hợp Multiplexing bị vô hiệu hóa
Đây là phần quan trọng nhất cần hiểu khi triển khai multiplexing: Ở đây mình tóm tắt lại một số thành phần mình thấy thực tế sẽ gặp. Và nếu các bạn muốn biết chi tiết hơn thì có thể xem trên document của ProxySQL
- Active Transaction: Khi transaction active trên một backend connection thì multiplexing bị disable cho đến khi transaction commit/rollback hoặc frontend connection gắn với backend connection đóng kết nối. Ví dụ ở 5.2 mình giải thích rõ vì sao active transaction lại disable multiplexing các bạn có thể xem lại.
START TRANSACTION;
-- Hoặc
SET @@autocommit = 0;
- Table Locks: Nếu LOCK TABLE, LOCK TABLES or FLUSH TABLES WITH READ LOCK được thực thi, multiplexing được disable cho đến khi UNLOCK TABLES được thực thi
LOCK TABLES users READ; -- disable multiplexing
UNLOCK TABLES; – enable multiplexing
- GET_LOCK: Nếu GET_LOCK() được thực thi, multiplexing bị disable và không bao giờ được enable trên backend connection được sử dụng để thực thi
SELECT GET_LOCK('my_lock', 10);
- Temporary Tables: Nếu tạo table tạm, multiplexing bị disable và không bao giờ được enable trên backend connection được sử dụng để tạo bảng tạm.
CREATE TEMPORARY TABLE temp_data (id INT);
Lưu ý rằng: disable multiplexing nhưng không disable routing. Trong trường hợp bạn triển khải hostgroups trong ProxySQL rất có khả năng gặp lỗi với message là `'schemaname.temporary_tablename' doesn't exist` khi thực thi CREATE TEMPORARY TABLE và thực thi SELECT bảng tạm vừa tạo. Vì multiplexing bị disable còn routing thì không. Khả năng hai statements được gửi tới hai hostgroups khác nhau và lỗi sẽ xảy ra. Để ngăn điều này chúng ta cần tạo các query rules để chuyển hướng (route) các câu liên quan đến temporary table tới cùng một hostgroup cụ thể.
- Specific session/user variables: Tất cả các query có dùng ký hiệu @ thì multiplexing bị disable và không bao giờ được enable lại
Lưu ý rằng: Nếu bạn select 1 variable (ví dụ: @test_var) và bạn không nhận được kết quả như mong đợi thì rất có thể là do routing. Vấn đề mình đã đề cập ở Temporary Tables phía trên.
- Disable By Configuration: Với cấu hình global là mysql-multiplexing có khả năng disable và enable multiplexing trên toàn sessions
UPDATE global_variables SET variable_value='true'
WHERE variable_name='mysql-multiplexing';
7. Delay Parameters
7.1. Tình huống gây lỗi nếu không delay multiplexing
- Giả sử một query INSERT được gửi đến một backend connection A và tạo giá trị auto-increment X.
- Ngay sau đó, app gửi query SELECT LAST_INSERT_ID() để lấy giá trị mới tạo.
- Nếu multiplexing hoạt động bình thường không delay, ProxySQL có thể gửi query LAST_INSERT_ID() lên backend connection B khác, không phải backend connection A đã INSERT.
- Backend B không có thông tin auto-increment mới, trả về sai hoặc NULL.
- App nhận kết quả sai, dẫn đến lỗi logic.
7.2. Vai trò của delay multiplexing (theo số câu query hoặc thời gian)
- Khi ProxySQL nhận được kết quả OK packet từ câu INSERT có auto-increment, nó ghi nhận "backend connection này mới tạo auto-increment".
- ProxySQL sau đó sẽ tạm thời disable multiplexing trên kết nối này theo:
- Số câu query tiếp theo (vd: 5 câu với biến `mysql-auto_increment_delay_multiplex` = 5)
- Hoặc thời gian (vd: 1000 ms với `mysql-connection_delay_multiplex_ms` = 1000)
- Trong khoảng delay này, ProxySQL sẽ không dùng backend connection này để phục vụ query multiplex từ các client khác.
- Đồng thời, các query tiếp theo từ cùng một frontend connection (client) sẽ được "gắn" (pinned) hoặc reuse trên đúng backend connection đã tạo auto-increment.
- Vì vậy, khi frontend connection gửi query SELECT LAST_INSERT_ID(), ProxySQL biết phải gửi query đó trên backend connection vừa thực hiện INSERT, đảm bảo giá trị trả về chính xác.
-- Delay multiplexing 5 lần query sau khi có auto-increment
UPDATE global_variables SET variable_value='5'
WHERE variable_name='mysql-auto_increment_delay_multiplex';
-- Delay multiplexing theo thời gian (milliseconds)
UPDATE global_variables SET variable_value='1000'
WHERE variable_name='mysql-connection_delay_multiplex_ms';
8. Query Rules Control
Query rules trong ProxySQL dùng để điều khiển hành vi xử lý các câu query. Ở trong bài này mình chỉ giới thiệu sơ qua về query rule trong phạm vi multiplexing chứ không đi sâu vào query rules.
8.1. Mục đích của query rules trong kiểm soát multiplexing
- Điều chỉnh multiplexing dựa trên kiểu query: Không phải tất cả các câu query đều phù hợp để multiplexing, ví dụ: câu query sử dụng các user variables hoặc các query cần liên tục trên cùng một backend connection để đảm bảo tính nhất quán.
- Tăng độ chính xác, giảm lỗi: Bằng cách tắt multiplexing (multiplexing=0) với những câu query đặc biệt (ví dụ SET @var), đảm bảo câu query đó không bị chuyển sang backend connection khác, tránh mất trạng thái connection (ví dụ: user variables).
- Vẫn cho phép multiplexing cho những câu query an toàn: Ví dụ đơn giản như SELECT thường được phép multiplexing (multiplexing=1) để tăng hiệu năng.
- Kiểm soát nâng cao: Giá trị multiplexing=2 nghĩa là không disable multiplexing cho các query chứa dấu @ (giả sử câu query cần lấy system variables), cho phép tối ưu mà không gây lỗi.
8.2. Vậy query rules này giải quyết vấn đề gì?
- Giúp ProxySQL thông minh hơn trong việc chọn khi nào nên multiplexing và khi nào không.
- Tránh các lỗi phát sinh do multiplexing, như sai giá trị user variable, hoặc mất đồng bộ kết nối.
- Giúp cân bằng giữa hiệu năng (bằng multiplexing với các query an toàn) và sự chính xác, nhất quán của kết quả (disable multiplexing với các query cần trạng thái connection).
-- Rule-based multiplexing control
INSERT INTO mysql_query_rules (rule_id, active, match_pattern, multiplexing, apply) VALUES
(1, 1, '^SELECT.*', 1, 1), -- Enable cho SELECT
(2, 1, '^SET @.*', 0, 1), -- Disable cho user variables
(3, 1, '^SELECT @@max_allowed_packet', 2, 1); -- Không disable cho system vars
LOAD MYSQL QUERY RULES TO RUNTIME;
SAVE MYSQL QUERY RULES TO DISK;
Multiplexing values:
- 0: Disable multiplexing
- 1: Enable multiplexing
- 2: Không disable multiplexing cho queries chứa @
9. Kết luận
Connection Multiplexing trong ProxySQL là công cụ mạnh mẽ để tối ưu hóa hiệu suất Database, đặc biệt quan trọng trong các hệ thống high-concurrency. Tuy nhiên, để triển khai thành công, chúng ta cần:
- Hiểu rõ limitations: Biết khi nào multiplexing bị disable
- Tuning đúng parameters: Đặc biệt là auto-increment delay
- Monitoring thường xuyên: Theo dõi metrics để đảm bảo hiệu quả
- Application design: Thiết kế ứng dụng multiplexing-friendly:
- Hạn chế sử dụng các session variable và user variable
- Sử dụng START TRANSACTION rõ ràng
- Hạn chế sử dụng các câu query bị phụ thuộc trạng thái connection như LAST_INSERT_ID()
Khi được cấu hình đúng cách, multiplexing có thể giảm 80-90% số lượng backend connections, từ đó cải thiện đáng kể performance và scalability của hệ thống Database.
Các nguồn tham khảo thêm: