Skip to content

Commit ef007ea

Browse files
committed
1.4.0 Drop rollup_state
1 parent 36257b4 commit ef007ea

4 files changed

Lines changed: 51 additions & 63 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
# WIP
1+
# 1.4.0 - Jul 22, 2026
22

33
- Cache DB connection between dashboard accesses
44
- More compact `rollup_daily` keeping top 20 per day instead of `daily_counts`
5+
- Drop `rollup_state`
56

67
# 1.3.0 - Jul 20, 2026
78

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ This library was born out of a desire to have a basic understanding of my web pr
44

55
`clj-simple-stats`, as the name suggests, is trivial to set up:
66

7-
1. Add `io.github.tonsky/clj-simple-stats {:mvn/version "1.2.0"}` to `deps.edn`.
7+
1. Add `io.github.tonsky/clj-simple-stats {:mvn/version "1.4.0"}` to `deps.edn`.
88
2. Add `clj-simple-stats.core/wrap-stats` anywhere in your middleware stack.
99

1010
That’s it! There’s no step 3.

‎src/clj_simple_stats/core.clj‎

Lines changed: 42 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@
117117
(.execute stmt
118118
"CREATE TABLE IF NOT EXISTS version (version INTEGER)")
119119
(.execute stmt
120-
"INSERT INTO version VALUES (4)")
120+
"INSERT INTO version VALUES (5)")
121121
(.execute stmt
122122
"CREATE TYPE IF NOT EXISTS agent_type_t AS ENUM ('feed', 'bot', 'browser')")
123123
(.execute stmt
@@ -139,7 +139,7 @@
139139
(.execute stmt
140140
"CREATE TYPE IF NOT EXISTS dim_t AS ENUM ('feed', 'bot', 'browser', 'path', 'query', 'ref_domain')")
141141
(.execute stmt
142-
;; Per-day aggregates of stats for all days <= rollup_state.last_date.
142+
;; Per-day aggregates of stats for all completed days.
143143
;; For dims feed/bot/browser, value is agent (possibly NULL).
144144
;; For dims path/query/ref_domain, value is that column.
145145
;; Only the top 20 (rollup-depth) values per (date, dim) are kept;
@@ -150,11 +150,7 @@
150150
dim dim_t,
151151
value VARCHAR,
152152
cnt BIGINT
153-
)")
154-
(.execute stmt
155-
"CREATE TABLE IF NOT EXISTS rollup_state (last_date DATE)")
156-
(.execute stmt
157-
"INSERT INTO rollup_state VALUES (DATE '1970-01-01')")))
153+
)")))
158154

