Monitor business metrics với Metabase + VPS PostgreSQL

Chia sẻ bài viết

Mục lục
TL;DR
  • Metabase open source 7 năm tuổi, 38k star GitHub, là BI tool tốt nhất cho startup không muốn trả Tableau/Looker hàng nghìn USD/tháng.
  • Chạy Docker Compose trên Cloud VPS 80 (799k/tháng) là đủ cho team 20 người và database 50GB.
  • Kết nối read-replica PostgreSQL để query không block production. Cache result 5-15 phút cho dashboard nặng.
  • Quan trọng: MRR, churn, conversion funnel, cohort retention, DAU/WAU/MAU. Code SQL mẫu sẵn dùng được.
  • Bonus: alert email khi metric vượt threshold, embed dashboard public, schedule daily report.

Bạn launched product 6 tháng, có 500 user, doanh thu 100 triệu/tháng. Nhưng hỏi: tuần này MRR tăng hay giảm? Churn rate bao nhiêu? Cohort tháng 3 retain còn mấy phần trăm? Không trả lời được vì data nằm rải rác trong PostgreSQL, chưa ai pull ra dashboard. Metabase giải bài đó: kết nối DB, viết SQL, render chart, share team, alert khi bất thường. Hoàn toàn open source, self-host trên VPS, không tốn xu nào ngoài hosting.

Mình deploy Metabase cho 6 startup khách hàng và 2 dự án nội bộ. Workflow chuẩn: 1 ngày setup, 2 ngày build dashboard cốt lõi, sau đó founder tự xem chart hằng ngày không cần dev support. Bài này chia sẻ full setup, SQL template, và 12 metric startup nào cũng nên track.

1. Tại sao chọn Metabase thay vì Tableau, Looker, Power BI?

3 lý do chính: rẻ, dễ dùng cho non-dev, self-host được.

ToolChi phí/user/thángSelf-hostSQL editorKhó học
Metabase OSSFreeDễ
Metabase Cloud~85 USD/5 userKhôngDễ
Tableau70 USDServer enterpriseKhó
Looker~5000 USD/clusterGCP onlyLookMLRất khó
Power BI10 USDServer enterpriseDAXTrung bình
SupersetFreeTrung bình

Metabase thắng cho team Việt: UI tiếng Anh dễ đọc, có tính năng "question builder" để non-tech tự tạo chart bằng click chuột, SQL editor có autocomplete, dashboard share link với password public.

2. Cài Metabase trên VPS bằng Docker Compose

Metabase cần database riêng để lưu config (questions, dashboards, user). Khuyến nghị dùng PostgreSQL chính cho app, tạo schema metabase_app riêng. Không nên dùng H2 mặc định vì không bền và khó backup.

mkdir -p /opt/metabase
cd /opt/metabase

File docker-compose.yml:

services:
  metabase:
    image: metabase/metabase:v0.51.0
    container_name: metabase
    restart: unless-stopped
    environment:
      MB_DB_TYPE: postgres
      MB_DB_HOST: db.your-domain.com
      MB_DB_PORT: 5432
      MB_DB_DBNAME: metabase_app
      MB_DB_USER: metabase
      MB_DB_PASS: "your_secure_password"
      MB_SITE_URL: https://bi.your-domain.com
      JAVA_TIMEZONE: Asia/Ho_Chi_Minh
      MB_PASSWORD_COMPLEXITY: strong
      MB_PASSWORD_LENGTH: 12
    ports:
      - "127.0.0.1:3000:3000"
    healthcheck:
      test: curl -f http://localhost:3000/api/health || exit 1
      interval: 30s
      timeout: 10s
      retries: 5

Tạo database và user trên PostgreSQL trước:

psql -U postgres
CREATE USER metabase WITH PASSWORD 'your_secure_password';
CREATE DATABASE metabase_app OWNER metabase;
GRANT ALL PRIVILEGES ON DATABASE metabase_app TO metabase;

