diff --git a/src/api.rs b/src/api.rs index 9949dc27..d88606b2 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1327,8 +1327,8 @@ fn scratch_path(app: &App, kind: &str) -> String { /// The data page's figures. /// /// Off the runtime, like the three routes below: `stats` counts every row of -/// `metric` and `ping_record` -- both WITHOUT ROWID, so each count is a full -/// index scan -- holding the connection the agents report through throughout. At +/// both tiers of history -- all WITHOUT ROWID, so each count is a full index +/// scan -- holding the connection the agents report through throughout. At /// 2.2M rows that is 127 ms during which the public page and every agent report /// also wait, growing with `retention_days`. pub async fn db_stats(_: Admin, State(app): State) -> Response { diff --git a/src/db.rs b/src/db.rs index 3c899e63..86422398 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1520,7 +1520,11 @@ impl Db { /// `minutes` is how many minute rows the bucket holds, against the /// `step / 60` it spans: a node offline for part of a bucket has its means /// taken over the minutes it reported, and a caller integrating the rates - /// or showing availability needs the difference. + /// or showing availability needs the difference. That holds while the + /// agent reports at least once a minute. A row is written only in a minute + /// a report arrives, so past a 60-second `--interval` each row covers one + /// interval and `minutes` falls in proportion: a fifth of the full count at + /// 300 seconds. /// /// An hourly window reads the folded hours before the watermark and the /// minute rows after it, each hour weighted by the minutes it holds, so a @@ -1691,11 +1695,18 @@ impl Db { /// `delete_node` clears the two probe tables, and `insert_pings` writes only /// for probes assigned to an existing node. /// - /// At most a day of one node's rows per statement, with the lock the agents - /// write through released in between. An hourly pass deletes an hour and is - /// one statement per table; the first pass after an upgrade deletes - /// everything past the week, which in one statement would hold the lock for - /// 37.8 s at 90 days of 100 nodes, and a node at a time 0.9 s each. + /// Minute rows are deleted at most one node's day per statement, with the + /// lock the agents write through released in between. An hourly pass + /// deletes an hour and is one statement per table; the first pass after an + /// upgrade deletes everything past the week, which in one statement would + /// hold the lock for 37.8 s at 90 days of 100 nodes, and a node at a time + /// 0.9 s each. + /// + /// Hourly rows are deleted a node at a time, one statement per table: + /// lowering the window from a year to a month deletes 8,040 and 32,160 rows + /// of a node with four probes, 24 ms each. A day per statement would spend + /// 0.96 s per node on the lookups and pauses in between, 4.8 minutes at 300 + /// nodes ahead of the `VACUUM` that usually follows. /// /// The watermark is read with each statement: a restore can replace it, /// and the minute rows it guards, between two of them. @@ -1729,7 +1740,7 @@ impl Db { let Some(first) = first.filter(|&ts| ts < before) else { break }; pruned += conn .prepare_cached(&format!("DELETE FROM {table} WHERE node_id=?1 AND ts } -// The only two tables whose row count indicates anything about size. Every other -// holds one row per node or per key. +// The only tables whose row count indicates anything about size, each kind of +// history in both tiers. Every other holds one row per node or per key. const DB_ROWS: [string, string][] = [ ["metric", "历史明细"], + ["metric_hour", "历史小时汇总"], ["ping_record", "延迟记录"], + ["ping_hour", "延迟小时汇总"], ] function Data() { @@ -2941,8 +2943,8 @@ function Data() { } if (!info) return null - const stat = (label: string, value: string) => ( -
+ const stat = (label: string, value: string, className = "") => ( +
{label}
{value}
@@ -2952,10 +2954,13 @@ function Data() {

数据库

-
+ {/* Five columns: the file and the window on one row, the four row counts + on the next. On two columns the free space takes a row of its own, so + the window and each kind's two tiers still pair up. */} +
{stat("文件大小", bytes(info.size))} {stat("预写日志", bytes(info.wal))} - {stat("可回收空间", bytes(info.free))} + {stat("可回收空间", bytes(info.free), "col-span-2 sm:col-span-1")} {stat("保留天数", `${info.retention} 天`)} {/* 和保留天数并排:跨度小于保留期是还没攒够,大于保留期就是每小时 那次 prune 没在跑。 */}