159155
(defn db-version ^long [^DuckDBConnection conn]
160156
(or
@@ -232,29 +228,27 @@
232228
(log "Migrating" db-path "to version 4")
233229
(with-open [conn (connect db-path)
234230
stmt (.createStatement conn)]
235-
(.execute stmt "BEGIN TRANSACTION")
231+
(.execute stmt "DROP TABLE IF EXISTS daily_counts")
236232
(.execute stmt
237-
(str
238-
"CREATE TABLE rollup_daily AS
239-
WITH ranked AS (
240-
SELECT date, dim, value, cnt,
241-
ROW_NUMBER() OVER (PARTITION BY date, dim ORDER BY cnt DESC, value) AS rn
242-
FROM daily_counts
243-
WHERE value IS NOT NULL
244-
)
245-
SELECT date, dim, value, cnt FROM ranked WHERE rn <= " rollup-depth "
246-
UNION ALL
247-
SELECT date, dim, '" rollup-sentinel "', SUM(cnt)::BIGINT
248-
FROM ranked WHERE rn > " rollup-depth " GROUP BY date, dim
249-
UNION ALL
250-
SELECT date, dim, value, cnt FROM daily_counts WHERE value IS NULL"))
251-
(.execute stmt "DROP TABLE daily_counts")
252-
(.execute stmt "UPDATE version SET version = 4")
253-
(.execute stmt "COMMIT"))
233+
"CREATE TABLE IF NOT EXISTS rollup_daily (
234+
date DATE,
235+
dim dim_t,
236+
value VARCHAR,
237+
cnt BIGINT
238+
)")
239+
(.execute stmt "UPDATE version SET version = 4"))
254240
(log "Migration to version 4 complete"))
255241

242+
(defn migrate-4->5! [^String db-path]
243+
(log "Migrating" db-path "to version 5")
244+
(with-open [conn (connect db-path)
245+
stmt (.createStatement conn)]
246+
(.execute stmt "DROP TABLE IF EXISTS rollup_state")
247+
(.execute stmt "UPDATE version SET version = 5"))
248+
(log "Migration to version 5 complete"))
249+
256250
(defn- rollup-day!
257-
"Aggregates one day of stats into rollup_daily and advances rollup_state to it"
251+
"Aggregates one day of stats into rollup_daily"
258252
[^DuckDBConnection conn ^LocalDate date]
259253
(log-verbose "Rolling up" (str date))
260254
(.setAutoCommit conn false)
@@ -306,9 +300,6 @@
306300
FROM ranked WHERE rn > " rollup-depth " GROUP BY date"))]
307301
(.setObject stmt 1 date)
308302
(.execute stmt)))
309-
(with-open [stmt (.prepareStatement conn "UPDATE rollup_state SET last_date = ?")]
310-
(.setObject stmt 1 date)
311-
(.execute stmt))
312303
(.commit conn)
313304
(catch Throwable t
314305
(.rollback conn)
@@ -317,51 +308,43 @@
317308
(.setAutoCommit conn true))))
318309

319310
(def ^:private *rollup-dates
320-
"db-path -> in-memory mirror of rollup_state.last_date, so worker ticks
321-
with a current watermark don't open the database at all. Read from the
322-
database once after startup, then maintained in memory"
311+
"db-path -> last rolluped up day"
323312
(atom {}))
324313

325314
(defn rollup!
326-
"Rolls up every completed UTC day after rollup_state.last_date into rollup_daily,
327-
day by day, then advances the watermark to yesterday"
315+
"Rolls up every completed UTC day after the watermark into rollup_daily,
316+
day by day. The persistent watermark is MAX(date) in rollup_daily"
328317
[db-path]
329318
(let [yesterday (.minusDays (LocalDate/now UTC) 1)
330319
last-date (get @*rollup-dates db-path)]
331320
(when (or (nil? last-date) (LocalDate/.isBefore last-date yesterday))
332321
(with-open [conn (acquire-conn db-path)]
333322
(let [last-date (or last-date
334323
(with-open [stmt (.createStatement conn)
335-
rs (.executeQuery stmt "SELECT last_date FROM rollup_state")]
324+
rs (.executeQuery stmt "SELECT COALESCE(MAX(date), DATE '1970-01-01') FROM rollup_daily")]
336325
(when (.next rs)
337326
^LocalDate (.getObject rs 1))))]
338-
(when last-date
339-
(when (LocalDate/.isBefore last-date yesterday)
340-
(let [dates (with-open [stmt (doto (.prepareStatement conn
341-
"SELECT DISTINCT date FROM stats WHERE date > ? AND date <= ? ORDER BY date")
342-
(.setObject 1 last-date)
343-
(.setObject 2 yesterday))
344-
rs (.executeQuery stmt)]
345-
(loop [acc []]
346-
(if (.next rs)
347-
(recur (conj acc (.getObject rs 1)))
348-
acc)))]
349-
(doseq [date dates]
350-
(rollup-day! conn date))
351-
;; advance watermark over trailing empty days too
352-
(with-open [stmt (.prepareStatement conn "UPDATE rollup_state SET last_date = ?")]
353-
(.setObject stmt 1 yesterday)
354-
(.execute stmt))
355-
(log-verbose "Rolled up" (count dates) "day(s), watermark at" (str yesterday))))
356-
(swap! *rollup-dates assoc db-path yesterday)))))))
327+
(when (LocalDate/.isBefore last-date yesterday)
328+
(let [dates (with-open [stmt (doto (.prepareStatement conn
329+
"SELECT DISTINCT date FROM stats WHERE date > ? AND date <= ? ORDER BY date")
330+
(.setObject 1 last-date)
331+
(.setObject 2 yesterday))
332+
rs (.executeQuery stmt)]
333+
(loop [acc []]
334+
(if (.next rs)
335+
(recur (conj acc (.getObject rs 1)))
336+
acc)))]
337+
(doseq [date dates]
338+
(rollup-day! conn date))
339+
(log-verbose "Rolled up" (count dates) "day(s), watermark at" (str yesterday))))
340+
(swap! *rollup-dates assoc db-path yesterday))))))
357341

