17/08/2018, 20:24

Những điều cần biết về PostgreSQL Vacuuming

Khi làm việc với PostgreSQL, có thể bạn đã gặp autovacuum daemon *. Nó luôn xuất hiện, tiêu hao tài nguyên rồi biến mất. Nếu bạn vẫn còn mơ hồ về autovacuum daemon thì trong bài viết này, tôi sẽ giúp các bạn tìm hiểu thêm về vacuuming, những công việc mà autovacuum daemon đang ...

Khi làm việc với PostgreSQL, có thể bạn đã gặp autovacuum daemon*. Nó luôn xuất hiện, tiêu hao tài nguyên rồi biến mất. Nếu bạn vẫn còn mơ hồ về autovacuum daemon thì trong bài viết này, tôi sẽ giúp các bạn tìm hiểu thêm về vacuuming, những công việc mà autovacuum daemon đang thực thi và cách làm quen với chúng.

Trong hệ điều hành đa nhiệm, khái niệm daemon ám chỉ các process chạy nền (background process). Mỗi daemon sẽ perform một vài hành động cụ thể tại một thời điểm được xác định trước hoặc để đáp lại event cố định nào đó.

Vacuuming???

Khái niệm vacuuming liên quan mật thiết với các tính năng của hệ quản trị CSDL quan hệ.

Một hệ quản trị CSDL quan hệ (RDBMS) phải có khả năng điều khiển tương tranh (concurrency control) giữa các transaction. Nói một cách dễ hình dung hơn, nếu 2 transaction thực thi cùng một lúc thì nguyên tắc thực thi là thực thi độc lập. Một transaction không thể “nhìn” thấy một transaction khác – không nhìn thấy ở đây là không tác động lẫn nhau, chủ yếu trên dữ liệu. Concept này được gọi là Transaction Isolation – tương đương yếu tố I trong ACID…Quản lý trạng thái dữ liệu (data state) là một việc phức tạp bởi thực tế, RDBMS vừa phải đảm bảo lưu trữ các bảng cũng như index của chúng, vừa phải quản lý data visibility. RDBMS không thể tùy tiện chỉnh sửa primary data structure nhằm tránh lỗi invalid state.

Với bài toán điều khiển tương tranh, PostgreSQL đưa ra giải pháp  MVCC – điều khiển tương tranh đa phiên bản – Multi Version Concurrency Control. Ý tưởng cơ sở của nó là đánh dấu các bản ghi bị tác động bởi transaction và quản lý visibility của chúng một cách phù hợp. Mỗi transaction được gán một ID – ID này là số nguyên 32 bit. Sau đó, các bản ghi sẽ được đánh dấu theo ID của transaction đã tác động lên nó lần cuối cùng. ID này được lưu trong 2 cột xmin và xmax. Nếu ta không truy vấn tới 2 cột này, chúng mặc định được ẩn đi. Hãy xét một ví dụ, ở ví dụ này, ta INSERT dữ liệu vào bảng và kiểm tra transaction ID tương ứng với các bản ghi được INSERT.

Ta sử dụng bảng person với các trường như sau:

Tiếp đó, tạo và commit một transaction:

Trên máy của tôi, câu lệnh “SELECT txid_current();” trả về giá trị 627.

Khi ta truy vấn tới 2 cột xmin xmax, ta có thể thấy giá trị tương ứng:

Như vậy, giá trị của xmin (ứng với bản ghi vừa được chèn vào) được gán bằng ID của transaction vừa commit. Bất kỳ transaction nào xảy ra trước nó (có ID nhỏ hơn) đều không thể thấy được bản ghi này. Đó là xmin, còn với xmax thì ngược lại. Giá trị xmax là ID của transaction xóa bản ghi tương ứng, vì thế, tất cả transaction xảy ra sau nó đều không thấy được bản ghi này. Thường thì điều kiện để một transaction có thể “nhìn thấy” một bản ghi là xmin < txid_current < xmax. (txid_current là giá trị của transaction ID).

