Back to Blogengineering

Fast profit reports at scale, by design

Postgres runs the product. ClickHouse runs the history. Reports stay responsive as order volume grows, without caching fake totals to look fast.

Animated column-oriented data flowing into analytical charts

You do not open Pluto to admire a dashboard. You open it because you need an answer: after ad spend, COGS, fees, shipping, and refunds, what did the store actually keep? That answer needs to be current and quick to reach. Otherwise, the spreadsheet starts looking useful again.

Reporting is the product#

Pluto assembles data from several sources. Orders arrive from Shopify. Spend comes from ad platforms. Product costs, payment fees, shipping, and custom expenses complete the picture. The work behind that view is substantial, but the experience should not feel complicated.

A merchant should be able to change a date range, switch stores, or filter by channel and keep moving. Those are not occasional reporting tasks. They are the normal way people use Pluto, so query speed is a product requirement from the start.

Two databases, two different jobs#

Pluto separates operational data from analytical data. Postgres is the source of truth for things such as shops, connections, settings, costs, and sync state. These records are created and updated individually, and consistency matters more than scanning millions of historical rows.

ClickHouse handles the growing history behind reports: orders, ad performance, and daily profit facts. A report may need to read months of data, group it by day, product, store, or channel, and calculate several totals at once. That is a different workload, and it deserves a database designed for it.

Why ClickHouse#

Transactional databases are excellent at frequent writes, updates, and precise lookups. Profit reporting asks a different kind of question: "Show net profit for the last 90 days, grouped by channel, after refunds." Answering it means scanning and aggregating a large set of facts.

ClickHouse stores data by column. If a report only needs the date, channel, revenue, and cost columns, it can read those columns without pulling every field from every record. Values with the same type and distribution are stored together, which makes column-specific compression more effective and reduces the data that needs to move. The result is a better fit for the questions Pluto asks repeatedly.

Fast without hiding the numbers#

We do not cache profit figures in Redis to make the dashboard appear fast. Redis is used for coordination work such as locks and idempotency. Financial results are read from the databases that hold the underlying facts. Freshness comes from the sync pipeline, not from serving an old number until a cache expires.

The same rule applies to the calculation path. Monetary values remain decimals rather than floating-point numbers, and missing source data should be visible instead of quietly estimated away. A fast answer is only useful when the merchant can trust it.

What fast means here#

Fast is not a benchmark slide. It is changing the range, getting the number, and continuing your work without losing context. We care about that path more than a theoretical peak. That means report-friendly schemas, fewer unnecessary round trips, and queries that stay fast as order history grows.

Built to grow with the store#

Parts of the architecture will evolve, but the requirement will not: profit reports should remain responsive as stores add more orders, products, channels, and history.

For how Pluto keeps the numbers returned by those reports exact, read How Pluto keeps money precise from API to database.

Questions or sharp takes: join Discord or email hello@plutoprofit.com.