358342
(defn rebuild-rollup!
359343
"Recompute rollup_daily from scratch"
360344
[db-path]
361345
(with-conn [conn db-path]
362346
(with-open [stmt (.createStatement conn)]
363-
(.execute stmt "DELETE FROM rollup_daily")
364-
(.execute stmt "UPDATE rollup_state SET last_date = DATE '1970-01-01'")))
347+
(.execute stmt "DELETE FROM rollup_daily")))
365348
(swap! *rollup-dates dissoc db-path)
366349
(rollup! db-path))
367350

@@ -376,7 +359,9 @@
376359
(when (<= v 2)
377360
(migrate-2->3! db-path))
378361
(when (<= v 3)
379-
(migrate-3->4! db-path)))))
362+
(migrate-3->4! db-path))
363+
(when (<= v 4)
364+
(migrate-4->5! db-path)))))
380365

381366
(def ^:private *worker-pool
382367
(atom nil))

‎src/clj_simple_stats/dashboard.clj‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,9 @@
163163
#_(top-10 conn "query" "path = '/search' AND type = 'browser'")))
164164

165165
;; Rollup variants: used when no filters except from/to are set. Closed days come
166-
;; from rollup_daily, days after rollup_state.last_date (i.e. today) from stats.
166+
;; from rollup_daily, days after its MAX(date) watermark (i.e. today) from stats.
167+
;; The watermark is derived inside the query so both parts see one consistent
168+
;; snapshot even if a rollup commits mid-render.
167169

168170
(defn visits-by-type+date-rollup [conn from to]
169171
(->
@@ -176,7 +178,7 @@
176178
SELECT type::VARCHAR, date, SUM(mult)
177179
FROM (SELECT type, date, uniq, MAX(mult) AS mult
178180
FROM stats
179-
WHERE date > (SELECT last_date FROM rollup_state) AND date >= ? AND date <= ?
181+
WHERE date > (SELECT COALESCE(MAX(date), DATE '1970-01-01') FROM rollup_daily) AND date >= ? AND date <= ?
180182
GROUP BY type, date, uniq)
181183
GROUP BY type, date"
182184
[from to from to]
@@ -199,7 +201,7 @@
199201
SELECT " what " AS value, COUNT(*) AS cnt
200202
FROM stats
201203
WHERE type = 'browser' AND " what " IS NOT NULL
202-
AND date > (SELECT last_date FROM rollup_state) AND date >= ? AND date <= ?
204+
AND date > (SELECT COALESCE(MAX(date), DATE '1970-01-01') FROM rollup_daily) AND date >= ? AND date <= ?
203205
GROUP BY " what "
204206
),
205207
top_values AS (
@@ -243,7 +245,7 @@
243245
SELECT ANY_VALUE(agent) AS value, MAX(mult) AS cnt
244246
FROM stats
245247
WHERE type = '" type "'
246-
AND date > (SELECT last_date FROM rollup_state) AND date >= ? AND date <= ?
248+
AND date > (SELECT COALESCE(MAX(date), DATE '1970-01-01') FROM rollup_daily) AND date >= ? AND date <= ?
247249
GROUP BY date, uniq
248250
),
249251
top_values AS (

0 commit comments

Comments
 (0)