MVCC không phải là giải pháp điều khiển tương tranh duy nhất của các RDBMS. Mỗi hệ quản trị CSDL quan hệ đều có một cơ chế tương ứng. Trong khi PostgreSQL trung thành với MVCC thì Oracle và MySQL sử dụng rollback segments. Ưu điểm của MVCC so với các giải pháp khác nằm ở “chi phí” cho việc rollback transaction: sau khi rollback, ta không cần clean up. Tài nguyên (bộ nhớ và bộ xử lý) vẫn giống như trạng thái ban đầu.

Tìm hiểu thêm một chút về Vacuum:

Vacuuming chủ yếu đóng vai trò như một garbage collector(cơ chế thu hồi các vùng nhớ đã được cấp phát).  Như chúng ta đã biết, khi một bản ghi được UPDATE hoặc DELETE, PostgreSQL không hoàn toàn gỡ bản ghi đó ra khỏi bộ nhớ vật lý, hậu quả là sau một thời gian sử dụng mà không được “dọn dẹp”, database của chúng ta sẽ ngốn kha khá bộ nhớ (tùy thuộc vào tần suất của các phép UPDATE và DELETE).

Bên cạnh vai trò là garbage collector, Vacuuming còn liên quan tới Visibility MapTransaction ID Wraparound(tạm dịch: giới hạn của transaction ID).

Visibility Map là một giải pháp của PostgreSQL nhằm tránh các truy xuất không cần thiết tới vùng heap (heap là nơi chứa dữ liệu thực tế của các bản ghi)…. Ta xét tình huống sau: khi một câu SQL truy vấn tới các bản ghi trong index*, PostgreSQL phải kiểm tra trạng thái các bản ghi đó (trạng thái ở đây là visible hoặc invisible) bằng cách lấy (fetch) dữ liệu từ vùng heap. Quá trình kiểm tra này được bỏ qua nhờ Visibility Map. Visibility Map sẽ ghi lại những trang (thuộc vùng heap) chứa dữ liệu ở trạng thái visible. Nhờ đó, PostgreSQL không phải truy cập tới heap để kiểm tra trạng thái dữ liệu nữa.

Index là tập hợp các lookup tables (bảng tìm kiếm) được sử dụng bởi search engine của database nhằm tăng tốc độ lấy dữ liệu – index trong database giống như phần mục lục của mọi cuốn sách.

Transaction ID Wraparound (giới hạn của transaction ID) là cụm từ ngắn gọn để diễn tả một sự thật hiển nhiên: ID của mỗi transaction là số nguyên 32 bit, do đó, nó không thể lớn hơn 232 … Một khi số lượng transaction mà database phải xử lý vượt quá 232, transaction ID sẽ overflow và quay về 0. Nếu database không phải đảm đương tác vụ nào nữa, hầu hết tất cả bản ghi sẽ bị ẩn đi. Với vấn đề này, PostgreSQL đưa ra giải pháp: gán một giá trị nhỏ(vừa đủ) cho xmin của mỗi bản ghi – giá trị ấy được gọi là FrozenTransactionID (còn được biết đến với cái tên FrozenXID). Giá trị của FrozenTransactionID luôn được coi là nhỏ hơn các TransactionID khác. Việc gán giá trị FrozenTransactionID cho xmin là một công đoạn của quá trinh vacuuming. Nếu ta không chú ý vacuuming thường xuyên, các dữ liệu cũ có nguy cơ rơi vào trạng thái invisible (không thể truy vấn những dữ liệu này dù chúng vẫn tồn tại).

Vacuuming kiểu thủ công khá là đơn giản, gõ vào psql lệnh VACUUM hoặc VACUUM VERBOSE. Cả hai lệnh trên đều nhận một argument tương ứng với tên một bảng thuộc database. Bỏ trống argument đó đồng nghĩa rằng lệnh VACUUM sẽ được áp dụng cho toàn bộ database.

