Skip to main content

What we learned building live payment analytics with ClickHouse and Grafana

From generated payment events to operational dashboards and AI-assisted investigation

A payment operations dashboard needs to answer practical questions: Are payments succeeding? Which provider or payment method is struggling? How much unpaid order value needs attention? What evidence should an operator check next?

We built a demonstration around those questions using ClickHouse Cloud, Grafana Cloud, and the native ClickHouse AI assistant. The project started with a dashboard request and developed into a working payment analytics pipeline with repeatable incidents, retry handling, shared metric definitions, and natural-language investigation.

The source is generated demo data. AlphaPay and BetaPay are fictional providers, and no actual customer or payment-provider system is connected. The records change inside ClickHouse on a schedule. That scope made it possible to test the analytical behavior without processing real payments.

The architecture

The running pipeline has six stages:

SQL scenario generator inside ClickHouse
                  |
                  v
Refreshable materialized view every 15 seconds
                  |
                  v
Stored rolling payment_attempts snapshot
                  |
                  v
Latest order state and provider health views
                  |
                  v
Saved SQL queries and investigation rules
          /               |                \
         v                v                 v
Grafana dashboard  ClickHouse dashboard  ClickHouse AI assistant

The database, revenue_demo, lives in our existing ClickHouse Cloud service. A refreshable materialized view generates a rolling hour of payment activity every 15 seconds. Ordinary SQL views turn those attempt records into order-level metrics and provider health indicators.

Grafana connects to ClickHouse through the official ClickHouse data-source plugin over HTTPS with TLS. The native ClickHouse dashboard runs saved queries within the same service. The AI assistant uses the same demo data for investigation.

This implementation has no Postgres source or WalShadow replication. Those would be additional ingestion components for a different version of the project.

A scenario that produces something worth investigating

Steady successful payments would make a poor operational demonstration. We needed a repeatable change that would appear in the charts and produce a meaningful investigation.

The SQL generator creates approximately four orders per simulated second, giving 14,400 unique orders in the rolling hour. Each order has a provider, payment method, region, INR amount, payment status, error code, and latency. Attempt IDs are distinct from order IDs so retries remain visible.

The simulation includes:

Stable IDs and deterministic hashes preserve an order's identity across successive snapshots. Separate hash inputs control provider, method, region, and retry eligibility.

The scheduled refresh is simple:

CREATE MATERIALIZED VIEW IF NOT EXISTS revenue_demo.refresh_payments
REFRESH EVERY 15 SECOND
TO revenue_demo.payment_attempts
AS
SELECT *
FROM revenue_demo.generated_attempts;

Each refresh replaces the stored snapshot. It does not append a new copy of the entire hour, and it is not a measurement of external event-ingestion throughput.

Why order state matters as much as attempt status

One of the first design decisions was to distinguish a failed attempt from an unpaid order.

Consider an order that fails on AlphaPay and succeeds on a later BetaPay attempt. The failure still matters when measuring AlphaPay's attempt success rate. The order should no longer contribute to unpaid order value.

We used a view that groups attempts by order and selects the latest status. The implementation uses both the attempt timestamp and attempt ID to resolve ordering:

SELECT
    order_id,
    argMax(status, tuple(attempt_time, attempt_id)) AS latest_status,
    argMax(provider, tuple(attempt_time, attempt_id)) AS latest_provider,
    max(attempt_time) AS latest_attempt_at
FROM revenue_demo.payment_attempts
GROUP BY order_id;

The full current_orders view also retains order value, creation time, payment method, region, error, freshness, and attempt count.

This gives us two useful metrics with different meanings:

Metric What it counts
Payment attempt success Successful attempts divided by all attempts
Order payment completion Orders whose latest status is successful divided by unique orders

A retry can improve order completion while leaving the original provider failure visible. Keeping both metrics helps explain what customers and payment infrastructure experienced.

Defining unpaid value carefully

We defined unpaid value at risk as the value of unique orders created in the reporting window whose latest attempt is unsuccessful and at least 60 seconds old.

That grace period gives the simulated retry a chance to complete. We call the result at-risk unpaid value because the data does not establish that the revenue is permanently lost.

For the native dashboard, the calculation uses a 15-minute order window:

SELECT
    count() AS orders_15m,
    countIf(latest_status = 'success') AS paid_orders,
    round(
        100.0 * paid_orders / nullIf(orders_15m, 0),
        2
    ) AS completion_pct,
    countIf(
        latest_status != 'success'
        AND latest_attempt_at <= now() - INTERVAL 60 SECOND
    ) AS unpaid_orders,
    sumIf(
        amount_inr,
        latest_status != 'success'
        AND latest_attempt_at <= now() - INTERVAL 60 SECOND
    ) AS unpaid_value_inr
FROM revenue_demo.current_orders
WHERE order_created_at >= now() - INTERVAL 15 MINUTE;

Grafana uses its selected dashboard window for the equivalent order KPIs. Its default is 30 minutes, so its totals can differ from the native 15-minute cards even when both are correct.

Using views and materialized views together

The project uses a refreshable materialized view for the stored simulation snapshot and ordinary views for business calculations.

The ordinary views serve as our semantic layer: they define latest order state, provider health, and investigation rules consistently. A semantic layer is a way of organizing business meaning; it can also sit above materialized aggregates in a larger system.

Ordinary views were convenient here because time windows continue moving even when no new insert arrives. A last-two-minute success rate or a 60-second aging condition needs to be evaluated against the current clock. The small rolling dataset also made query-time aggregation a reasonable starting point.

For higher volumes, we would measure query costs and consider incremental materialized views for provider time buckets and latest-order aggregate states. We have not benchmarked that production design.

