Build an Automated Data Monitoring & Alert System with DuckDB + Cron
Do you ever face this pain point—your business data grows every day, but you have no idea when something goes wrong. By the time your customers complain, the damage is already done.
Today I’ll show you how to build a fully automated Data Health Monitoring System using DuckDB. It runs queries on a schedule, detects anomalies, and sends instant Telegram alerts. No complex ETL, no expensive monitoring tools—just a Python script + DuckDB.

1. Why Do You Need Data Monitoring?
Let’s clarify the value first. Why build a monitoring system?
- E-commerce: GMV drops 30% overnight—you need to catch it before customers place orders, not discover it the next day from reports
- Finance: A stock breaks through a key level with high volume—push the signal to subscribers instantly
- SaaS: User registrations drop for 3 consecutive days—something is broken in your product
- Content: A video’s views spike unexpectedly—catch the trend while it’s hot
The common thread: fast detection, accurate alerts, minimal cost.
DuckDB’s role in this system: it’s not a database—it’s a query engine. It reads directly from CSV/Parquet/API files, runs one SQL query to find anomalies, and hands results to Telegram for delivery.
2. System Architecture
CSV/Parquet Data Sources
↓
DuckDB (Query Engine)
↓
Python Anomaly Detection
↓
Telegram Bot API
↓
Real-time Mobile Notifications
Everything runs in Python with zero external service dependencies. Data is stored in a single .duckdb file with incremental appends.
3. Core Monitoring Logic
Let’s say you’re an e-commerce seller tracking three core metrics:
- Daily order volume anomalies
- Sustained decline in average order value
- Sudden spikes in refund rate
Your data source is daily order CSV exports. Here’s the DuckDB monitoring query:
import duckdb
import json
from datetime import datetime, timedelta
con = duckdb.connect("monitor.duckdb")
# Merge historical orders into a persistent table (one-time import)
con.execute("""
CREATE TABLE IF NOT EXISTS orders AS
SELECT * FROM read_csv_auto('orders_2024*.csv')
""")
# Monitor query: compute core metrics for the past 14 days
def check_health():
today = datetime.now().date()
result = con.execute("""
WITH daily_stats AS (
SELECT
DATE(order_date) AS stat_date,
COUNT(*) AS order_count,
ROUND(AVG(amount), 2) AS avg_order_value,
ROUND(SUM(CASE WHEN status = 'refunded' THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 2) AS refund_rate
FROM orders
WHERE DATE(order_date) >= CURRENT_DATE - INTERVAL '14' DAY
GROUP BY DATE(order_date)
)
SELECT
MAX(CASE WHEN stat_date = CURRENT_DATE - INTERVAL '1' DAY THEN order_count END) AS yesterday_orders,
AVG(order_count) OVER (ORDER BY stat_date ROWS BETWEEN 6 PRECEDING AND 1 PRECEDING) AS ma7_orders,
MAX(CASE WHEN stat_date = CURRENT_DATE - INTERVAL '1' DAY THEN refund_rate END) AS yesterday_refund_rate,
AVG(refund_rate) OVER (ORDER BY stat_date ROWS BETWEEN 6 PRECEDING AND 1 PRECEDING) AS ma7_refund_rate
FROM daily_stats
ORDER BY stat_date DESC
LIMIT 1
""").fetchone()
return {
'yesterday_orders': result[0],
'ma7_orders': result[1],
'yesterday_refund_rate': result[2],
'ma7_refund_rate': result[3]
}
Key points:
read_csv_auto('orders_2024*.csv')— DuckDB supports wildcards to read multiple files at once, no manual concatenation needed- Window function
AVG(...) OVER (...)— Computes 7-day moving average without pre-aggregation - Results persist in
.duckdbfile — Incremental append, not full rebuild each time
4. Anomaly Detection Algorithm
With baseline metrics in hand, the next step is determining whether something is actually abnormal.
We use a simple statistical approach: if the current value deviates from the 7-day average beyond a threshold, flag it as anomalous.
def detect_anomalies(stats):
"""Returns a list of anomalies, each with metric name, current value, expected range, and severity."""
anomalies = []
# Order volume anomaly
if stats['ma7_orders'] > 0:
order_drop = (stats['yesterday_orders'] - stats['ma7_orders']) / stats['ma7_orders']
if order_drop < -0.20: # Drop exceeds 20%
anomalies.append({
'metric': 'Orders',
'value': stats['yesterday_orders'],
'expected': f"~{stats['ma7_orders']:.0f}",
'severity': '🔴 Critical',
'message': f"Yesterday's orders: {stats['yesterday_orders']}, down {abs(order_drop)*100:.1f}% from 7-day avg"
})
elif order_drop > 0.50: # Surge exceeds 50%
anomalies.append({
'metric': 'Orders',
'value': stats['yesterday_orders'],
'expected': f"~{stats['ma7_orders']:.0f}",
'severity': '🟡 Attention',
'message': f"Yesterday's orders: {stats['yesterday_orders']}, up {order_drop*100:.1f}% from 7-day avg—verify if this is a legitimate promotion"
})
# Refund rate anomaly
if stats['ma7_refund_rate'] > 0:
refund_spike = (stats['yesterday_refund_rate'] - stats['ma7_refund_rate']) / stats['ma7_refund_rate']
if refund_spike > 0.50: # Refund rate surges 50%
anomalies.append({
'metric': 'Refund Rate',
'value': f"{stats['yesterday_refund_rate']:.2f}%",
'expected': f"~{stats['ma7_refund_rate']:.2f}%",
'severity': '🔴 Critical',
'message': f"Yesterday's refund rate: {stats['yesterday_refund_rate']:.2f}%, up {refund_spike*100:.1f}% from 7-day avg"
})
return anomalies
Benefits of this approach:
- Tunable thresholds (
-0.20,0.50)—adjust based on business sensitivity - Detects both drops and surges in a single pass
- Structured output—easy to push via Telegram or log to file
5. Telegram Push Module
Use Python’s requests library to call the Telegram Bot API and push anomaly alerts to a channel or group.
import requests
TELEGRAM_BOT_TOKEN = "YOUR_BOT_TOKEN"
CHAT_ID = "YOUR_CHAT_ID" # Group ID or channel ID
def send_telegram(message):
url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage"
payload = {
'chat_id': CHAT_ID,
'text': message,
'parse_mode': 'HTML'
}
try:
r = requests.post(url, json=payload, timeout=10)
return r.json().get('ok', False)
except Exception as e:
print(f"Push failed: {e}")
return False
def notify_anomalies(anomalies):
if not anomalies:
send_telegram(
f"✅ <b>Daily Data巡检 Complete</b>\n\n"
f"📅 {datetime.now().strftime('%Y-%m-%d')}\n"
f"All metrics normal. No action needed.\n\n"
f"— DuckDB Monitor Bot"
)
return
lines = [
f"🚨 <b>Data Anomaly Alert</b>",
f"📅 {datetime.now().strftime('%Y-%m-%d %H:%M')}",
""
]
for a in anomalies:
lines.append(f"{a['severity']} {a['metric']}")
lines.append(f" Current: {a['value']}")
lines.append(f" Expected: {a['expected']}")
lines.append(f" {a['message']}")
lines.append("")
lines.append("--- DuckDB Monitor Bot")
send_telegram("\n".join(lines))
6. Complete Script & Cron Scheduling
Now combine everything into a complete monitoring script:
import os
import glob
import json
from datetime import datetime
def incremental_update():
"""Incremental update: only import new CSV files"""
today_str = datetime.now().strftime('%Y%m%d')
new_files = glob.glob(f'orders_{today_str}*.csv')
if not new_files:
print("No new data files today")
return
for f in new_files:
count = con.execute(f"SELECT COUNT(*) FROM read_csv_auto('{f}')").fetchone()[0]
con.execute(f"""
INSERT INTO orders
SELECT * FROM read_csv_auto('{f}')
WHERE order_id NOT IN (SELECT order_id FROM orders)
""")
print(f"Imported {f}: {count} records")
def main():
# 1. Incremental data update
incremental_update()
# 2. Run health check
stats = check_health()
anomalies = detect_anomalies(stats)
# 3. Push results
notify_anomalies(anomalies)
# 4. Log today's run
log_entry = {
'date': datetime.now().strftime('%Y-%m-%d'),
'anomalies': len(anomalies),
'stats': stats
}
with open('monitor_log.jsonl', 'a') as f:
f.write(json.dumps(log_entry, ensure_ascii=False) + '\n')
print(f"巡检 complete. Found {len(anomalies)} anomalies")
if __name__ == '__main__':
main()
Schedule with cron (Linux):
# Run automatically at 9 AM every day
0 9 * * * cd /home/user/duckdb-monitor && python3 monitor.py >> monitor.log 2>&1
Or use macOS launchd or Windows Task Scheduler—the principle is the same.
7. Advanced: Multi-Source Cross-Database Monitoring
Real-world monitoring often requires crossing data sources. Your e-commerce data is in Shopify API, logistics data is in a JSON file, and financial data is in Excel.
DuckDB’s strength shines here—you don’t need to ETL everything into one database first. Just ATTACH different sources:
# Attach: Shopify orders (CSV)
con.execute("ATTACH 'shopify_orders.csv' AS shopify (READ_ONLY)")
# Attach: Logistics data (JSON)
con.execute("ATTACH 'logistics.json' AS log (READ_ONLY, TYPE JSON)")
# Attach: Financial data (Excel)
con.execute("ATTACH 'finance.xlsx' AS fin (READ_ONLY)")
# Cross-source query: find SKUs with late shipping AND high refund rates
con.execute("""
SELECT
s.sku,
s.product_name,
s.order_count,
AVG(l.transit_days) AS avg_transit,
SUM(CASE WHEN f.status = 'refunded' THEN 1 ELSE 0 END) AS refund_count
FROM shopify.orders s
LEFT JOIN log.packages l ON s.tracking_number = l.tracking_id
LEFT JOIN fin.refunds r ON s.order_id = r.order_id
GROUP BY s.sku, s.product_name
HAVING avg_transit > 7 AND refund_count > 5
ORDER BY refund_count DESC
""").fetchdf()
ATTACH is one of DuckDB’s most underrated features. It lets you query CSV, JSON, Excel, Parquet, PostgreSQL, MySQL, and more—in a single query—without any ETL preprocessing.
8. Comparison with Traditional Monitoring Tools
| Feature | DuckDB + Python | Grafana + Prometheus | Commercial SaaS (Datadog) |
|---|---|---|---|
| Data source access | Direct CSV/JSON/Excel read | Requires Exporter agents | API integration, complex config |
| Anomaly detection | Custom Python logic | Alert rule configuration | Built-in AI anomaly detection |
| Push notifications | Telegram Bot API | Slack/PagerDuty | Multi-channel |
| Deployment cost | $0 (local/VPS) | Self-hosted infrastructure | /node/month |
| Learning curve | Python + SQL | High | Medium |
| Customization | Fully controllable | Medium | Low |
| Best for | SMBs, indie developers | Large infra teams | Enterprise |
10. Summary
| Component | Technology | Cost |
|---|---|---|
| Data querying | DuckDB (Python API) | Free |
| Data storage | Single .duckdb file | Free |
| Anomaly detection | Python logic + SQL window functions | Free |
| Push notifications | Telegram Bot API | Free |
| Scheduling | Linux cron / cloud VPS | Free |
| Total cost | $0 |
Compared to commercial monitoring tools (Grafana + PagerDuty, thousands to tens of thousands annually), this approach is functionally competitive at zero cost.
Remember: The essence of monitoring isn’t technology—it’s the awareness to catch problems before they cascade. DuckDB just makes it trivially simple.
📖 Full runnable code (including multi-source ATTACH examples, Telegram template, and cron configuration guide) is available at duckdblab.org, with complete step-by-step deployment instructions.
💡 Want to systematically learn DuckDB for monitoring, automation, and data products? duckdblab.org has a complete tutorial series.
本文信息
| 项目 | 内容 |
|---|---|
| DuckDB 版本 | v1.5.x(部分功能基于 v2.0 Preview) |
| 最后验证 | 2026-09-12 |
| 测试环境 | Linux / x86_64 / 16GB RAM |
| 官方文档 | DuckDB Documentation |
| GitHub | pengzz9527/duckdb-blog |
如发现错误,欢迎通过 GitHub Issue 或邮件 contact@duckdblab.org 反馈。