Khởi động Metabase, đợi 1-2 phút lần đầu migrate schema:

docker compose up -d
docker logs -f metabase

3. Caddy reverse proxy với HTTPS

bi.your-domain.com {
    reverse_proxy 127.0.0.1:3000
    encode gzip
    header {
        Strict-Transport-Security "max-age=31536000;"
        X-Frame-Options "SAMEORIGIN"
    }
    log {
        output file /var/log/caddy/metabase.log
    }
}

Reload caddy, vào https://bi.your-domain.com sẽ thấy wizard setup: tạo admin account, kết nối database production (read-only user khuyến nghị), import sample data Metabase cung cấp để học.

4. Tạo read-only user PostgreSQL cho Metabase

Quan trọng: KHÔNG cho Metabase user toàn quyền DB production. Nếu lỡ ai đó query DROP TABLE trong SQL editor là xong. Tạo user chỉ SELECT:

CREATE USER metabase_ro WITH PASSWORD 'another_secure_password';
GRANT CONNECT ON DATABASE app_production TO metabase_ro;
GRANT USAGE ON SCHEMA public TO metabase_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO metabase_ro;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO metabase_ro;

-- Set statement timeout 30s để query nặng không treo DB
ALTER USER metabase_ro SET statement_timeout = '30s';
ALTER USER metabase_ro SET idle_in_transaction_session_timeout = '60s';

Nếu có read-replica thì connect Metabase vào replica chứ không phải primary, đảm bảo query analytics không ảnh hưởng app.

5. Metric thứ 1: MRR (Monthly Recurring Revenue)

SQL tính MRR cho SaaS subscription:

SELECT
  date_trunc('month', invoice_date) AS month,
  SUM(amount / EXTRACT(month FROM (period_end::date - period_start::date)::interval + interval '1 day')) AS mrr
FROM invoices
WHERE status = 'paid'
  AND invoice_date >= NOW() - interval '12 months'
GROUP BY 1
ORDER BY 1;

Chart line, x = month, y = mrr. Set Metabase question refresh cache 1 hour. Save vào dashboard "Finance".

6. Metric thứ 2: Churn Rate hằng tháng

WITH monthly_stats AS (
  SELECT
    date_trunc('month', current_date - generate_series(0, 11) * interval '1 month') AS month
)
SELECT
  ms.month,
  COUNT(DISTINCT s.user_id) FILTER (WHERE s.canceled_at >= ms.month AND s.canceled_at < ms.month + interval '1 month') AS churned,
  COUNT(DISTINCT s.user_id) FILTER (WHERE s.created_at < ms.month AND (s.canceled_at IS NULL OR s.canceled_at >= ms.month)) AS active_start,
  ROUND(
    100.0 * COUNT(DISTINCT s.user_id) FILTER (WHERE s.canceled_at >= ms.month AND s.canceled_at < ms.month + interval '1 month')
    / NULLIF(COUNT(DISTINCT s.user_id) FILTER (WHERE s.created_at < ms.month), 0), 2
  ) AS churn_pct
FROM monthly_stats ms
CROSS JOIN subscriptions s
GROUP BY 1
ORDER BY 1;

Industry benchmark: SaaS B2B healthy churn dưới 2%/tháng. Trên 5%/tháng là red flag. Set alert email khi churn_pct vượt 5%.

7. Metric thứ 3: Cohort Retention

SELECT
  cohort_month,
  months_since_signup,
  COUNT(DISTINCT user_id) AS retained_users,
  ROUND(100.0 * COUNT(DISTINCT user_id) / FIRST_VALUE(COUNT(DISTINCT user_id))
    OVER (PARTITION BY cohort_month ORDER BY months_since_signup), 1) AS retention_pct