Thực thi lệnh VACUUM ứng với cấp độ đầu tiên của quá trình vacuuming: xóa bỏ các bản ghi và cập nhật Visibility Map. Lưu ý rằng VACUUM không trả lại dung lượng bộ nhớ “thừa” cho OS – điều này có vẻ trái ngược với những gì tôi nói ở trên. Thực ra là thế này, VACUUM sẽ cập nhật FSMFree Space Map để đánh dấu các trang có free space (free space này là dung lượng “thừa ra” sau quá trình DELETE hoặc UPDATE các bản ghi), sau này, khi tạo các bản ghi mới, thay vì “xin thêm” bộ nhớ từ hệ thống, PostgreSQL sẽ xem xét các Free Space Map và sử dụng free space trên các trang này… Nếu muốn “lấy lại” tất cả free space, chúng ta cần lệnh VACUUM FULL. Lệnh này tái xử lý dữ liệu trên các bảng, rewrite chúng, nhờ đó, lượng bộ nhớ tiêu hao sẽ đúng bằng lượng dữ liệu trên các bảng. Tuy nhiên, hãy cân nhắc khi dùng lệnh này vì các bảng được xử lý bởi VACUUM FULL sẽ bị khóa tạm thời, khi các bảng bị khóa, ta không thể đọc và truy vấn chúng.

VACUUM và ANALYZE hoàn toàn khác nhau:

PostgreSQL sẽ dựa vào các số liệu thống kê để sinh ra câu truy vấn tối ưu. Việc cập nhật các số liệu thống kê ấy là nhiệm vụ của lệnh ANALYZE, không phải của VACUUM. Dù vậy, ta vẫn có thể kết hợp đồng thời cả hai: VACUUM ANALYZE. Phạm vi thực thi lệnh ANALYZE cũng tương tự như VACUUM: truyền tên một bảng nào đó hoặc mặc định áp dụng cho toàn bộ database. Một điều thú vị về cú pháp của lệnh này, đó là ANALYZE và ANALYSE đều cho kết quả tương tự!

Autovacuum daemon:

Kể từ phiên bản 8.1, nhằm đơn giản hóa quá trình sử dụng database, PostgreSQL cung cấp autovacuum daemon – một daemon chạy đồng thời VACUUM và ANALYZE trong một khoảng thời gian cố định (hay còn gọi là interval). Autovacuum daemon chạy như một daemon bình thường, ta có thể kiểm tra sự hiện diện của nó với câu lệnh ps aux | grep autovacuum. Trong một khoảng thời gian cố định, Autovacuum daemon kiểm tra mỗi database xem chúng có cần vacuuming hoặc analyzing không. Cụ thể: xét một bảng bất kỳ, nếu số lượng bản ghi được UPDATE hoặc DELETE lớn hơn “ngưỡng” của bảng đó thì tiến trình autovacuuming sẽ được thực thi. Số lượng các bản ghi đã UPDATE hoặc DELETE được lưu ở phần dữ liệu thống kê. Ta hãy thử một câu truy vấn tới phần dữ liệu thống kê, in ra số lượng các bản ghi đã UPDATE và DELETE:

Kết quả tương ứng:

n_tup_del là số các bản ghi được DELETE, n_tup_upd là số các bản ghi được UPDATE, tup là viết tắt của tuple – bộ. Khái niệm tuple xuất hiện trong mô hình dữ liệu quan hệ, nó tương đương với bản ghi.

Giá trị “ngưỡng” của một bảng được tính bằng công thức:

Giá trị của autovacuum_vacuum_threshold nằm ở bảng pg_settings, trong khi đó, ta lấy được number_of_tuples nhờ câu SQL sau:

Kết hợp lại, ta được một câu truy vấn trả về kết quả xấp xỉ với “ngưỡng” mà autovacuum vạch ra cho quá trình vacuuming:

Và kết quả đối với database của tôi:

F = false, như vậy, database của tôi chưa cần vacuuming.

