Originally published at: Fresha Data Engineering on Medium
About Fresha
Fresha, headquartered in London, runs a booking and business software platform for the beauty and wellness industry. Salons, barbers, spas and other businesses use it for scheduling, payments and client management, and consumers use its marketplace to find and book appointments.
Postgres was serving both transactions and analytics. Moving the hot analytics path to StarRocks internal tables fixed home-page latency for the largest partners and took that load off the OLTP database.
Key takeaway · Keep analytics off the transaction path
The original post was written by Anton Borisov, with credit to Fresha's data engineering team: Emiliano Mancuso, Nicoleta Lazar, Samuel Valente, Paritosh Anand, Piotr Królikowski and Emil Smoleński.
Fresha's data engineering team has run StarRocks in production since spring 2025. As far as the team knows, that made Fresha one of the first companies in the UK to do so.
The challenge
By the middle of 2024, analytics at Fresha ran on two systems, and each covered only part of the need.
Snowflake handled classic BI and large exports. For product and operational analytics that issue many small queries and need near-real-time data, it was neither fast enough nor cost-effective.
The customer-facing PostgreSQL database took on the rest. Ad-hoc analysis and product dashboards sent wide joins and heavy aggregations to the same database that processed transactions. During traffic peaks this caused head-of-line blocking and noisy-neighbor effects: dashboards slowed down unpredictably, and transaction paths, checkout included, were sometimes slower too.
The team wrote down what a new analytics layer had to do:
- Take historical analytics off the OLTP path so Postgres is protected.
- Treat open table formats on object storage (Iceberg and Paimon) as the source of truth.
- Be easy for engineers to adopt: MySQL protocol, standard drivers, little new tooling.
- Absorb traffic spikes without long capacity planning.
- Support tiered freshness: the hottest paths within seconds to minutes, everything else within minutes.
- Keep operations light, with fewer custom pipelines and special-purpose systems.
The solution
After a short evaluation, the team chose StarRocks for a few reasons. It speaks the MySQL wire protocol, so engineers and most BI tools connect to it directly. It can query open table formats in place for history and keep internal columnar tables for the lowest-latency paths. Its materialized views support transparent query rewrite, and in shared-data mode compute can scale without moving data around.
Architecture
Debezium captures changes from Postgres and publishes them to Kafka as Avro, with a Schema Registry. Flink and Spark consume from Kafka and write to the lakehouse, and StarRocks reads the lake through external catalogs.

From the Kafka spine, data moves along three lanes:
- Real-time: Flink writes into StarRocks internal tables. Primary-key tables hold current rows, aggregate-key tables hold pre-rolled metrics, and duplicate-key tables take append-heavy streams. Dashboards and APIs read from them, with materialized views for rollups and pre-joins. Object storage is not on this path.
- Historical: Flink lands operational CDC in Paimon. Iceberg receives long-term facts and slowly changing dimensions from both Flink and Spark, and Spark runs backfills, repairs and compaction. StarRocks queries both formats through external catalogs, and materialized views are refreshed after backfills.
- Text search: Flink and Spark index data into Elasticsearch. StarRocks exposes it through its experimental Elasticsearch catalog, so search results can be joined with other data in SQL.

StarRocks is the single MySQL endpoint in front of all three. That gives Fresha three freshness tiers: hot data in seconds through Kafka, Flink and internal tables; warm data in minutes from StarRocks over Iceberg and Paimon, by federation or optional materialized views; and deep history in Iceberg and Paimon, with Spark backfills.
Home-page analytics
The first workload was the home page that Fresha partners see. It shows top employees compared over two months, top services and a feed of recent sales. On Postgres these widgets were fine for small accounts. For the largest partners, pages took 15 to 20 seconds to load or timed out, and the cold queries pushed useful pages out of the database's buffer cache, which hurt transactional work.
The requirement for the move was minute-level staleness. The team first tried serving the widgets from Iceberg. It worked, but frequent small files and the resulting compaction load made minute-level freshness unreliable. The hot path moved to StarRocks internal tables, and Iceberg and Paimon stayed as the historical record.
The team did not use materialized views here. Instead, a stack of SQL views sits on the internal rt_sales table, which Flink keeps current from the Debezium and Kafka stream. vw_sales_enriched applies business joins and status rules, vw_recent_sales defines what counts as recent, and vw_top_employees_2m and vw_top_services build the widgets. Developers reuse the same definitions, and the optimizer pushes filters and projections down through the views.

Operating practices
The team settled on a few habits. Schema changes go through a migration tool with up and down SQL scripts; it records the schema version in StarRocks, waits for asynchronous ALTER jobs to finish and rolls back on failure. Query tuning starts from EXPLAIN ANALYZE profiles and metrics such as bytes scanned, partitions touched, join type and P50/P95 latency. Tables are partitioned by time and distributed by business keys such as provider_id. Large-to-small joins use broadcast, and large-to-large joins use colocation or bucket shuffle. Bloom filter and bitmap indexes are added only when a profile shows they help. Schema evolution relies on Schema Registry compatibility checks, versioned views switched by pointer, idempotent Flink sinks and CI gates.
The results
Home-page analytics queries now return in about 200 ms, even when users apply the heaviest filters and aggregations, and the data is at most minutes old. Postgres no longer acts as a cache for analytics.
Fresha compared the two paths behind a feature flag. Postgres-backed queries showed frequent spikes of several seconds. With StarRocks enabled, p95 latency dropped to roughly sub-second and the p99 and p99.9 spikes went away.

StarRocks is now part of the team's everyday analytics work, serving real-time tables, lakehouse history and search from one SQL endpoint.