FROM (
  SELECT
    u.id AS user_id,
    date_trunc('month', u.created_at) AS cohort_month,
    EXTRACT(year FROM age(date_trunc('month', e.event_date), date_trunc('month', u.created_at))) * 12
    + EXTRACT(month FROM age(date_trunc('month', e.event_date), date_trunc('month', u.created_at))) AS months_since_signup
  FROM users u
  JOIN events e ON e.user_id = u.id
  WHERE u.created_at >= NOW() - interval '12 months'
) AS cohorts
GROUP BY cohort_month, months_since_signup
ORDER BY cohort_month, months_since_signup;

Trong Metabase chọn visualization "Pivot Table" hoặc "Heat map" để hiển thị cohort matrix. Row = cohort_month, Column = months_since_signup, Cell = retention_pct.

8. Metric thứ 4: DAU/WAU/MAU và stickiness

-- DAU 30 ngày qua
SELECT
  event_date::date AS day,
  COUNT(DISTINCT user_id) AS dau
FROM events
WHERE event_date >= NOW() - interval '30 days'
GROUP BY 1
ORDER BY 1;

-- Stickiness = DAU/MAU ratio
WITH daily AS (
  SELECT day::date, COUNT(DISTINCT user_id) AS dau
  FROM events
  WHERE event_date >= NOW() - interval '30 days'
  GROUP BY day::date
),
monthly AS (
  SELECT COUNT(DISTINCT user_id) AS mau
  FROM events
  WHERE event_date >= NOW() - interval '30 days'
)
SELECT
  ROUND(AVG(d.dau)::numeric / m.mau, 3) AS stickiness
FROM daily d, monthly m
GROUP BY m.mau;

Stickiness > 0.2 (DAU/MAU) là tốt cho consumer app, > 0.5 là xuất sắc (Facebook level). Cho SaaS B2B 0.1-0.15 đã chấp nhận được.

9. Metric thứ 5: Conversion Funnel

WITH funnel AS (
  SELECT
    COUNT(DISTINCT user_id) FILTER (WHERE event_name = 'visit_landing') AS step_1_visit,
    COUNT(DISTINCT user_id) FILTER (WHERE event_name = 'click_signup') AS step_2_click_signup,
    COUNT(DISTINCT user_id) FILTER (WHERE event_name = 'create_account') AS step_3_register,
    COUNT(DISTINCT user_id) FILTER (WHERE event_name = 'activate_account') AS step_4_activate,
    COUNT(DISTINCT user_id) FILTER (WHERE event_name = 'first_purchase') AS step_5_purchase
  FROM events
  WHERE event_date >= NOW() - interval '30 days'
)
SELECT
  step_1_visit, step_2_click_signup, step_3_register, step_4_activate, step_5_purchase,
  ROUND(100.0 * step_2_click_signup / NULLIF(step_1_visit, 0), 2) AS conv_1_to_2,
  ROUND(100.0 * step_3_register / NULLIF(step_2_click_signup, 0), 2) AS conv_2_to_3,
  ROUND(100.0 * step_4_activate / NULLIF(step_3_register, 0), 2) AS conv_3_to_4,
  ROUND(100.0 * step_5_purchase / NULLIF(step_4_activate, 0), 2) AS conv_4_to_5
FROM funnel;

Tìm step nào drop nhiều nhất, đó là chỗ cần optimize UX/copy đầu tiên. Mình thường thấy step "create_account to activate" drop 50-70%, fix bằng email onboarding sequence.

10. Metric thứ 6: LTV, CAC, payback period

-- LTV avg
SELECT
  AVG(total_revenue) AS avg_ltv,
  AVG(lifetime_months) AS avg_lifetime_months
FROM (
  SELECT
    user_id,
    SUM(amount) AS total_revenue,
    EXTRACT(month FROM age(MAX(invoice_date), MIN(invoice_date))) + 1 AS lifetime_months
  FROM invoices
  WHERE status = 'paid'
  GROUP BY user_id
) AS user_revenue;

-- CAC by channel
SELECT
  acquisition_channel,
  SUM(spend) / NULLIF(COUNT(DISTINCT user_id), 0) AS cac