Bạn nên nhớ rằng, số liệu thống kê từ pg_stat_all_tables được tích trữ từ thời điểm pg_stat_archiver.stats_reset. Tài liệu của PostgreSQL không đề cập chính xác rằng số liệu thống kê ở khoảng thời gian nào sẽ được autovaccum daemon sử dụng, nhưng tôi chắc chắn rằng chỉ có các bản ghi được UPDATE hoặc DELETE sau lần vacuum gần nhất mới được tính.

Đối với Analyzing, autovacuum daemon cũng tính toán “ngưỡng” tương tự như vậy. Phần Routine Vacuuming trong tài liệu của PostgreSQL sẽ mô tả phần này chi tiết hơn.

Cải tiến quá trình vacuuming:

Như tôi đã đề cập ở đầu bài viết, một vấn đề của autovacuum daemon là nó xuất hiện tại những thời điểm không báo trước. Vấn đề này kéo theo nhiều bất tiện, thậm chí là nguy hiểm: nếu autovacuum daemon chạy vào “giờ cao điểm” của hệ thống, performance sẽ sụt giảm trầm trọng. Một triệu chứng khác khi chế độ autovacuum hoạt động không đúng lúc, đó là các câu truy vấn không được thực thi một cách tối ưu. Lý do: dữ liệu thống kê từ các bảng không chính xác. Sự thiếu chính xác này có thể được giảm bớt nhờ lệnh ANALYZE. Khi ấy, ta thêm ANALYZE vào như một phần của quá trình vacuuming. Mô phỏng chính xác từng hành vi của autovacuum không hề đơn giản chút nào.  Trường hợp lý tưởng là xác định được dữ liệu thống kê đã lỗi thời hay chưa – tuy nhiên rất khó làm được điều này.

Tuyệt đối không nên tắt tính năng autovacuum. Dù chúng ta thường xuyên vacuum thủ công đi chăng nữa thì luôn luôn tồn tại những hành động không mong muốn – chúng tác động lên rất nhiều bản ghi.  Bên cạnh đó, khi ta vacuum kiểu thủ công, autovacuum hầu như không làm gì nhiều. Vì thế, duy trì autovacuum cũng là điều dễ hiểu. Để mọi việc diễn ra suôn sẻ, lời khuyên hợp lý nhất là thiết lập tần suất vacuum hợp lý cho các bảng dữ liệu lớn cũng như các bảng thường xuyên hoạt động.

Câu SQL sau sẽ giúp ta biết bảng nào chứa nhiều bản ghi nhất:

Nếu nắm bắt được chu kỳ low load của các bảng dữ liệu lớn, bạn có thể lên lịch tự động (cron job) để vacuum chúng. Bên cạnh đó, ta hoàn toàn có thể thay đổi các vacuum parameter ứng với từng bảng để quá trình vacuuming diễn ra thường xuyên hơn. Ta xét 2 câu SQL sau:

Sau khi thực thi 2 câu SQL này, autovacuum sẽ tiến hành vacuum cho bảng person nếu số lượng bản ghi được UPDATE hoặc DELETE lớn hơn 4000 (chú ý: số lượng bản ghi được UPDATE hoặc DELETE chứ không phải số bản ghi hiện có của bảng). Nhờ việc vacuuming thường xuyên, thời gian vacuum mỗi lần sẽ ngắn hơn và performance được duy trì tốt hơn. Chi tiết phần settings của mỗi bảng dữ liệu được lưu trong bảng pg_class, ta dễ dàng lấy được chúng nhờ câu truy vấn:

Một phép thử đơn giản với pg_dumppg_restore đã chứng minh rằng những thay đổi gây ra bởi các lệnh ALTER bên trên được lưu giữ trong cả hai process dump và restore. Vì vậy, ta không cần chạy lại những lệnh ấy đối với các instance mới của database nếu ta khởi tạo các instance này thông qua SQL dump.

Techtalk via Techmaster

0