1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# MeterStore — deployment configuration.
#
# `meterstore check` validates this file without connecting to anything, and it
# runs the same cross-field checks the Rust builder does. There is no setting
# reachable from one and not the other.
#
# A `${DATABASE_URL}`-style placeholder in any value is interpolated from the
# environment. A missing variable is an error, not an empty string: a connection
# URL that silently became `postgresql://@/` would fail somewhere far from the
# typo. Comments are left alone, which is why this one can say so.
# --- The hot tier: PostgreSQL holds the recent interval window. ---
[]
= "${DATABASE_URL}"
= 16
# How long a DDL statement waits for a lock before giving up.
#
# PostgreSQL grants locks in arrival order, so a statement waiting for an
# ACCESS EXCLUSIVE lock blocks every reader and writer that arrives behind it.
# Archival detaches a partition on its own schedule, and a detach that queued
# would take ingest down for as long as whatever it waits on runs. With this it
# gives up having changed nothing, and the cycle is reported as deferred.
#
# Raise it on a deployment that reports out of the same tables it writes; every
# second added is a second the whole table can stall for. `0s` disables it,
# which is PostgreSQL's own default and this crate's advice against.
= "3s"
# --- The cold tier: Apache Iceberg holds the history. ---
[]
# "rest" for Polaris, Lakekeeper, Nessie or Gravitino — what every engine
# already speaks, so an external reader needs nothing extra. "sql" for a
# PostgreSQL-backed catalogue on the same database as the hot tier, which is one
# fewer service to run and needs the JDBC catalogue implementation on the far
# side.
= "sql"
= "${DATABASE_URL}"
# file:// and memory:// are always available. s3://, gs:// and abfss:// need the
# matching `object-store-*` feature, and a scheme whose feature was not compiled
# in is a configuration error rather than a surprise on the first commit.
= "file:///var/lib/meterstore/warehouse"
= "metering"
# Target size for a data file. Larger files mean less manifest to plan against
# and better compression; smaller ones mean finer pruning.
= 536870912
= 4
# --- One entry per managed table. ---
[[]]
# The physical table holds *every version* of every reading — the audit trail.
# The `_versions` suffix is load-bearing: queries read `readings`, a view that
# keeps only the value currently in force, and summing the physical table
# double-counts every corrected interval.
= "readings_versions"
# "interval" for a Lastgang — energy over [from, to). "point" for a
# Zählerstandsgang — a cumulative register value at an instant.
#
# Never the same table: `value` means two different things, and summing the two
# together gives a number with no meaning that looks exactly like a consumption
# total.
= "interval"
[]
# How far ahead partitions are pre-created. Running out makes inserts fail
# outright, so `partitions_ahead` in `meterstore status` is the number that
# predicts a failure rather than describing one.
= "7d"
[]
# How far behind wall clock archival stays, so a late correction still finds its
# interval in the hot tier where it can be superseded cheaply. It must cover at
# least one `archival_step`, or a window can close while corrections for it are
# still arriving — and they would land below the watermark, where no query looks.
= "7d"
# One window per commit — and the hot table's partition granularity, which is
# the same number. The purge of an archived window is DROP TABLE, not DELETE,
# and that only holds when a window is exactly one partition.
#
# It cannot be changed once a table has archived: the watermark sits on the old
# grid, and a window off that grid names a partition relation nothing creates.
= "1d"
[]
# A snapshot is what makes a past settlement reproducible, so this is a
# compliance decision rather than a disk-space one — which is why expiry is
# opt-in (`meterstore maintain --expire-snapshots`) rather than automatic.
= "10y"
# --- Columns beyond the core schema. ---
#
# `identity = true` puts the column in the merge key, so two rows differing in
# it are different readings. A tenant discriminator declared as an attribute
# instead would let one tenant's correction supersede another's reading.
#
# [[tables.extra_columns]]
# name = "tenant"
# type = "string"
# identity = true
#
# `values` declares a closed vocabulary and renders a `CHECK ... IN (...)`;
# `check` declares an identifier scheme, and the write path parses every value
# with it. A Bilanzkreis is an ENTSO-E EIC with a check character, not sixteen
# arbitrary characters — so a transposition is caught while the delivery that
# carried it is still in hand. The two are mutually exclusive.
#
# [[tables.extra_columns]]
# name = "bilanzkreis"
# type = "string"
# identity = false
# check = "EIC"