This blog was authored by Sven Hubert, Associate Director IT Operations at Wolters Kluwer, Oliver Heimlich, Lead Product Software Engineer at Wolters Kluwer, Volker Krebs, DevOps Engineer at Wolters Kluwer
As part of Wolters Kluwer, a global leader in professional information, software solutions, and services, Wolters Kluwer Tax & Accounting Germany develops software that helps tax advisory firms and midsize businesses streamline payroll, bookkeeping, banking, and tax compliance. With decades of experience, we are a leading technology partner in the industry's transition towards an integrated digital ecosystem.
More than 6,000 tax advisory firms and 14,000 midsize businesses rely on our solutions, which process 2.5 million payslips every month, 2.5 million tax returns annually, and support 15% of Germany’s annual financial statements.
All these applications run on PostgreSQL, but their data requirements vary significantly, from transactional accounting and dense payroll data to complex tax structures requiring frequent recalculation. At the same time, decades-long customer relationships mean years of historical data must remain readily accessible.
Combined, those demands have helped drive our PostgreSQL data from about 6 TB in early 2026 to nearly 10 TB by September 2026, and it's still roughly doubling every year. Keeping ahead of that kind of growth is what led us to adopt elastic clusters in Azure Database for PostgreSQL, enabling us to scale PostgreSQL horizontally as our data volumes and workloads continue to expand.
Looking for a more sustainable way to scale
Our PostgreSQL architecture has evolved as our business has grown. Before moving to Azure Database for PostgreSQL three years ago, we managed PostgreSQL ourselves on Azure infrastructure, which meant our team was responsible for server patching, backups, and day-to-day database operations. Azure Database for PostgreSQL gave us the managed platform-as-a-service (PaaS) model we wanted and reduced that operational burden.
But our data kept growing. Reaching 6 TB was not a hardware problem. We could still scale vertically with relatively little operational effort. The challenge was that the growing size of our datasets increasingly affected PostgreSQL's day-to-day operation. By that point, 42 tables exceeded 10 GB and several had grown beyond 100 GB. Larger tables and indexes made maintenance tasks, queries, updates, and retention-driven cleanup operations more expensive, and a single poorly performing query could impact the wider system.
Our engineers were manually creating or tuning indexes in production almost every week, consuming roughly half of one engineer's time, while peak memory usage approached 90%. We wanted to retain the managed experience of Azure Database for PostgreSQL while gaining the ability to scale horizontally. With our data continuing to roughly double each year, it became clear that we needed a more sustainable scaling model.
Finding a path to horizontal scale
Those requirements narrowed the field quickly. We wanted to stay on PostgreSQL so we could preserve our existing data-access layer and the time-based partitioning we'd already built with pg_partman. Any new solution also needed to run on Microsoft Azure and provide a managed PaaS experience rather than require us to manage the infrastructure ourselves.
Data residency requirements limited our options even further: we needed high availability and disaster recovery in multiple German regions.
We evaluated several options, running proof-of-concept tests on synthetic banking data. But it was hard to find one that supported development and CI testing without requiring a live cluster and was also available in two German regions.
When elastic clusters in Azure Database for PostgreSQL reached general availability in both Germany West Central and Germany North, we had our path forward. Elastic clusters use the Citus extension to turn PostgreSQL into a distributed database. It's still PostgreSQL, so scaling out is transparent to our applications, and we keep the managed-service model we already relied on. We also had a head start because our custom data-access layer was already sharding-aware from that earlier proof-of-concept work. The move meant extending a partitioning strategy we understood, not rewriting the application from scratch.
Getting that architecture right took longer than the cutover itself. Across roughly 400 tables, we decided which ones to distribute by tenant, which to replicate as reference tables, and which also needed pg_partman's time-based partitioning layered on top. That combination mattered because our data is both multi-tenant and long-lived, with some customers' records going back decades. We spent about three months on that design and cleanup work as well as continuous testing to ensure a smooth and short migration. The production migration itself ran over a single weekend earlier this year, when we used pgcopydb to move roughly 6.4 TB of data onto the new cluster.
What we learned as an early adopter
Working with elastic clusters at production scale taught us things we hadn't seen in smaller test environments, particularly around connection pooling. We hadn't used PgBouncer in our previous architecture, so we initially ran elastic clusters without it as well. But distributing and partitioning our data multiplied the number of shards, and in Citus, memory cost per connection scales with shard count. Adding PgBouncer reduced our physical connections to close to one-sixth of their previous level, helping us use cluster resources far more efficiently.
We also learned that the test environment has to match production exactly, not just the data but every configuration setting. We enabled high availability only after the production migration, and that surfaced an issue we hadn't seen in testing. Now we validate failover, health probes, and traffic routing with high availability enabled before making any changes to the cluster.
However, being early to the platform also meant we didn't have to work through issues like that on our own. We had direct access to the Azure Database for PostgreSQL product group throughout the project, beyond the standard support model. That relationship has become an ongoing feedback loop, where we bring current challenges and upcoming requirements to Microsoft's team, test new capabilities against our own environment, and help shape what matters most as the platform evolves.
Leaving weekly database troubleshooting behind
We went into this migration primarily looking for a more sustainable way to scale. What exceeded our expectations was the performance improvement we saw after the migration and the relatively short tuning and optimization work that followed.
Before the migration, some queries encountered multi-second I/O waits as the database grew larger. Since then, average statement execution time has stabilized below 20 milliseconds, and peak memory now stays consistently below 80%. The weekly cycle of hunting slow queries and hand-building new indexes has stopped completely. Long-standing performance tickets have been closed as "not reproducible," and work that had consumed close to half of one engineer's time now requires only occasional attention.
That shift means our engineers spend less time chasing the next database bottleneck and more time building the features our customers use. That matters on the development side, too: roughly 100 developers work against the databases across development, test, integration, and production, with around two dozen systems running at once. Once we'd proven the configuration in production, rolling it out across those environments was straightforward. We use the same Terraform provisioning scaled down to inexpensive single-node clusters, and a plain Citus Docker container for local work.
Preparing for the next doubling
Just as important, we now have room to keep scaling. The growth that took us from roughly 6 TB to nearly 10 TB hasn't slowed, and we don't expect it to. As that data grows, we need our applications to keep performing for the thousands of firms and businesses that rely on them every day. But for the first time, that expansion feels like something our architecture is designed for rather than something we're constantly reacting to.
Today, our production deployment of elastic clusters in Azure Database for PostgreSQL runs across six nodes. As data volumes continue to increase, we expect to add a seventh and eighth, testing the scale-out process against a restored production backup before applying it live. With elastic clusters, we can now scale both horizontally and vertically as our capacity needs change. And we're continuing to work with Microsoft on what comes next, from further optimizing connection pooling to major PostgreSQL version upgrades, as we prepare for the next doubling and the one after that.