From PostgreSQL 12 to MariaDB 11: A Gradual Fintech Migration with 23% Lower TCO

Database migrations are rarely only about replacing one database server with another.

In real production systems, especially in fintech, a migration is usually about reducing risk, keeping the application online, improving scalability, and giving teams more room to evolve the architecture without freezing product development.

This story is about a fintech B2B enterprise that migrated from a monolithic PostgreSQL 12 environment to MariaDB 11. The company runs a payments-related platform for discounts and cashback, where users can check balances, transaction history, applicable discounts, and other account information. The payment-related part of the workload was mostly transactional, while the rest was mixed, with roughly 60% reads and 40% writes.

The result was a gradual migration completed in approximately one year, with a final 23% reduction in TCO and a 20% improvement in time-to-market for new features.

The starting point: a monolithic PostgreSQL database

The original environment was based on PostgreSQL 12 and deployed on bare metal, with a primary server and standby instances. One standby was deployed on the same machine, and another on a second machine.

The database had become a bottleneck not because PostgreSQL 12 could not store the data, but because the overall architecture no longer matched the business needs.

The company had introduced microservices, but the database was still monolithic. Several services could access the same tables, creating operational complexity and making scaling difficult. The system also had mixed workloads: transactional operations, batch processing, and analytical queries that were better suited to columnar execution.

The main service operated with approximately 80–100 TB of data, with smaller services adding around 10 TB each. There were approximately up to 1,000 microservices in the project. Data volume itself was not the primary obstacle. Schema complexity and the monolithic database design were.

On an average day, the system handled around 1 million queries, but during promotional campaigns, this could increase to 10 million queries per day.

The team was already using pgpool and pgbouncer for their usual roles in the PostgreSQL environment, but the architecture needed more than pooling and connection management. It needed a way to evolve from a single shared database to a more flexible model, in which services and workloads could be separated more cleanly.

Why not just upgrade PostgreSQL?

A database version upgrade was considered, but it did not solve the architectural problem.

The team estimated that a PostgreSQL-to-PostgreSQL migration would take around two years

As you may know, you can’t just switch between PostgreSQL versions. If they had chosen a fresher version after 12, almost all of their queries would have needed fine-tuning.

That was too long for the business, which wanted to expand into several new markets within roughly 12 to 18 months.

Other options were also considered.

A PostgreSQL-and-ClickHouse combination was rejected because the workload still had a significant transactional component, and the existing team lacked ClickHouse expertise. Citus was also evaluated, but it did not address the columnar portion of the workload as the team needed.

The requirement was not simply “make PostgreSQL newer.” The requirement was:

  • keep the application running,
  • migrate gradually,
  • support OLTP and some OLAP,
  • reduce architectural complexity,
  • avoid long maintenance windows,
  • and make future changes easier.

That is where MariaDB became interesting.

Why MariaDB 11?

MariaDB was selected because it could meet several needs within a single database ecosystem.

The decisive factors included Galera clustering, ColumnStore for analytical workloads, better observability, predictable internals, and support for both transactional and analytical use cases.

The target architecture used MariaDB 11 on bare metal. Transactional workloads remained on InnoDB, while analytical workloads moved to ColumnStore.

The analytical workload was not constant heavy analytics. It was mostly used by marketing teams to review fintech service performance weekly, monthly, and quarterly.

This distinction matters. Many real-world systems are not purely OLTP or purely OLAP. They are mostly transactional, but with analytical queries that cannot be ignored. In this case, MariaDB allowed the team to keep transactional workloads on InnoDB and move analytical workloads to ColumnStore where appropriate.

ColumnStore was part of the MariaDB environment, although not every cluster included ColumnStore.

Sharding by time, migrating object by object

Before the migration, sharding had already started partly on the application side, influenced by the microservice architecture. There was no Citus involved. The environment combined replication and sharding.

The sharding strategy continued to be based on time periods, typically months or weeks.

That detail shaped the migration approach.

Instead of performing a big-bang migration, the team moved one object at a time: a table, a partition, or a loosely coupled part of the schema. They started with objects that had fewer dependencies, because those were easier to isolate and validate.

The process looked like this:

  1. Copy the object from PostgreSQL to MariaDB.
  2. Apply ongoing changes through change data capture.
  3. Use dual writes during the transition.
  4. Keep PostgreSQL as the default source until the team is ready.
  5. Promote the object to MariaDB when the related tables and application code are ready.

In some cases, a dump was enough for the initial copy. In others, dump plus CDC was used. Change capture was handled through Otterstax.

An object could live in both PostgreSQL and MariaDB for several weeks. Promotion to MariaDB usually happened within two to four weeks, depending on whether related tables had also moved and whether the application code had already switched to MariaDB.

This gradual approach reduced risk. There was no need for a long maintenance window, and the team could focus on one table or partition at a time.