FROM marketing_spend ms
JOIN users u ON u.acquisition_channel = ms.channel AND date_trunc('month', u.created_at) = ms.month
GROUP BY 1
ORDER BY cac;

LTV/CAC > 3 là healthy. < 1 là đốt tiền marketing không hiệu quả. Payback period = CAC / (ARPU * gross margin), nên dưới 12 tháng cho SaaS.

11. Dashboard cấu trúc cho startup

  • Executive Dashboard: MRR, ARR, total users, total revenue, growth rate. View hằng ngày.
  • Acquisition Dashboard: visits, signups, conversion funnel, CAC by channel, ad spend ROI.
  • Retention Dashboard: cohort retention, churn rate, NPS score, feature adoption.
  • Finance Dashboard: revenue by plan, refund rate, failed payment, AR aging.
  • Product Dashboard: DAU/MAU, top features used, time-to-aha-moment, session duration.

Mỗi dashboard nên có 6-12 chart, đừng quá nhồi nhét. Founder xem 30 giây phải hiểu trạng thái business.

12. Setup alert email khi metric bất thường

Metabase có tính năng Alert built-in. Cấu hình SMTP trong Admin Settings (dùng Mailgun, Postmark, hoặc Gmail SMTP):

SMTP Host: smtp.mailgun.org
SMTP Port: 587
SMTP Security: STARTTLS
SMTP Username: [email protected]
SMTP Password: $MAILGUN_PASSWORD
From Address: [email protected]

Trong mỗi question, click Settings (chuông), set điều kiện: "Send email when this question returns any results" và viết SQL kiểu:

SELECT
  date,
  churn_pct
FROM monthly_churn
WHERE date = date_trunc('month', NOW())
  AND churn_pct > 5;

Khi query trả về row, Metabase tự gửi email tới founder. Cron schedule mỗi ngày 8h sáng.

13. Performance tuning cho dashboard nặng

  1. Cache result: Admin Settings, Caching, set TTL 60-300 giây cho question nặng. Không cần realtime cho metric tháng/tuần.
  2. Materialized view: tạo view trong PostgreSQL aggregate sẵn data, Metabase chỉ query view. Refresh view 1h/lần qua cron.
  3. Index column hay dùng: CREATE INDEX trên cột date, user_id, status để query GROUP BY và WHERE nhanh.
  4. Tách database analytics: nếu DB lớn, dùng dbt copy data sang warehouse riêng (ClickHouse, DuckDB), Metabase query warehouse.
  5. Limit row trong SQL editor: thêm LIMIT 10000 cho query exploration, tránh load hàng triệu row vào browser.

14. Bonus: embed dashboard public không cần login

Metabase có "Public Sharing" toggle cho dashboard. Bật xong sẽ có link share, có thể embed iframe vào website company:

Hữu ích cho investor update, monthly board report, hoặc public metric như Buffer/Baremetrics. Backup an toàn: disable feature này nếu data sensitive.

15. Backup config Metabase và disaster recovery

Tất cả dashboard, question, user permission của Metabase lưu trong metabase_app database. Backup hằng đêm là bắt buộc, không thì khi VPS hỏng phải build lại 200+ chart từ đầu.

#!/bin/bash
# /opt/metabase/backup.sh
DATE=$(date +%F-%H%M)
BACKUP_DIR=/opt/backups/metabase
mkdir -p $BACKUP_DIR

# Dump metabase config db
PGPASSWORD=your_pass pg_dump -h db.your-domain.com -U metabase metabase_app 
  | gzip > $BACKUP_DIR/metabase-$DATE.sql.gz

# Push lên S3-compatible storage
aws s3 cp $BACKUP_DIR/metabase-$DATE.sql.gz s3://your-backup-bucket/metabase/ 
  --endpoint-url https://s3.your-domain.com

