Sao lưu - Backup

Full, Incremental hay Differential? Áp dụng trong BareOS

Full, Incremental hay Differential? Áp dụng trong BareOS
Chia sẻ

Differential không bao giờ tiết kiệm đĩa hơn Incremental, Incremental không bao giờ khôi phục nhanh hơn. Bài viết phân tích chi tiết kèm cách viết Schedule để hai job không chạy chồng.

Chọn level backup thường được bàn như chuyện khẩu vị: người thích Differential vì khôi phục nhanh, người thích Incremental vì đỡ tốn đĩa. Thật ra đây là một bài toán đánh đổi và mỗi phương án cấu hình đều có ưu/nhược điểm riêng.

Bài trước đã viết một Schedule đơn giản cho máy chủ web01: Full ngày 1, Incremental các ngày còn lại. Bài này trả lời hai câu hỏi đứng sau dòng cấu hình đó. Thứ nhất, nên dùng Incremental hay Differential, và vì sao. Thứ hai, viết Schedule thế nào để ra đúng lịch mình muốn mà không vô tình chạy hai job trong một đêm.

Ba level và khôi phục cần những gì

Bareos làm việc theo file: một file có thay đổi thì cả file đó được lấy lại. Với định nghĩa đó, ba level khác nhau ở chỗ lấy mốc so sánh:

LevelLưu gìKhôi phục về một mốc cần đọc
FullMọi file trong FileSetChỉ bản Full đó
IncrementalFile thay đổi từ lần backup thành công gần nhất (bất kể level)Bản Full + mọi bản Incremental sau nó tới mốc cần khôi phục
DifferentialFile thay đổi từ bản Full gần nhấtBản Full + một bản Differential ở mốc cần khôi phục

Bareos còn tự nâng level trong vài trường hợp. Khi một job Incremental hoặc Differential không tìm thấy bản Full nào của chính nó, job được nâng lên Full. Như bài trước đã nói, sửa FileSet cũng thường dẫn tới việc nâng level. Nên nếu thấy một job “Incremental” ghi tới vài trăm GB, việc đầu tiên là xem nó có bị nâng lên Full hay không.

Một phép chứng minh

Gọi B_k là tập file thay đổi trong ngày thứ k sau bản Full. Ta so sánh Incremental và Differential trong một chu kỳ n ngày.

Về dung lượng đĩa

  • Incremental ngày k lưu đúng B_k.
  • Differential ngày k lưu D_k = B_1 ∪ B_2 ∪ … ∪ B_k: mọi thứ đã đổi từ bản Full.
  • Tập D_k chứa B_k, nên |D_k| ≥ |B_k| với mọi k.
  • Cộng lại cả chu kỳ: Σ|D_k| ≥ Σ|B_k|. Differential không bao giờ tốn ít đĩa hơn Incremental.

Về lượng byte phải đọc khi khôi phục

  • Incremental khôi phục về ngày n phải đọc Full + |B_1| + |B_2| + … + |B_n|.
  • Differential chỉ đọc Full + |B_1 ∪ … ∪ B_n|.
  • Kích thước của hợp không vượt quá tổng kích thước các tập, nên Differential không bao giờ phải đọc nhiều hơn Incremental.
 Dung lượng đĩaByte đọc khi khôi phục
IncrementalLuôn ≤ DifferentialLuôn ≥ Differential
DifferentialLuôn ≥ IncrementalLuôn ≤ Incremental

Hai bất đẳng thức chỉ trở thành dấu bằng trong những trường hợp đặc biệt, ví dụ ngày nào cũng đổi đúng cùng một tập file. Nhưng không bao giờ đảo chiều. Nói cách khác: đây là đánh đổi thuần, không có cấu hình nào được cả hai.

Một ví dụ bằng số

Máy chủ db01 có bản Full 100 GB. Mỗi ngày có 5 GB file thay đổi. Trong đó 3 GB là cùng một nhóm file ngày nào cũng bị ghi lại, 2 GB còn lại là file mới khác nhau mỗi ngày. Sau sáu ngày:

NgàyIncremental ghiDifferential ghi
15 GB5 GB
25 GB7 GB
35 GB9 GB
45 GB11 GB
55 GB13 GB
65 GB15 GB
Tổng đĩa30 GB60 GB
Đọc khi khôi phục ngày 6130 GB115 GB

Differential trả gấp đôi đĩa để khôi phục nhanh hơn khoảng 12%. Có đáng hay không tuỳ bạn cần gì, nhưng phép toán không cho phép kỳ vọng “tiết kiệm đĩa nhờ Differential”.

Vậy khi nào chọn Differential