Building the visual reports

The Grafana report contains 12 panels, including KPI cards, a success gauge, provider comparison, payment volume, p95 latency, provider health, affected orders, and the deterministic investigator. Provider and payment-method controls filter the relevant queries.

Grafana payment dashboard using generated demo data

Grafana report showing payment KPIs, freshness, and provider behavior. Values are generated demo observations at capture time.

Grafana refreshes every five seconds. The underlying ClickHouse snapshot changes approximately every 15 seconds, so consecutive dashboard refreshes can show the same source data. We added a snapshot-age card to make that distinction visible.

We also expanded the native ClickHouse report to ten visual elements: four KPI cards, success, volume and latency charts, and three operational tables. Its styling and available controls differ from Grafana. It reproduces the main analytical workflow rather than an identical visual layout.

For volume and latency, 15-second buckets expose the simulated event more clearly than a coarse 15-minute grouping:

SELECT
    toStartOfInterval(attempt_time, INTERVAL 15 SECOND) AS bucket,
    provider,
    count() AS total_attempts,
    quantile(0.95)(latency_ms) AS p95_latency_ms
FROM revenue_demo.payment_attempts
WHERE attempt_time >= now() - INTERVAL 30 MINUTE
GROUP BY bucket, provider
ORDER BY bucket, provider;

The native viewer's refresh interval needed attention. We enabled auto-refresh and set it to 15 seconds, but reopening previously returned it to a 60-second default. Source refresh, viewer refresh, and chart bucket size are separate settings.

Adding investigation rules and a native AI assistant

We first created a deterministic investigation view. It compares recent provider and method performance against a baseline, checks sample size and timeout evidence, and returns a finding plus a suggested follow-up.

For example, it flags provider timeouts when recent success drops sufficiently or falls below 85 percent, with at least ten timeout attempts. A small sample produces an insufficient-sample finding. No triggered rule is not a guarantee of health.

Later, after enabling ClickHouse's native AI feature, we tested conversational investigation against the same data.

ClickHouse native report and AI assistant

The built-in ClickHouse assistant beside the native report. The assistant and the SQL rules panel are separate features.

We did not build a custom LLM agent. Both ClickHouse Assistant and Grafana Assistant are vendor-provided features. The ClickHouse assistant executed SQL, saved query definitions, and rendered charts in chat. Its responses are answer-time snapshots; they do not automatically become refreshing dashboard panels.

Reviewing the AI output was an essential part of the work. Its first KPI draft omitted the 15-minute order window and 60-second aging condition. We supplied the exact definition, updated the saved query, and validated the corrected result.

We also corrected an explanation that treated latency as a proven cause of failures. The demo showed elevated latency and timeouts together; the report should describe that evidence and suggest checks without claiming a root cause it has not established.

What we checked

We checked data invariants as well as the visual report. One recorded validation run on November 11 returned:

Check Recorded result
Payment attempts 14,661
Unique attempt IDs 14,661
Unique orders 14,400
Successful retry attempts 261
Duplicate attempt IDs 0
Invalid retry statuses 0
Attempts after their generated timestamp 0
Snapshot age 9 seconds

A separate order-level check found no retried orders still classified as unpaid. These checks ran against separate snapshots, so their results should not be presented as one atomic reconciliation. The nine-second snapshot age is not a replication-latency benchmark.

We verified the Grafana connection health check, live KPI updates, source-age resets, provider filtering, charts, and tables. In ClickHouse, we verified the native report queries and an AI investigation that identified the simulated AlphaPay UPI timeout signal while distinguishing observations from root-cause inference.

We did not perform sustained load testing, production failover testing, or reconciliation against an external payment source.

What a production version would need

The next step would be to replace the SQL simulator with an actual event source:

Checkout service or payment provider
    -> authenticated event collection or source CDC
    -> durable ingestion pipeline
    -> ClickHouse payment event tables
    -> latest order state and provider aggregates
    -> dashboards and controlled AI investigation

That work would need idempotent event handling, duplicate and late-event processing, cancellation and refund semantics, source reconciliation, retention, and freshness monitoring. The analytical connection should use a dedicated restricted identity, and AI query scopes should be reviewed.

Managed Postgres and WalShadow could be evaluated if Postgres were chosen as the application's transactional source. They are not components of the current demonstration.

Lessons from the project

The most useful result was a shared, testable definition of payment health. Latest-order state prevented recovered orders from inflating unpaid value. Explicit windows explained differences between dashboards. Fine time buckets made a short degradation visible. Freshness indicators made the refresh behavior understandable.

The AI assistant added a useful conversational route into the data, but its output still needed review against the SQL definitions. The corrected KPI draft was a practical reminder to make metric scope and business rules explicit in every investigation.

The completed demonstration now provides a reproducible path from changing payment data to operational charts and AI-assisted analysis. The remaining production work starts at the source and ingestion layer.

Further reading and project material

Practical data engineering notes and implementation insights by Ameer Shaik, including hands-on ClickHouse pipelines, dashboards and analytics.

Burning Questions
About CelestInfo

Simple answers to make things clear.

Our AI insights are continuously trained on large datasets and validated by experts to ensure high accuracy.

Absolutely. CelestInfo supports integration with a wide range of industry-standard software and tools.

We implement enterprise-grade encryption, access controls, and regular audits to ensure your data is safe.

Insights are updated in real-time as new data becomes available.

We offer 24/7 support via chat, email, and dedicated account managers.

Still have questions?

Get Assistance

Ready? Let's Talk!

Get expert insights and answers tailored to your business requirements and transformation.

Get Assistance