# Xoá file cũ hơn 30 ngày local
find $BACKUP_DIR -name "metabase-*.sql.gz" -mtime +30 -delete

Crontab: 0 3 * * * /opt/metabase/backup.sh. Test restore quarterly: pull file gz, restore vào DB staging, mở Metabase staging xem dashboard có hiện lại không. Snapshot Cloud VPS TND cũng là backup layer thứ 2 cho cả OS và Docker volume.

16. Khi nào nên upgrade từ Metabase sang data warehouse?

Metabase chạy SQL trực tiếp trên DB application. Khi DB > 500GB hoặc query analytics ăn quá nhiều CPU/IO, cần tách layer warehouse:

  • Stage 1 (Metabase + DB primary): dưới 50GB DB, dưới 10 user analytics.
  • Stage 2 (Metabase + DB read-replica): 50-500GB DB, 10-30 user.
  • Stage 3 (Metabase + dbt + ClickHouse/DuckDB): 500GB+, query phức tạp, cần materialized model.
  • Stage 4 (warehouse cloud): nhiều TB, multi-source data (web, app, ads, CRM), cân nhắc Snowflake/BigQuery.

Mình advise startup ở stage 1-2 trong 2-3 năm đầu. Sang stage 3 chỉ khi thực sự có nhu cầu, tránh over-engineering tốn tiền và thời gian setup.

Cloud VPS cho vibe coder

VPS chạy Metabase + PostgreSQL cho startup

Cloud VPS TND sẵn AlmaLinux 9, Ubuntu 22/24, Debian 12/13. SSD CEPH, snapshot 1-click, backup hằng ngày, network 200Mbps trong nước. Cấu hình 4-8GB RAM đủ chạy Metabase + Postgres analytics cho team 20 dev, dashboard render dưới 1 giây.

Xem 8 cấu hình Cloud VPS →

FAQ

Metabase OSS có giới hạn gì so với Pro/Enterprise?

OSS đủ 95% nhu cầu startup: dashboard, SQL editor, alert email, public share, embed iframe, kết nối 20+ database. Pro/Enterprise có thêm: SSO SAML, audit log, multi-tenant, white-label, official iframe embedding signed. Cho startup giai đoạn đầu OSS dư dùng.

VPS bao nhiêu RAM đủ cho Metabase?

Metabase JVM ăn tối thiểu 2GB RAM, idle khoảng 1.2GB. Cho team 5 user và database 10GB, Cloud VPS 40 (399k, 2GB RAM) là đủ. Team 20 user và DB 100GB, lên Cloud VPS 80 (4GB RAM). Lưu ý chia sẻ VPS với PostgreSQL thì cộng thêm 2-4GB nữa.

Có nên connect Metabase trực tiếp vào DB production?

Không khuyến nghị. Tạo read-only user PostgreSQL, set statement_timeout 30s, tốt nhất là dùng read-replica để query analytics không impact production. Nếu DB nhỏ và load thấp thì có thể dùng primary với read-only user.

So với Apache Superset thì Metabase có gì khác?

Metabase dễ dùng cho non-dev, có Question Builder click-and-go. Superset mạnh hơn về visualization (50+ chart type), SQL Lab cho power user, multi-tenant tốt hơn. Cho startup dưới 50 người, Metabase win về UX. Cho team data engineer chuyên nghiệp, Superset linh hoạt hơn.

Metabase kết nối được những database nào?

PostgreSQL, MySQL/MariaDB, MongoDB, SQL Server, Oracle, Redshift, BigQuery, Snowflake, ClickHouse, Druid, Presto, Trino, SQLite, H2 và nhiều hơn nữa qua community driver. Startup phổ biến: PostgreSQL cho app + ClickHouse cho event analytics nếu data lớn.

Bản quyền chính hãng Hóa đơn VAT đầy đủ Đổi key lỗi 30 ngày Vận hành từ 2009 MST 0200994870 Hotline 0225.999.6666
Gọi ngay Chat Zalo Messenger