Differential hợp khi thời gian khôi phục là ràng buộc chính và chuỗi Incremental dài: ví dụ Full mỗi tháng, Incremental mỗi ngày, tức có thể phải đọc gần ba mươi mắt xích. Khi chuỗi ngắn, lợi ích gần như biến mất. Theo kinh nghiệm của chúng tôi với lịch Full tháng và Incremental tuần (chuỗi ba bốn mắt), Differential tốn thêm khoảng 7–16% đĩa để đổi lấy tốc độ khôi phục nhanh hơn chỉ 0–7%.

Còn một lựa chọn thứ tư là Always Incremental kết hợp Virtual Full, đã nhắc ở bài 1: chỉ chạy Incremental, định kỳ ghép thành bản Full tổng hợp ngay trên máy chủ backup. Nó đáng cân nhắc, nhưng hãy thử khôi phục thật từ bản Virtual Full trước khi tin vào nó. Không phải nguồn dữ liệu hay plugin nào cũng hỗ trợ việc ghép này.

Cảnh báo: một con số trung bình có thể kết luận ngược

Trong quá trình vận hành, đội kỹ thuật Cloudzone từng suýt kết luận sai vì một con số tổng. Một máy chủ có tổng dung lượng Incremental trong chu kỳ gấp khoảng 8,6 lần bản Full. Con số đó gợi ý dữ liệu bị ghi đi ghi lại rất nhiều, tức Differential (lưu phần hợp) sẽ thắng đậm về đĩa.

Soi kỹ thì cả chu kỳ chỉ có một job đột biến: ai đó chép một file dump khổng lồ vào thư mục được backup, rồi xoá đi. Các Incremental khác đều bình thường. Bỏ job đó ra, tỷ lệ còn khoảng 1,8 lần, và kết luận đổi hẳn.

Bài học: mọi chỉ số dựa trên tổng hay trung bình phải xem phân phối trước khi kết luận. Liệt kê dung lượng từng job theo ngày, tìm các giá trị vượt hẳn phần còn lại, hỏi xem chúng từ đâu ra. Một outlier có thể là byte thật cần tính vào dung lượng, nhưng không nên để nó quyết định chọn level cho cả hệ thống.

Viết lịch trong Schedule

Mỗi dòng Run trong Schedule gồm level, tập ngày và giờ chạy. Một số mẫu hay dùng:

Dòng RunÝ nghĩa
Run = Level=Incremental daily at 21:00Mỗi ngày
Run = Level=Incremental mon-fri at 21:00Thứ Hai tới thứ Sáu
Run = Level=Full 1st sun at 21:00Chủ nhật đầu tiên của tháng
Run = Level=Differential 2nd-5th sun at 21:00Các Chủ nhật còn lại
Run = Level=Full on 1 at 21:00Ngày 1 hằng tháng
Run = Level=Full on 1,11,21 at 21:00Ngày 1, 11, 21
Run = Level=Incremental on 2-10,12-20,22-31 at 21:00Mọi ngày trừ 1, 11, 21

Một chi tiết nhỏ nhưng tốn thời gian: luôn viết từ on trước danh sách ngày trong tháng. Tài liệu mô tả on là từ tuỳ chọn, nhưng khi thử trên thực tế, dòng Run = Level=Full 1 at 21:00 bị bareos-dir -t từ chối với lỗi kiểu expected a name, got BCT_NUMBER. Viết on 1 thì qua.

Ví dụ: Full đầu tháng, Incremental thứ Sáu

Giả sử muốn Full vào ngày 1, và Incremental mỗi thứ Sáu, trừ thứ Sáu nằm trong tuần đã có Full. Có thể viết:

Schedule {
  Name = "lich-thang"
  Run = Level=Full on 1 at 21:00
  Run = Level=Incremental fri on 6-31 at 21:00
}

Dòng thứ hai kết hợp hai điều kiện: là thứ Sáu, và nằm trong khoảng ngày 6–31. Vì sao lại là ngày 6? Thứ Sáu cùng tuần với ngày 1 (tuần tính từ thứ Hai) luôn rơi vào ngày 1 tới ngày 5. Nếu ngày 1 là thứ Hai thì thứ Sáu đó là ngày 5; nếu ngày 1 là thứ Năm thì là ngày 2. Loại bỏ ngày 1–5 là loại đúng thứ Sáu đó, không hơn không kém.

Sơ đồ minh hoạ: Full, Incremental hay Differential? Một phép chứng minh

Với tháng 10/2026 (ngày 1 là thứ Năm), lịch trên cho một Full ngày 1 và bốn Incremental vào các ngày 9, 16, 23, 30. Thứ Sáu ngày 2 bị bỏ vì chỉ cách Full một ngày. Mô phỏng nhiều tháng liền, mỗi tháng luôn có đúng một Full và ba hoặc bốn Incremental.

Cách một dòng Run kết hợp thứ trong tuần với dải ngày có thể khác nhau giữa các phiên bản. Trước khi dùng, hãy kiểm bằng bareos-dir -t, rồi xem Director thực sự định chạy gì trong các ngày tới:

