thực hiện được chuẩn và kết nối hệ thống history
Mình đã tìm ra nguyên nhân, đang sửa History.cs, AlarmStore.cs và ScadaEngine.cs.
Mình đã dừng lại, chưa có file nào bị ghi. Mình chưa chạy gì ở lượt này nên các file History.cs, AlarmStore.cs, ScadaEngine.cs vẫn như bản gốc của bạn.
Đây là kết quả kiểm tra, đã đọc code nhưng chưa chạy thử:
Nguyên nhân khả dĩ nhất: dữ liệu 07:00–15:00 chưa bao giờ nằm an toàn trên đĩa.
History.csvàAlarmStore.csghi SQLite ở chế độ WAL vớiPRAGMA synchronous=NORMAL. Ở chế độ này mỗi lần commit chỉ ghi vào file WAL, không ép xuống đĩa (fsync).- Dữ liệu chỉ được ép xuống đĩa khi SQLite checkpoint, và checkpoint tự động chỉ chạy khi WAL vượt khoảng 1000 trang (~4 MB). Dự án của bạn ghi mỗi tag 30 giây một điểm nên có thể 8 tiếng chưa đủ 4 MB.
- Code cũng không tự checkpoint định kỳ. Chỉ có
Shrink()làm việc đó, và hàm này chỉ chạy khi xoá dữ liệu cũ. - Máy reset đột ngột thì phần WAL chưa fsync (từ 06:30 đến lúc reset) mất hết. Điều này khớp với báo cáo: có dữ liệu đến 06:30, trống đến 15:00, rồi có lại từ 15:30 khi chương trình chạy lại.
Cách sửa mình định làm (ở History.cs, AlarmStore.cs, ScadaEngine.cs):
- Đổi
synchronous=NORMALthànhFULL, và đặtwal_autocheckpoint=200, để mỗi lô ghi (vài giây một lần) được fsync ngay. - Thêm checkpoint nền định kỳ (khoảng 20 giây một lần) cho history và alarm.
- Khi chương trình dừng bình thường thì checkpoint ép xuống đĩa lần cuối.
Mình chưa kiểm chứng được giả thuyết này trên máy của bạn. Bạn có muốn mình áp dụng 3 thay đổi trên không? Nếu bạn gửi thêm thư mục history của máy đó (file history.db, history.db-wal, history.db-shm), mình xem được dữ liệu 07:00–15:00 có thật sự mất khỏi DB hay chưa. Máy bạn reset do mất điện, treo hay tắt thường (không qua Shutdown) cũng giúp mình đoán chắc hơn.Mình đã dừng lại, chưa file nào bị ghi. Lệnh sửa bị từ chối lần thứ hai nên mình không thử lại.
Mình hiểu “tiếp đi” là muốn sửa luôn, nhưng có thể bạn không muốn mình ghi file theo cách này, hoặc muốn xem trước thay đổi. Bạn chọn giúp mình một hướng:
- Mình ghi thẳng vào project như các lần trước rồi gửi file zip. Bạn bấm cho phép lệnh ghi khi hiện ra.
- Mình chỉ đưa đoạn code cần thêm vào đây để bạn tự dán vào
History.cs,AlarmStore.cs,ScadaEngine.cs. - Bạn muốn dừng sửa, hoặc đổi cách tiếp cận (ví dụ gửi thư mục
historyđể mình kiểm tra dữ liệu trước).
Nếu bạn chọn tự dán, đây là nội dung ba thay đổi:
History.csvàAlarmStore.cs, trong hàmOpen(): đổiPRAGMA synchronous=NORMAL;thànhPRAGMA synchronous=FULL; PRAGMA wal_autocheckpoint=200;.History.cs: thêm hàmCheckpoint(bool force)chạyPRAGMA wal_checkpoint(PASSIVE)tối đa 20 giây một lần (bảnTRUNCATEkhiforce), và gọi nó ở cuốiFlush(). TrongAlarmStore.Flush()thì thêm lệnh checkpoint PASSIVE tương tự ngay sautx.Commit().ScadaEngine.cs, dòngtry { History.Flush(); } catch { }lúc dừng chương trình: đổi thànhtry { History.Flush(); History.Checkpoint(true); } catch { }.
history_fix.zip gồm 3 file: History.cs, AlarmStore.cs, ScadaEngine.cs. Chép đè vào project rồi build lại. Mình chưa biên dịch hay thử reset máy, nên cần bạn kiểm tra.Nguyên nhân mình đoán: history và alarm ghi SQLite kiểu WAL với synchronous=NORMAL, nên dữ liệu nằm trong file WAL mà chưa được ép xuống đĩa cho tới khi SQLite checkpoint. Checkpoint tự động chỉ chạy khi WAL đầy khoảng 4 MB, và dự án của bạn ít dữ liệu nên có thể 8 tiếng chưa đầy. Máy reset đột ngột thì phần đó mất, khớp với khoảng trống từ 07:00 đến 15:00 trong báo cáo.
Các thay đổi:
History.csvàAlarmStore.cs: đổi sangsynchronous=FULLvàwal_autocheckpoint=200, để mỗi lần ghi lô (vài giây một lần) được ép xuống đĩa ngay.History.cs: thêmCheckpoint(), tự đẩy WAL vào file chính khoảng 20 giây một lần, không chặn việc ghi hay đọc.AlarmStore.cs: tự checkpoint khoảng 20 giây một lần ở cuốiFlush().ScadaEngine.cs: khi chương trình dừng bình thường thì flush rồi checkpoint lần cuối.
Lưu ý:
- Dữ liệu 07:00–15:00 đã mất trước khi sửa thì không lấy lại được. Bản sửa chỉ bảo vệ từ nay về sau.
- Để kiểm tra: chạy bản mới vài phút rồi tắt tiến trình đột ngột (kill, không qua Shutdown). Dữ liệu những phút đó vẫn phải còn trong báo cáo.
- Nếu sau bản sửa vẫn mất dữ liệu, gửi mình thư mục
history(history.db,history.db-wal) và log lúc khởi động lại để mình kiểm tra tiếp.Ưu điểm so với code cũ- Không mất dữ liệu khi reset đột ngột: code cũ có thể mất vài giờ dữ liệu chưa được ép xuống đĩa. Code mới chỉ có thể mất khoảng vài giây cuối, tức một chu kỳ Flush (
FlushSec, 1–60 giây). - Áp dụng cho cả alarm:
AlarmStorecó cùng lỗi nên giờ cũng được bảo vệ, cả lịch sử alarm lẫn trạng thái Ack. - File
.db-walkhông phình to: trước đây WAL có thể tích tụ nhiều giờ, còn bây giờ được dọn đều đặn. - Tắt đúng cách thì sạch hơn: lúc dừng chương trình có checkpoint lần cuối, nên file
history.dbđầy đủ ngay, copy hay backup không phải kèm file WAL.
Nhược điểm
- Tốn đĩa hơn một chút:
synchronous=FULLép đĩa (fsync) mỗi lần commit. Mỗi lô Flush là một commit (vài giây một lần), nên 2693 tag cũng không đáng kể. Chỉ đáng lưu ý nếu ổ đĩa rất chậm, như thẻ SD hoặc ổ mạng; bạn có thể tăngFlushSecđể giảm số lần ghi. - Ghi thêm dữ liệu so với trước: mỗi 20 giây có checkpoint, chép dữ liệu từ WAL sang file chính. Mình đặt chế độ PASSIVE nên không chặn đọc/ghi, chỉ thêm một chút I/O nền.
- Mỗi lần Flush chậm hơn chút: vì chờ đĩa xác nhận ghi xong. Việc này chạy ở luồng engine, nên nếu đĩa bị nghẽn thì vòng lặp đọc tag có thể trễ vài chục mili-giây.
- Vẫn không bảo vệ tuyệt đối: dữ liệu còn trong hàng chờ bộ nhớ (chưa Flush) vẫn mất nếu máy tắt ngay lúc đó, thường chỉ vài giây. Muốn giảm thêm thì giảm
FlushSec, đổi lại ghi đĩa thường xuyên hơn.
Mình chưa đo hiệu năng trên máy của bạn, nên các nhận xét về tốc độ là ước lượng theo cách SQLite hoạt động. Nên theo dõi thời gian vòng lặp engine vài giờ sau khi chạy bản mới.
- Không mất dữ liệu khi reset đột ngột: code cũ có thể mất vài giờ dữ liệu chưa được ép xuống đĩa. Code mới chỉ có thể mất khoảng vài giây cuối, tức một chu kỳ Flush (

