- Overview
- Features
- Prerequisites
- Installation
- Usage
- Configuration
- Data Processing Workflow
- Grafana Setup
- Troubleshooting
- License
This project provides a real-time Grafana dashboard for Safecast device monitoring, pulling data from TTServer every 5 minutes and storing it in DuckDB. It uses a flip-flop database approach to eliminate locking issues and ensure continuous Grafana availability.
- Flip-Flop Database Architecture: Uses two databases (A/B) with atomic symlink switching for zero-conflict updates
- Automated Data Collection: Fetches device data every 5 minutes from TTServer
- Performance Optimized: Pre-aggregated summary tables for 18-20x faster Grafana queries
- No Locking Issues: Grafana reads from active DB while updates write to inactive DB
- Simple, Maintainable: Direct TTServer → DuckDB flow without complex dependencies
- DuckDB Integration: Efficient columnar storage for time-series data
- Linux Environment: Ubuntu/Debian recommended
- DuckDB: Database engine for time-series data storage
- Grafana: Visualization platform with MotherDuck DuckDB data source plugin
- jq: JSON processing tool
- wget: For API data fetching
- Passwordless sudo: For Grafana service management (see setup below)
-
Clone the Repository:
git clone https://github.com/Safecast/realtime-grafana-last-24-hours.git cd realtime-grafana-last-24-hours -
Install DuckDB:
wget https://github.com/duckdb/duckdb/releases/download/v1.4.1/duckdb_cli-linux-amd64.zip unzip duckdb_cli-linux-amd64.zip mkdir -p ~/.local/bin mv duckdb ~/.local/bin/ chmod +x ~/.local/bin/duckdb
-
Install Grafana and MotherDuck Plugin:
# Install Grafana (Ubuntu/Debian) sudo apt-get install -y software-properties-common sudo add-apt-repository "deb https://packages.grafana.com/oss/deb stable main" wget -q -O - https://packages.grafana.com/gpg.key | sudo apt-key add - sudo apt-get update sudo apt-get install grafana # Install MotherDuck DuckDB plugin sudo grafana-cli plugins install motherduck-motherduck-datasource sudo systemctl restart grafana-server
-
Set Up Passwordless Sudo for Grafana Restarts:
sudo bash -c 'cat > /etc/sudoers.d/grafana-restart << EOF rob ALL=(ALL) NOPASSWD: /bin/systemctl stop grafana-server rob ALL=(ALL) NOPASSWD: /bin/systemctl start grafana-server rob ALL=(ALL) NOPASSWD: /bin/systemctl restart grafana-server EOF' sudo chmod 440 /etc/sudoers.d/grafana-restart
Run the flip-flop update manually:
./update-flipflop-simple.shSet up automated updates every 5 minutes:
-
Edit the crontab:
crontab -e
-
Add this entry:
*/5 * * * * cd /home/rob/Documents/realtime-grafana-last-24-hours && ./update-flipflop-simple.sh >> /home/rob/Documents/realtime-grafana-last-24-hours/flipflop.log 2>&1
-
Monitor the log:
tail -f /home/rob/Documents/realtime-grafana-last-24-hours/flipflop.log
The flip-flop approach eliminates database locking issues:
┌─────────────────┐ ┌──────────────────────┐
│ TTServer API │ │ Two Databases │
│ │ │ │
│ Device Data │──────▶ devices_a.duckdb │
│ (JSON) │ │ devices_b.duckdb │
└─────────────────┘ └──────────────────────┘
│
▼
┌──────────────────────┐
│ Symlink (atomic) │
│ devices.duckdb ────▶│
└──────────────────────┘
│
▼
┌──────────────────────┐
│ Grafana │
│ (reads via symlink) │
└──────────────────────┘
Update Flow:
- Determine Active DB: Check which database (A or B) Grafana is currently reading
- Stop Grafana: Brief pause to release locks (~5 seconds)
- Write to Inactive DB: Fetch TTServer data and write to the inactive database
- Update Summary Tables: Refresh hourly_summary, recent_data, daily_summary
- Atomic Flip: Change symlink to point to newly updated database
- Restart Grafana: Back online (~10 seconds)
Total downtime: ~30-60 seconds (acceptable for 5-minute update intervals)
- Fetch Data: Pull device readings from TTServer API (
https://tt.safecast.org/devices) - Validate & Filter: Remove invalid timestamps, filter to last 30 days
- Store Measurements: Insert into inactive database (no conflicts!)
- Create Summaries: Generate pre-aggregated tables for fast queries:
hourly_summary: Hourly averages for last 30 daysrecent_data: All readings from last 7 daysdaily_summary: Daily aggregates for all data
- Flip Database: Atomic symlink switch makes new data visible to Grafana
-
Configure MotherDuck Data Source:
- Open Grafana (http://localhost:3000)
- Go to Configuration → Data Sources
- Add MotherDuck data source
- Set database path:
/var/lib/grafana/data/devices.duckdb(the symlink!) - Save & Test
-
Create Dashboard Queries:
Fast query using summary table:
SELECT hour as time, device_sn, avg_radiation, avg_temp FROM hourly_summary WHERE hour >= NOW() - INTERVAL 24 HOURS ORDER BY hour
Recent data (last 7 days):
SELECT when_captured as time, device_urn, lnd_7318u as radiation FROM recent_data WHERE when_captured >= NOW() - INTERVAL 24 HOURS ORDER BY when_captured DESC
- Query Speed: 18-20x faster with summary tables (590ms → 30-50ms)
- Database Size: ~1.6GB for 238K measurements (30 days)
- Update Time: ~30-60 seconds every 5 minutes
- Summary Tables: Pre-aggregated hourly/daily data eliminates expensive GROUP BY queries
- Dashboards: Use the pre-configured dashboards located in the Dashboards folder to visualize data.
# Check cron job status
tail -f /home/rob/Documents/realtime-grafana-last-24-hours/flipflop.log
# Run manual update to see errors
./update-flipflop-simple.shIf you see "Conflicting lock" errors:
- Make sure Grafana is configured to read from
/var/lib/grafana/data/devices.duckdb(symlink) - NOT from
devices_a.duckdbordevices_b.duckdbdirectly - The script stops Grafana automatically, but check it's actually stopping:
sudo systemctl status grafana-server
- Check data source configuration points to symlink
- Verify permissions:
sudo chown grafana:grafana /var/lib/grafana/data/devices*.duckdb sudo chmod 664 /var/lib/grafana/data/devices*.duckdb
- Check which database is active:
ls -la /var/lib/grafana/data/devices.duckdb cat /var/lib/grafana/data/.active_db
The script handles invalid timestamps (like 2012-00-00T00:00:00Z) automatically using TRY_CAST. If you see these errors, the script continues and filters them out.
If queries are slow:
- Use summary tables (
hourly_summary,recent_data,daily_summary) - Check indexes exist:
duckdb /var/lib/grafana/data/devices.duckdb "SHOW TABLES; PRAGMA show_tables;"
This project is licensed under the MIT License. See the LICENSE file for more details.



