Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Shared>) -> Response {
Expand Down
25 changes: 18 additions & 7 deletions src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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<?2"))?
.execute(params![id, before.min(first + 86_400)])?;
.execute(params![id, if guarded { before.min(first + 86_400) } else { before }])?;
drop(conn);
let_waiters_in();
}
Expand Down
17 changes: 11 additions & 6 deletions web-admin/src/components/Admin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2883,11 +2883,13 @@ type DbInfo = {
rows: Record<string, number>
}

// 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() {
Expand Down Expand Up @@ -2941,8 +2943,8 @@ function Data() {
}

if (!info) return null
const stat = (label: string, value: string) => (
<div key={label}>
const stat = (label: string, value: string, className = "") => (
<div key={label} className={className}>
<div className="text-xs text-muted-foreground">{label}</div>
<div className="tnum mt-0.5 text-sm">{value}</div>
</div>
Expand All @@ -2952,10 +2954,13 @@ function Data() {
<div className="space-y-4">
<Card className="gap-4 p-5">
<h3 className="text-sm font-medium">数据库</h3>
<div className="grid grid-cols-2 gap-4 sm:grid-cols-4">
{/* 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. */}
<div className="grid grid-cols-2 gap-4 sm:grid-cols-5">
{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 没在跑。 */}
Expand Down
Loading