|
| 1 | +--- |
| 2 | +title: Using PostgreSQL with QueryCanary |
| 3 | +slug: servers/postgresql |
| 4 | +--- |
| 5 | +QueryCanary fully supports PostgreSQL for production data monitoring. This guide walks you through connecting your database, writing SQL checks, and troubleshooting common issues. |
| 6 | + |
| 7 | +## Overview |
| 8 | + |
| 9 | +QueryCanary connects directly to your PostgreSQL database — with or without SSH tunneling — and allows you to define SQL checks to monitor important conditions in your data (e.g. low signups, null prices, broken joins). |
| 10 | + |
| 11 | +You can: |
| 12 | + |
| 13 | +- Run scheduled SQL queries on your prod database |
| 14 | +- Set expectations or anomaly detection on results |
| 15 | +- Receive alerts via email or Slack when something looks off |
| 16 | + |
| 17 | +## Requirements |
| 18 | + |
| 19 | +- PostgreSQL version 10 or higher |
| 20 | +- A read-only database user |
| 21 | +- Access via direct TCP or through an SSH tunnel |
| 22 | + |
| 23 | +We recommend connecting to a replica or using a read-only role for safety. |
| 24 | + |
| 25 | +## Setup |
| 26 | + |
| 27 | +### 1. Create a Read-Only User |
| 28 | + |
| 29 | +In your Postgres server: |
| 30 | + |
| 31 | +```sql |
| 32 | +-- 1. Create a dedicated read-only user |
| 33 | +CREATE USER querycanary_reader WITH PASSWORD 'your_secure_password'; |
| 34 | + |
| 35 | +-- 2. Allow it to connect to your database |
| 36 | +GRANT CONNECT ON DATABASE your_database TO querycanary_reader; |
| 37 | + |
| 38 | +-- 3. Grant usage on the schema you want to monitor (typically public) |
| 39 | +GRANT USAGE ON SCHEMA public TO querycanary_reader; |
| 40 | + |
| 41 | +-- 4. Grant read-only access to all existing tables |
| 42 | +GRANT SELECT ON ALL TABLES IN SCHEMA public TO querycanary_reader; |
| 43 | + |
| 44 | +-- 5. Ensure access to future tables too |
| 45 | +ALTER DEFAULT PRIVILEGES IN SCHEMA public |
| 46 | +GRANT SELECT ON TABLES TO querycanary_reader; |
| 47 | +``` |
| 48 | + |
| 49 | +## Connecting to Postgres |
| 50 | + |
| 51 | +### Option 1: Direct Connection |
| 52 | + |
| 53 | +Use this if your database is publicly accessible or hosted on a service like RDS, Supabase, or Fly.io. |
| 54 | + |
| 55 | +You’ll need: |
| 56 | + |
| 57 | +- Hostname (e.g. db.example.com) |
| 58 | +- Port (usually 5432) |
| 59 | +- Database name |
| 60 | +- Username & password |
| 61 | + |
| 62 | +### Option 2: SSH Tunnel (Recommended for Private Networks) |
| 63 | + |
| 64 | +Use this if your DB is in a private VPC and only accessible from a bastion host. |
| 65 | + |
| 66 | +You’ll additionally need: |
| 67 | + |
| 68 | +- Bastion host (hostname/IP) |
| 69 | +- SSH username |
| 70 | +- SSH public key (we generate one for you securely) |
| 71 | + |
| 72 | +QueryCanary connects via SSH, opens a secure tunnel, and connects to your internal Postgres instance. |
| 73 | + |
| 74 | +## Writing SQL Checks |
| 75 | + |
| 76 | +You can monitor anything you can query. Example: |
| 77 | + |
| 78 | +**Daily Signups Check** |
| 79 | + |
| 80 | +```sql |
| 81 | +SELECT COUNT(*) FROM users WHERE created_at >= CURRENT_DATE - INTERVAL '1 day'; |
| 82 | +``` |
| 83 | + |
| 84 | +**Broken Joins Check** |
| 85 | + |
| 86 | +```sql |
| 87 | +SELECT COUNT(*) FROM orders o LEFT JOIN users u ON u.id = o.user_id WHERE u.id IS NULL; |
| 88 | +``` |
| 89 | + |
| 90 | +## Troubleshooting |
| 91 | + |
| 92 | +**Can't connect to database** |
| 93 | + |
| 94 | +- Make sure the hostname and port are reachable |
| 95 | +- Check that the user has CONNECT and SELECT permissions |
| 96 | +- Confirm no VPN/firewall blocks our IP (contact support if needed) |
| 97 | + |
| 98 | +**SSH tunnel fails** |
| 99 | + |
| 100 | +- Make sure your bastion host is reachable |
| 101 | +- Add the provided public key to ~/.ssh/authorized_keys on the bastion |
| 102 | +- Check that the bastion user can access the database internally |
| 103 | + |
| 104 | +**Query fails or returns empty** |
| 105 | + |
| 106 | +- Test your query locally with psql or your app first |
| 107 | +- Avoid LIMIT, ORDER BY, or formatting functions — we only need values |
| 108 | +- Use COUNT, SUM, AVG, or conditional expressions to track value changes |
0 commit comments