echo "status scheduler days=40" | bconsole

Mô phỏng lịch trước khi áp

Với lịch phức tạp, một đoạn script nhỏ liệt kê ngày chạy qua vài năm sẽ bắt được những ca biên mà đọc bằng mắt khó thấy:

from datetime import date, timedelta

d = date(2026, 10, 1)
while d < date(2028, 10, 1):
    full = d.day == 1
    incr = d.weekday() == 4 and 6 <= d.day <= 31   # thứ Sáu, ngày 6-31
    assert not (full and incr), f"Hai job cùng đêm: {d}"
    if full:
        print(d, "Full")
    elif incr:
        print(d, "Incremental")
    d += timedelta(days=1)

Dòng assert kiểm đúng bất biến quan trọng nhất của mọi Schedule nhiều dòng, là chủ đề của phần tiếp theo.

Bẫy và bài học vận hành

Hai dòng Run trùng ngày thì chạy cả hai

Nhiều người nghĩ khi hai dòng Run cùng khớp một ngày, dòng Full sẽ “thắng”. Không phải vậy. Tài liệu Bareos ghi rõ: hai dòng Run cùng thời điểm thì hai job cùng khởi chạy. Cộng với Allow Duplicate Jobs mặc định là yes, máy chủ đó sẽ bị backup hai lần trong một đêm, một Full và một Incremental.

Lỗi này không làm hỏng dữ liệu nên rất khó thấy. bareos-dir -t không bắt được nó. Thường nó chỉ lộ ra khi nhìn biểu đồ dung lượng đĩa hôm sau. Cách phòng:

  • Các dòng Run phải có tập ngày tách rời nhau. Đừng viết Incremental daily chồng lên Full on 1.
  • Bareos không có cú pháp loại trừ kiểu “mọi ngày trừ ngày 1”. Phải liệt kê hết, như on 2-31.
  • Lưới an toàn thứ hai: đặt Allow Duplicate Jobs = no và Cancel Lower Level Duplicates = yes trong JobDefs. Khi hai job trùng, Bareos huỷ job có level thấp hơn. Chỉ dùng nó làm đai phụ, không thay cho việc tách ngày.

Lệch giờ không cứu được

Một cách “chữa” hay gặp là cho Full chạy 21:00 và Incremental 23:00 cùng ngày. Với máy chủ nhỏ, Full xong trước 23:00 nên có vẻ ổn. Với máy chủ lớn, hoặc khi nhiều Full xếp hàng, Full có thể còn đang chạy lúc 23:00. Lúc đó Incremental không có bản Full mới làm mốc, và có thể lấy mốc là lần backup thành công của chu kỳ trước. Kết quả là một Incremental chứa thay đổi của cả chu kỳ, ghi trùng phần lớn với bản Full đang chạy.

Tách ngày giải quyết gốc vấn đề. Lệch giờ chỉ đẩy vấn đề sang lúc hệ thống bận nhất.

Thời gian khôi phục đi theo chuỗi, không theo bản Full

Khi ước lượng thời gian khôi phục, đừng chỉ nhìn kích thước bản Full. Với Incremental, phải đọc bản Full cộng toàn bộ Incremental sau nó. Máy chủ thay đổi nhiều có thể có tổng Incremental lớn hơn cả bản Full, tức khôi phục phải đọc gấp đôi dung lượng thật của máy chủ. Bài 9 sẽ quay lại chuyện này khi diễn tập khôi phục.

Level quyết định cả pool

Nếu muốn giữ bản Full lâu hơn bản Incremental, có thể khai Full Backup Pool và Incremental Backup Pool trong JobDefs để mỗi level ghi vào pool riêng. Một job Incremental bị nâng lên Full cũng sẽ đi vào pool Full. Chuyện retention theo pool là nội dung của bài 5.

Tóm tắt

  • Incremental luôn tốn đĩa ít hơn hoặc bằng Differential; Differential luôn đọc ít hơn hoặc bằng khi khôi phục. Không cấu hình nào được cả hai.
  • Differential chỉ đáng giá khi chuỗi Incremental dài và thời gian khôi phục là ràng buộc chính.
  • Lọc outlier trước khi dùng tổng hay trung bình để chọn level.
  • Các dòng Run phải có tập ngày tách rời nhau; luôn viết on trước số ngày.
  • Kiểm lịch bằng bareos-dir -t, status scheduler và một đoạn mô phỏng nhiều tháng.

Bài sau: Retention: vì sao đĩa backup cứ đầy — Job, File và Volume retention khác nhau thế nào, và vì sao thứ quyết định dung lượng đĩa là volume chứ không phải job.

Bài viết thuộc chuỗi “Bareos từ cài đặt tới vận hành” của đội kỹ thuật Cloudzone.

Chia sẻ

Bài viết liên quan