- ngày ghi
- phút đọc
- 6
- nguồn dẫn
- 7
Sao lưu chưa mở lại đếm thì chưa phải sao lưu
Ba đêm nhật ký ghi 'đã sao lưu kho' trong khi kho thật 12.282 dòng, file được chép 9.027. Ba cổng chặn tôi dựng sau đó, mỗi cổng từ một sự cố có ngày tháng.
- vận hành
- dữ liệu
- cổng kiểm
Tôi vận hành một kho lãi suất ngân hàng cập nhật hằng ngày. Bảng quan trọng nhất trong đó là bảng biểu lãi suất theo ngày. Nó ghi biểu lãi suất của từng ngân hàng theo từng ngày, chỉ ghi thêm, lịch sử giữ nguyên. Biểu hôm qua đã rời khỏi trang của mọi ngân hàng. Mất bảng đó là mất vĩnh viễn, cào lại cũng chịu. Vì thế sao lưu là việc chính. Và tôi đã làm hỏng nó theo cách im lặng nhất có thể.
Ba đêm nhật ký báo xong, kho thật đứng ngoài
Ngày 09/08/2026 tôi chuyển kho từ file SQLite trên máy lên Turso. Biến cấu hình bản sao dưới máy để trống, nên file kho cũ đóng băng từ đúng hôm đó. Chương trình chạy đêm vẫn giữ nguyên dòng cũ: chép chính file đó sang thư mục sao lưu, rồi ghi nhật ký "đã sao lưu kho".
Ba đêm liền nó chép lại cùng một file chết. Đo ngày 11/08/2026:
| Bảng | Kho thật trên mây | File đang được "sao lưu" |
|---|---|---|
| Biểu lãi suất theo ngày | 12.282 | 9.027 |
| Tín hiệu mạng xã hội | 2.449 | 2.029 |
Có nhật ký, có file, có cảm giác an toàn. Kho thật thì đứng ngoài cả ba. Tôi gọi đây là thành công giả: mỗi lệnh chạy hết, nhật ký sạch, kết quả sai.
Một ngày trước đó, 10/08/2026, dịch vụ đồng bộ tệp trên máy thay mất kho: file 5,5 MB còn 53 KB, mọi bảng rỗng. Cứu được nhờ một bản sao tình cờ có từ 01:05 cộng với 88 file bài thô. Cơ chế mới thay được may mắn. Hai sự cố dồn trong ba ngày là đủ để tôi viết lại chương trình sao lưu từ đầu, với ba cổng chặn. Mỗi cổng gắn với một chuyện đã xảy ra.
Cổng 1: đọc thẳng kho đang chạy
Chương trình mới bỏ hẳn bước chép file. Nó mở kết nối tới kho đang chạy, bất kể là Turso hay SQLite dưới máy, đọc từng bảng rồi ghi ra một file SQLite mới. Bảng kê đi kèm ghi luôn loại kho đã đọc, để người mở bản sao lưu biết nó rút từ đâu.
Turso trả kết quả qua mạng theo từng khúc. Đọc trọn một bảng lớn bằng một câu truy vấn duy nhất làm luồng đứt giữa chừng. Đêm 21/08/2026 lượt sao lưu chết đúng như vậy, và bản gần nhất lúc đó đã 5 ngày tuổi. Lỗi nặng dần theo kích thước kho, nên cách chữa phải nằm ở cách đọc. Tôi chuyển sang đọc từng lô 2.000 dòng theo số thứ tự dòng, lô nào đứt thì thử lại một lần.
Cổng 2: kho co lại thì dừng
Ba bảng trong kho chỉ được ghi thêm: biểu lãi suất theo ngày, tín hiệu mạng xã hội, và nhật ký các lượt cào. Số dòng của chúng chỉ được tăng. Trước khi ghi bản mới, chương trình đọc bảng kê của bản trước và so từng bảng. Bản mới ít dòng hơn ở bất kỳ bảng nào trong ba bảng đó, chương trình in ra bảng nào mất bao nhiêu dòng, rồi dừng lại trước khi ghi.
Kho co lại có ba khả năng: kho bị thay như vụ 10/08, có người xoá nhầm, hoặc chương trình đang đọc một kho khác kho hôm qua. Cả ba đều cần người nhìn vào trước khi bản cũ bị đè. Nếu thật sự cố ý, chạy lại kèm một cờ nói rõ là tôi đã biết và vẫn muốn ghi. Cờ đó tồn tại để việc bỏ qua cổng luôn là một quyết định có chữ ký.
Vì cổng này đếm dòng, mọi thứ khác trong chương trình phải giữ số dòng nguyên vẹn. Bản đi ra khỏi máy cần bỏ dữ liệu cá nhân, tôi che cột và giữ nguyên mọi dòng. Xoá dòng ở đó là tự tay bịt mắt cái cổng này.
Cổng 3: ghi xong mở lại đếm
Sau khi nén và ghi file, chương trình mở lại chính bản vừa ghi và làm năm việc: bảng kê phải có; mã kiểm tra sha256 của file phải khớp bảng kê; giải nén ra thư mục tạm; chạy bài kiểm toàn vẹn của SQLite; đếm từng bảng và so với số trong bảng kê. Sai một dòng là xoá cả file lẫn bảng kê, và báo lỗi. Bản sao lưu là file đã mở lại được.
Cổng này bắt lỗi đầu tiên ngay trong chính chương trình. Hàm tôi dùng để suy ra tên bảng kê chỉ thay phần đuôi cuối cùng của tên file, nên nó đi tìm một cái tên không tồn tại. Bản sao lưu đầu tiên tự huỷ ngay sau khi ghi vì thiếu bảng kê đi kèm. Bực, nhưng đúng: cổng làm việc của nó trước cả khi chương trình làm việc của mình.
Bảng kê ghi thời điểm, loại kho, số dòng từng bảng, kích thước nén, mã kiểm tra. Một cờ riêng chạy lại bài kiểm đó trên bản mới nhất bất cứ lúc nào. Chương trình giữ 14 bản gần nhất cộng bản cũ nhất, vì lỗi phát hiện muộn cần một mốc xa hơn hai tuần.
Nhịp hằng ngày gọi sao lưu trước khi ghi bất cứ thứ gì vào kho, ở chế độ không kèm dữ liệu cá nhân: bỏ bảng chứa số điện thoại người đọc để lại, che cột link bài gốc bằng một chuỗi cố ý không giống địa chỉ web. Che bằng chuỗi thay cho ô trống, vì cột đó khai là bắt buộc và một bản trích chỉ thành bản sao lưu khi nạp lại được. Bước tải lên chỉ nhận file đã che; thiếu file khớp thì bước đó phải đỏ. Sổ tay ghi giữ 90 ngày, cấu hình thực tế đặt 30, kèm chú thích: chính sách công bố hứa xoá dữ liệu cá nhân sau 90 ngày, giữ bản trích đúng 90 ngày là đặt hạn của bản sao bằng hạn của bản gốc.
Thành công giả thứ hai: trang phục vụ 16 ngân hàng khi kho có 29
Hệ thống còn báo xanh mà sai ở một chỗ nữa. Nhịp hằng ngày có bước 6 xuất dữ liệu từng trang ra từ kho, và bước 7 dựng trang tĩnh. Bước 7 đọc các file vừa xuất ra thay vì đọc thẳng kho. Quên bước 6 là dựng lại trang bằng dữ liệu cũ, mọi thứ vẫn báo thành công. Đã xảy ra thật: trang phục vụ bản 16 ngân hàng trong khi kho đã có 29.
Cùng một họ với vụ sao lưu. Mỗi bước xanh theo tiêu chí của riêng nó. Câu quan trọng vẫn để trống: cái vừa làm ra có khớp kho không. Cách chữa cũng cùng một họ: ghi thứ tự bắt buộc vào sổ tay vận hành, và đặt bước đếm ở đầu ra thay vì tin vào mã trả về.
Điều tôi rút ra. Nhật ký "đã xong" chỉ chứng minh lệnh chạy hết; việc xong hay chưa phải đo ở đầu ra. Cổng kiểm đặt ở đầu ra và so với nguồn sự thật, thay vì so với chính thứ vừa tạo ra nó. Mỗi cổng nên gắn với một sự cố có ngày tháng, vì cổng có sự cố đứng sau mới giữ được khi ai đó muốn bỏ. Và bản sao lưu đáng tin là bản đã từng được mở lại và đếm.
Nếu bạn cũng đang dựng một hệ thống chạy đêm mà chưa từng mở lại bản sao lưu của nó, kể tôi nghe bối cảnh.