Stalewatch › demo
See it work
Two weeks of write activity goes in; Stalewatch learns each table's normal hours and shows the alerts it would have sent, next to what a fixed threshold would have sent.
Input
3 tables · 14 days of write activity
See the input
public.orders (1 of 3 tables)
hour 0 6 12 18
Wed 10-21 ........############....
Thu 10-22 ........############....
Fri 10-23 ........############....
Sat 10-24 ........................
Sun 10-25 ........................
Mon 10-26 ........############....
Tue 10-27 ........############....
Wed 10-28 ........############....
Thu 10-29 ........############....
Fri 10-30 ........############....
Sat 10-31 ........................
Sun 11-01 ........................
Mon 11-02 ........############....
Tue 11-03 ........######..### got rows that hour, . got none.
Show the raw query output
table_name | day | hours
---------------+------------+--------------------------
public.orders | 2026-10-21 | ........############....
public.orders | 2026-10-22 | ........############....
public.orders | 2026-10-23 | ........############....
public.orders | 2026-10-24 | ........................
public.orders | 2026-10-25 | ........................
public.orders | 2026-10-26 | ........############....
public.orders | 2026-10-27 | ........############....
public.orders | 2026-10-28 | ........############....
public.orders | 2026-10-29 | ........############....
public.orders | 2026-10-30 | ........############....
public.orders | 2026-10-31 | ........................
public.orders | 2026-11-01 | ........................
public.orders | 2026-11-02 | ........############....
public.orders | 2026-11-03 | ........######..##
(14 rows)
table_name | day | hours
---------------+------------+--------------------------
public.events | 2026-10-21 | ########################
public.events | 2026-10-22 | ########################
public.events | 2026-10-23 | ########################
public.events | 2026-10-24 | ########################
public.events | 2026-10-25 | ########################
public.events | 2026-10-26 | ########################
public.events | 2026-10-27 | ########################
public.events | 2026-10-28 | ########################
public.events | 2026-10-29 | ########################
public.events | 2026-10-30 | ########################
public.events | 2026-10-31 | ########################
public.events | 2026-11-01 | ########################
public.events | 2026-11-02 | ########################
public.events | 2026-11-03 | ##################
(14 rows)
table_name | day | hours
---------------------+------------+--------------------------
public.daily_rollup | 2026-10-21 | ..#.....................
public.daily_rollup | 2026-10-22 | ..#.....................
public.daily_rollup | 2026-10-23 | ..#.....................
public.daily_rollup | 2026-10-24 | ..#.....................
public.daily_rollup | 2026-10-25 | ..#.....................
public.daily_rollup | 2026-10-26 | ..#.....................
public.daily_rollup | 2026-10-27 | ..#.....................
public.daily_rollup | 2026-10-28 | ..#.....................
public.daily_rollup | 2026-10-29 | ..#.....................
public.daily_rollup | 2026-10-30 | ..#.....................
public.daily_rollup | 2026-10-31 | ..#.....................
public.daily_rollup | 2026-11-01 | ..#.....................
public.daily_rollup | 2026-11-02 | ........................
public.daily_rollup | 2026-11-03 | ..#...............
(14 rows)Output
2 alerts instead of 23
Across 3 tables and the last 14 days: a fixed 60-minute threshold would have sent 23 alerts, 21 of them at hours these tables are normally quiet. Learned rhythm: 2 alerts.
1 alert
public.ordersWent quiet Tue 2026-11-03 14:00–16:00. Normally gets rows: Weekdays 08:00–20:00; weekends no regular hours.
Fixed 60-minute threshold: 10 alerts, 9 at normally quiet hours. Learned rhythm: 1 alert.
Rhythm and quiet hours
Normally gets rows Got rows Quiet in a normally busy hour
00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Weekdays busy busy busy busy busy busy busy busy busy busy busy busy Weekends Tue 11-03 rows rows rows rows rows rows quiet quiet rows rows 1 alert
public.daily_rollupWent quiet Mon 2026-11-02 02:00–03:00. Normally gets rows: Weekdays 02:00–03:00; weekends 02:00–03:00.
Fixed 60-minute threshold: 13 alerts, 12 at normally quiet hours. Learned rhythm: 1 alert.
Rhythm and quiet hours
Normally gets rows Got rows Quiet in a normally busy hour
00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Weekdays busy Weekends busy Mon 11-02 quiet No alerts
public.eventsNormally gets rows: Weekdays every hour; weekends every hour.
Fixed 60-minute threshold: 0 alerts, 0 at normally quiet hours. Learned rhythm: 0 alerts.
Rhythm and quiet hours
Normally gets rows Got rows Quiet in a normally busy hour
00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Weekdays busy busy busy busy busy busy busy busy busy busy busy busy busy busy busy busy busy busy busy busy busy busy busy busy Weekends busy busy busy busy busy busy busy busy busy busy busy busy busy busy busy busy busy busy busy busy busy busy busy busy
The Slack message for the latest alert:
public.orders got no new rows from 14:00 to 16:00 on Tue 2026-11-03. It normally gets rows on weekdays 08:00–20:00.
Raw JSON
{
"text": "Stalewatch: public.orders went quiet",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*public.orders* got no new rows from 14:00 to 16:00 on Tue 2026-11-03. It normally gets rows on weekdays 08:00–20:00."
}
}
]
}What Stalewatch did
Stalewatch: Learned each table's normal hours from 14 days of timestamps
By hand: pick a threshold per table, and re-tune it when the schedule changes
Stalewatch: Replayed the 14 days to separate real outages from normal quiet
By hand: a fixed threshold alerts every night and weekend, so people mute it
Stalewatch: Wrote the Slack message with the table, the gap and its normal hours
By hand: write and maintain the alert yourself
Want this watching your tables?
The first 100 on the waitlist get $15/mo, locked for as long as you stay subscribed. Then it's $29/mo.
We store your email, a source tag, your consent and the signup time in Cloudflare D1, to email you about Stalewatch. Privacy
Try it on your own tables
Run one query per table on your own Postgres and paste the output. You do not give us a database credential, and nothing you paste is stored.
Show the query to run
-- Run once per table: replace public.orders and created_at with yours.
-- Hours are read as printed, so set your time zone first.
SET TIME ZONE 'UTC';
WITH active AS (
SELECT DISTINCT date_trunc('hour', created_at) AS hour
FROM public.orders
WHERE created_at >= date_trunc('day', now()) - interval '13 days'
)
SELECT 'public.orders' AS table_name,
g::date AS day,
string_agg(CASE WHEN a.hour IS NULL THEN '.' ELSE '#' END, '' ORDER BY g) AS hours
FROM generate_series(date_trunc('day', now()) - interval '13 days',
date_trunc('hour', now()), interval '1 hour') AS g
LEFT JOIN active a ON a.hour = g
GROUP BY 1, 2
ORDER BY 2;Limits. Nothing is sent to Slack. Rhythm is learned per hour, from at most the last 14 days you paste.