Self-Hosted Alternative to Algolia and AWS Aurora: A Free Blueprint for Scaling Past 25 Million Records
Scaling a growing platform to tens of millions of records almost always ends with a painful invoice. Algolia's own 2026 pricing works out to roughly $9,960 a month in record-storage fees alone once you cross 25 million records, before a single search request is billed (checkthat.ai, 2026). Managed Elasticsearch and OpenSearch clusters aren't much kinder: they typically need 1 to 8 GB of RAM per node just to stay stable (OSSAlt, 2026).
None of this is unique to e-commerce catalogs. B2B multi-tenant directories, IoT device registries, high-throughput CMS platforms, and large asset-tracking systems all hit the same wall. A single relational database can't take on high-frequency writes and typo-tolerant search at the same time without one starving the other.
The good news is that you don't have to choose between an expensive managed stack and a database that falls over. This is the exact blueprint I use to build a highly available, write-optimized storage tier paired with a sub-50ms search layer, using MySQL Galera Cluster, Typesense, and RabbitMQ, all free and self-hosted.
Key Takeaways
- A 25-million-record catalog on Algolia's Grow tier costs roughly $9,960/month in record fees alone, on top of search-request billing (checkthat.ai, 2026).
- Splitting writes (MySQL Galera) from search (Typesense) and syncing them through a durable queue (RabbitMQ) removes the lock contention that kills single-node databases at scale.
- Self-hosting isn't literally free: you're still paying for compute. What disappears is per-record and per-request licensing, which is the part that actually scales against you.
Where a Single-Node Database Actually Breaks
A lone MySQL or Postgres instance usually starts choking on concurrent full-text search long before it runs out of disk space. The problem isn't storage. It's contention: every scraper update, state change, or user edit competes for the same locks that your search queries need to read from.
I've hit this exact ceiling on a high-volume marketplace platform I built for a client. Once the catalog crossed into the tens of millions of active listings, the single primary MySQL instance became the bottleneck for both scraper-driven writes and buyer-facing search, well before storage was ever a concern. Splitting write and read paths, the same pattern below, is what got response times back under a second.
Two demands are fighting for the same resource:
| Demand | What it needs | What breaks first |
|---|---|---|
| High-frequency writes | Fast inserts, minimal lock contention | B-tree indexes degrade, tables lock |
| Typo-tolerant search | Full-text matching, faceted filtering | Query latency spikes under write load |
The fix isn't a bigger server. It's decoupling the two workloads into a write-optimized storage tier and a read-optimized search tier, connected by a reliable sync layer. In practice, that's CQRS applied at the infrastructure level rather than just in your application code: one system of record for writes, a separate, purpose-built index for reads.
I saw the same shape of problem building PosifyHub, a multi-tenant inventory and POS platform for SMEs: even a well-indexed Postgres table starts to choke once you're running fuzzy, cross-tenant product search on top of constant stock updates.
Building a Write Tier That Never Goes Down: MySQL Galera Cluster
Galera Cluster uses synchronous, multi-master replication, so every node can accept writes and a transaction only commits once the other nodes confirm it. That's a meaningfully different guarantee than a standard master-replica setup, where the single write node is still a point of failure. Because it relies on quorum, you need at least three nodes to survive a single node failure without risking a split-brain.
Worth noting: "Galera" itself is a replication plugin, not a shippable product on its own. In production you'll run it through Percona XtraDB Cluster or MariaDB Galera Cluster, both free and widely deployed.
Core Cluster Configuration (my.cnf)
[mysqld]
binlog_format=ROW
default-storage-engine=InnoDB
innodb_autoinc_lock_mode=2
innodb_doublewrite=1
# Galera Provider Configuration
wsrep_on=ON
wsrep_provider=/usr/lib/galera/libgalera_smm.so
wsrep_cluster_name="catalog_cluster"
wsrep_cluster_address="gcomm://10.0.0.1,10.0.0.2,10.0.0.3"
wsrep_sst_method=mariabackup
# Sync parameters
wsrep_provider_options="gcache.size=2G; evs.keepalive_period=PT3S"
innodb_autoinc_lock_mode=2 isn't optional here. Without interleaved auto-increment, multi-master writes can generate conflicting IDs across nodes.
Put ProxySQL, also free and open-source, in front of the cluster to distribute writes across all active nodes. If one node drops, ProxySQL reroutes traffic without a visible outage.
A quick note on nomenclature: if you've been comparing this to AWS Aurora Multi-Master, that specific feature is gone. AWS deprecated it a few years back, tied to the MySQL 5.6-compatible Aurora version that has since reached end of life (AWS documentation, 2023). Its closest living relative is Aurora PostgreSQL Limitless Database, a sharded, serverless offering that went generally available in October 2024. It's Postgres-only, though, and still billed by usage (AWS News Blog, 2024). Galera doesn't need any of that: it's been doing synchronous multi-master on plain MySQL for over a decade.
Why Typesense Beats Elasticsearch for Self-Hosted Search at This Scale
Running full-text search against a relational database directly falls apart well before 25 million rows. Typesense, a free, open-source, in-memory search engine written in C++, is built to sidestep that entirely, and it does it on a fraction of the hardware Elasticsearch needs. A self-hosted Typesense node can run on roughly 256 MB of RAM, versus 1 to 8 GB minimum for Elasticsearch and around 512 MB for Meilisearch (OSSAlt, 2026).
Why does that gap exist? Elasticsearch runs on the JVM, and JVM heap tuning is its own discipline. Typesense skips the JVM entirely, which is most of why its footprint stays small even as your dataset grows.
(Chart: minimum RAM per self-hosted search node, Typesense vs. Meilisearch vs. Elasticsearch. Source: OSSAlt, 2026. See note below on rendering.)
Schema Definition & Querying in Typesense
const schema = {
name: 'catalog_items',
fields: [
{ name: 'title', type: 'string' },
{ name: 'description', type: 'string' },
{ name: 'category', type: 'string', facet: true },
{ name: 'is_active', type: 'bool', facet: true },
{ name: 'popularity', type: 'int32' }
],
default_sorting_field: 'popularity'
};
// Typo-tolerant, filtered search
const results = await typesenseClient.collections('catalog_items').documents().search({
q: 'sensor controller',
query_by: 'title, description',
filter_by: 'category:=electronics && is_active:true',
sort_by: 'popularity:desc',
per_page: 20
});
For high availability, Typesense uses the Raft consensus algorithm and replicates your full dataset to every node. Like Galera, it needs a minimum of three nodes to tolerate one failure (Typesense documentation, 2026). One trade-off worth knowing up front: HA replication buys you redundancy and read throughput, not extra storage capacity, since each node holds the entire index.
Keeping MySQL and Typesense in Sync Without Losing a Single Record
Once your source of truth (Galera) and your read index (Typesense) live in separate systems, you need a sync layer that survives crashes, restarts, and out-of-order delivery. RabbitMQ paired with Celery workers does that job.
+---------------+ Write +------------------------+
| Ingestion App | -------------> | MySQL Galera Cluster |
+---------------+ +------------------------+
|
| Publish event
v
+---------------+
| RabbitMQ |
+---------------+
|
| Consume task
v
+---------------+ Upsert +------------------------+
| Celery Worker | -------------> | Typesense Search Index |
+---------------+ +------------------------+
Reliable Ingestion Mechanics
- Persistent messages. Publish with
delivery_mode=2so messages are written to disk and survive a broker restart. - Dead-letter exchanges (DLX). A worker that fails on a temporary network timeout routes its message to a dead-letter queue for retry instead of losing it silently.
- Idempotency and version checks. Because messages can arrive out of order, every update carries a version or timestamp. The worker only applies an update if it's newer than what's already indexed.
One honest caveat: delivery_mode=2 marks a message as persistent, but RabbitMQ still batches writes to disk on its own schedule. That means it isn't an absolute guarantee against loss on its own (RabbitMQ Reliability Guide, 2026). For anything you can't afford to lose, pair persistent messages with quorum queues, RabbitMQ's replicated queue type, and publisher confirms. Publisher confirms wait for an acknowledgment that the message actually made it to disk before your app moves on.
# Idempotent Celery task processing updates safely
@app.task(bind=True, max_retries=3)
def sync_record_to_typesense(self, record_id, updated_data, version_timestamp):
try:
existing = typesense.collections('catalog_items').documents(record_id).retrieve()
if existing and existing.get('version') >= version_timestamp:
return # Stale, out-of-order message; ignore it
typesense.collections('catalog_items').documents().upsert({
'id': record_id,
'version': version_timestamp,
**updated_data
})
except Exception as exc:
raise self.retry(exc=exc, countdown=5)
On Virtual Investor Pal, a real estate intelligence platform I built that processes 10TB+ of property data across PostgreSQL and MongoDB, a stale search result isn't just an annoyance. It means showing an investor a rental estimate that no longer matches the underlying comps. That's the kind of bug that erodes trust fast, and it's exactly what the version check above is designed to prevent.
What This Actually Costs, Compared to Algolia and Managed Elasticsearch
Free software doesn't mean free infrastructure. You're still paying for compute, just not for every record or every search request. A single Hetzner CCX33 instance (8 dedicated vCPUs, 32GB RAM), enough to run one node comfortably, is listed at €138.49/month after Hetzner's June 2026 price adjustment (costgoat.com, 2026). Run six of those, a 3-node Galera cluster plus a 3-node Typesense cluster, and you're looking at roughly €830/month (about $900). That number stays flat whether you're storing 5 million records or 50 million.
Algolia's bill doesn't stay flat. Here's how its published Grow-tier record fee ($0.40 per 1,000 records beyond the included 100,000) scales as a catalog grows, based on Algolia's 2026 self-serve rates (checkthat.ai, 2026):
| Records | Algolia record-storage fee (monthly) |
|---|---|
| 1,000,000 | ~$360 |
| 5,000,000 | ~$1,960 |
| 10,000,000 | ~$3,960 |
| 15,000,000 | ~$5,960 |
| 20,000,000 | ~$7,960 |
| 25,000,000 | ~$9,960 |
That comparison only covers record-storage fees. Algolia also bills separately for search requests, so the real gap at scale is larger than the chart shows.
This is the same calculus I walk clients through as part of a cloud infrastructure and FinOps audit: the win from self-hosting isn't that it's free, it's that your bill stops scaling against your own growth. I ran a similar cost-modeling exercise on Cloud Auditor, a concurrent cost-auditing framework for AWS and Azure, and the pattern holds everywhere: managed convenience is usually billed per unit, and infrastructure you operate yourself is usually billed per node.
When This Architecture Is the Wrong Choice
This stack isn't the right default for every team. It's worth being upfront about when to skip it.
- You don't have dedicated ops capacity. Patching Galera nodes, watching Raft consensus health on Typesense, and rotating RabbitMQ credentials is real, ongoing work that a managed service would otherwise absorb for you, at a price.
- Your workload is analytical, not transactional. Put timeseries aggregation and log analytics in TimescaleDB or ClickHouse instead.
- You're nowhere near this scale yet. Under a couple million records, six VMs and three separate systems to operate is complexity you don't need yet. A single well-tuned Postgres or MySQL instance, properly indexed, will still outperform it.
- You need a vendor SLA. Some regulated environments simply require a support contract and audit trail that only a managed provider offers.
Frequently Asked Questions
Why not just use Elasticsearch instead of Typesense?
You can, but it costs more to run. Elasticsearch needs significant JVM heap tuning and typically 1 to 8 GB of RAM per node to stay stable, versus roughly 256 MB for Typesense (OSSAlt, 2026). Elasticsearch still makes sense if you're already running the ELK stack for log analytics or need its deeper aggregation features. For app and catalog search, though, Typesense usually wins on cost and simplicity.
Does MySQL Galera actually replace AWS Aurora Multi-Master?
Not exactly, because Aurora Multi-Master doesn't exist anymore. AWS deprecated it years ago, tied to an Aurora MySQL version that's since reached end of life (AWS, 2023). Galera gives you the synchronous multi-master write model that Aurora Multi-Master used to promise, on plain MySQL, without a managed-service bill attached.
How does Galera protect against data loss if a node crashes?
Galera's replication is synchronous: a write only commits once every active node in the cluster has confirmed it. If a node goes down, the remaining nodes keep serving traffic without losing any committed transaction.
Is delivery_mode=2 alone enough to guarantee RabbitMQ never loses a message?
No, and this is a common misconception. Persistent delivery mode gets messages written to disk, but RabbitMQ doesn't confirm that write instantly by default (RabbitMQ Reliability Guide, 2026). For a real guarantee, combine persistent messages with quorum queues and publisher confirms.
Can this stack handle IoT telemetry or SaaS log analytics?
For the transactional and search side, yes. Galera scales to millions of writes and Typesense stays fast across tens of millions of records. For the heavy analytical aggregations that timeseries telemetry usually needs, pair this stack with a dedicated engine like TimescaleDB or ClickHouse rather than trying to force that workload into Galera or Typesense.
Is this setup actually free, or does the cost just move somewhere else?
The software licensing cost goes away. The compute cost doesn't. You're still paying for VMs, and that pricing isn't static either: Hetzner raised its dedicated-vCPU pricing by well over 100% in June 2026 alone (Northflank, 2026). What you avoid is the part of the bill that scales with your record count and search volume, which is what actually punishes growth under Algolia or a managed search API.
Strategic Takeaways
You don't need a five-figure monthly bill to run a highly available database and search layer at 25 million records and beyond.
- Decoupling beats scaling up. Splitting writes (Galera) from reads (Typesense) removes the lock contention that kills single-node databases, and it's the same CQRS pattern used at the application layer, just applied one level down.
- "Free" means no per-unit licensing, not zero cost. Budget for compute anyway.
- Durability needs more than one setting.
delivery_mode=2is a start, not a finish. Pair it with quorum queues, publisher confirms, and idempotent consumers if you genuinely can't afford to lose data, the same version-check pattern shown earlier.
If you're weighing whether to build this yourself, hire a freelancer, or bring in an agency, I've written separately about how to make that call. And if you're scoping the multi-tenant SaaS platform that sits on top of this data layer, that's the kind of AI-SaaS build I take on end to end, architecture through deployment.