It is important to note that the client was a major company in the fintech sector that required a service level agreement (SLA) of 99.95%. Validating one object or query at a time by sending write operations to both the current and new systems simultaneously was the preferred solution, and perhaps the safest and most sensible choice.

The role of Otterstax

Otterstax is a data fabric + data mesh solution with cross-database routing & proxying functionality. In this migration, it acted as a unified control point for routing queries, managing cross-database operations, supporting consistency checks, and enabling a gradual transition from PostgreSQL to MariaDB. Unlike PostgreSQL-specific components such as pgpool and pgbouncer, Otterstax also provided capabilities across multiple database technologies and remained part of the production architecture after the migration.

Otterstax was placed between the applications and the databases as a routing layer. Before the migration, Python and Go services accessed PostgreSQL through pgpool and pgbouncer. During the migration, Otterstax replaced those components and added cross-database capabilities.

It was used for:

  • connection pooling,
  • query routing,
  • dual writes,
  • replication,
  • consistency checks,
  • and cross-database operations.

At the start, PostgreSQL remained the default target for all queries. The DBA team decided when to switch a query or object to MariaDB.

For queries that needed data from both PostgreSQL and MariaDB during the transition, Otterstax used cross-database joins.

Consistency was managed with CDC, dual writes, and additional mechanisms, including a write-ahead log inside Otterstax.

One important point is that Otterstax was not only a migration tool. It remained part of the production architecture after the migration, giving the company the option to add more database technologies later if needed, for example, after mergers or acquisitions.

Application changes: not zero, but controlled

A good migration layer does not mean the application never changes.

Some application adaptation was still required. Most changes were related to SQL dialect differences and switching queries to MariaDB syntax. The team described this as standard work that did not require extra effort or unexpected costs.

SQL dialect differences were a major part of the work. The application used functions, JSON operations, and pgvector, but not stored procedures or triggers.

JOIN operations required special attention. They were among the more challenging parts of the migration, not just because of performance, but also because some queries had to access data split across PostgreSQL and MariaDB. Data warehouse queries (star joins) worked well on the old PostgreSQL setup. When the team moved to microservices, they rewrote these queries, but only once, since ColumnStore handled the load well. They thought there would be more work, but the MariaDB ecosystem accommodated their case.

The gradual table-by-table approach also influenced testing. Instead of running a single massive test phase for the entire database, the team tested each migrated object and its related application changes.

The new deployment

Results: lower TCO, better flexibility

The migration was completed in approximately one year, faster than the initial worst-case expectation of up to one and a half years.

The TCO reduction was originally projected at 25%. The final measured reduction was 23%, which was close enough to the estimate to consider the project successful.

The TCO comparison included infrastructure, cloud costs, operational effort, instance sizes, storage, and engineering time. Licensing was not part of the comparison because the solutions used were open-source, and support costs had less impact since the company relied mostly on in-house expertise.

The number of database servers decreased after the migration. Query latency and throughput improved by 10 sec for certain queries. Analytical queries became faster, especially ones for DWH – they started to execute within a 3x shorter time frame, and the previous analytical bottlenecks were eliminated.

The team also reported improvements in reliability, scalability, and deployment flexibility.

There was one important trade-off: overall resource consumption did not decrease. As the company scaled and introduced new capabilities, resource usage increased. But this was a consequence of growth and new functionality, not a failure of the migration.

Lessons learned

The most important lesson from this migration is that the safest migration is not always the shortest one.

The team avoided a big-bang approach. PostgreSQL remained the default at first. Objects were gradually copied, synchronized, validated, and promoted. This gave DBAs and developers control over each step.

The most significant risk was not technical. It was organizational: management could have decided to roll back if the projected numbers were not achieved quickly enough.

For similar fintech and banking workloads, where the system is primarily OLTP but still needs some OLAP capabilities, this architecture can serve as a useful reference.

The advice from the team is simple and pragmatic: stay open-minded and evaluate multiple solutions instead of assuming there is only one correct path.

Conclusion

This migration was not only from PostgreSQL to MariaDB.

It was a move from a monolithic database architecture to a more flexible platform that supports microservices, transactional workloads, analytical queries, and future expansion.

MariaDB 11, InnoDB, ColumnStore, and Galera clustering gave the team the foundation they needed. Otterstax provided the routing and migration layer, enabling a gradual transition.

The outcome was a production migration completed in about one year, a 23% reduction in TCO, faster analytical query times, improved latency and throughput, and a 20% improvement in time-to-market for new features.

They still have some points to explore further, such as support for the VECTOR Data Type and Vector Search, which were announced just before the migration.

For teams facing a similar situation, the key takeaway is clear: when the database architecture becomes the bottleneck, a version upgrade may not be enough. Sometimes the better path is a controlled, object-by-object migration to an architecture that is easier to scale, observe, and evolve… and MariaDB Server provides you with all that!