the files
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
using LiteDB;
|
||||
|
||||
namespace Campus_live_rewrite.api.dbs
|
||||
{
|
||||
// LiteDB v5 writes through a write-ahead log (`<name>-log.db`) and only merges
|
||||
// it back into the main data file on Checkpoint() - which happens automatically
|
||||
// ONLY on LiteDatabase.Dispose(), or once the log passes CheckpointSize
|
||||
// (default 1000 pages / ~8MB).
|
||||
//
|
||||
// Every LiteDatabase in this project is a `static` field that is never disposed,
|
||||
// and `systemctl stop campus` kills the process without unwinding statics. So no
|
||||
// checkpoint ever ran, and the logs sat just under the 1000-page auto-threshold
|
||||
// (573-825 pages) and never tripped it either.
|
||||
//
|
||||
// The result, as of 2026-08-21: Heartbeats.db and Settings.db were 8192 bytes -
|
||||
// a bare header page - while Heartbeats-log.db was 5.6MB and Settings-log.db
|
||||
// 6.4MB. 100% of that data lived in the WAL and none of it in the data file.
|
||||
//
|
||||
// That matters beyond tidiness because install.sh deliberately preserves
|
||||
// Data/DBs/* across deploys, so those logs were copied forward verbatim into
|
||||
// every new build. Deploying a known-good server could not fix a data-level
|
||||
// problem, because the data came along with it. That is why swapping in a
|
||||
// working server still broke.
|
||||
public static class DbMaintenance
|
||||
{
|
||||
// Checkpoint every store. Each is wrapped separately so one bad file can't
|
||||
// stop the rest from flushing.
|
||||
private static Timer? _autoCheckpoint;
|
||||
|
||||
// Checkpoint on a schedule, not just at shutdown.
|
||||
//
|
||||
// Writes land in <name>-log.db and are only merged into <name>.db by a
|
||||
// checkpoint. Until then the data is recoverable ONLY by replaying that log.
|
||||
// Measured 2026-08-23: a setting written and read back survived a restart, but
|
||||
// Settings.db was still 8,192 bytes (untouched since 12 Aug) while
|
||||
// Settings-log.db had grown to 7.37 MB - i.e. every setting the server has ever
|
||||
// stored lived in the log alone.
|
||||
//
|
||||
// That works until the log is lost or truncated, at which point everything
|
||||
// written since the last checkpoint disappears at once. Shutdown-only
|
||||
// checkpointing means a hard kill (crash, SIGKILL, power loss) never merges.
|
||||
//
|
||||
// A periodic checkpoint bounds that exposure to `minutes`. It only flushes data
|
||||
// already committed - it cannot change any response or alter stored values.
|
||||
public static void StartAutoCheckpoint(int minutes = 5)
|
||||
{
|
||||
if (_autoCheckpoint != null) return;
|
||||
var period = TimeSpan.FromMinutes(minutes);
|
||||
_autoCheckpoint = new Timer(_ =>
|
||||
{
|
||||
try { CheckpointAll(quiet: true); }
|
||||
catch (Exception ex) { Console.WriteLine($"[DbMaintenance] auto-checkpoint failed: {ex.Message}"); }
|
||||
}, null, period, period);
|
||||
Console.WriteLine($"[DbMaintenance] auto-checkpoint every {minutes} min");
|
||||
}
|
||||
|
||||
public static void StopAutoCheckpoint()
|
||||
{
|
||||
_autoCheckpoint?.Dispose();
|
||||
_autoCheckpoint = null;
|
||||
}
|
||||
|
||||
public static void CheckpointAll(bool quiet = false)
|
||||
{
|
||||
Flush("Players", () => PlayerDB.PlayerDBFile.Checkpoint(), quiet);
|
||||
Flush("Rooms", () => RoomDB.rooms_db.Checkpoint(), quiet);
|
||||
Flush("Chat", () => ChatDB.ChatDBFile.Checkpoint(), quiet);
|
||||
Flush("Events", () => EventDB.EventDBFile.Checkpoint(), quiet);
|
||||
Flush("PlayersFriends", () => FriendsDB.PlayerFriendDBFile.Checkpoint(), quiet);
|
||||
Flush("Heartbeats", HeartbeatDB.Checkpoint, quiet);
|
||||
Flush("Settings", SettingsDB.Checkpoint, quiet);
|
||||
Flush("Leaderboard", LeaderboardDB.Checkpoint, quiet);
|
||||
Flush("Support", SupportDB.Checkpoint, quiet);
|
||||
Flush("Clubs", ClubDB.Checkpoint, quiet);
|
||||
Flush("Inventions", InventionDB.Checkpoint, quiet);
|
||||
}
|
||||
|
||||
private static void Flush(string name, Action action, bool quiet = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
if (!quiet) Console.WriteLine($"[DbMaintenance] checkpointed {name}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[DbMaintenance] FAILED to checkpoint {name}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// Print the data-file / log-file split at startup. A main file stuck at 8192
|
||||
// bytes with a multi-megabyte log means the checkpoint path is broken again.
|
||||
public static void ReportWalState()
|
||||
{
|
||||
string dir = Path.Combine(Environment.CurrentDirectory, "Data", "DBs");
|
||||
if (!Directory.Exists(dir))
|
||||
{
|
||||
Console.WriteLine($"[DbMaintenance] no DB directory at {dir}");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var db in Directory.GetFiles(dir, "*.db").OrderBy(f => f))
|
||||
{
|
||||
if (db.EndsWith("-log.db", StringComparison.OrdinalIgnoreCase)) continue;
|
||||
|
||||
var main = new FileInfo(db);
|
||||
var log = new FileInfo(db[..^3] + "-log.db");
|
||||
long logLen = log.Exists ? log.Length : 0;
|
||||
|
||||
string flag = (main.Length <= 8192 && logLen > 1024 * 1024)
|
||||
? " <-- ALL DATA IS IN THE LOG, main file is empty"
|
||||
: "";
|
||||
|
||||
Console.WriteLine(
|
||||
$"[DbMaintenance] {main.Name,-24} data={main.Length,10:n0} log={logLen,12:n0}{flag}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user