Unusual ways to use databases: 8 production-ready tricks

Contents
Most infrastructure sprawl doesn't come from genuine scaling needs, it comes from reaching for a specialized tool before checking whether the database you already run can do the job. Postgres can queue, search, and vector-match.
Redis can rank. SQLite can ship inside the browser. The trade-off isn't capability, it's knowing where the ceiling sits before you hit it in production.
This piece walks through eight unconventional database patterns we've shipped, with runnable snippets and the latency and throughput numbers we saw when replacing dedicated infrastructure with them. That ceiling looks different once you're running dedicated cloud data warehouses like Snowflake, where compute and storage scale independently by design.
TL;DR: 8 databases doing someone else's job
The database you already run in production can usually replace three other systems on your infrastructure diagram, not just one. Most teams reach for Kafka, Elasticsearch, or a dedicated vector store before checking whether Postgres or Redis already does the job at their actual scale.
In our work with engineering teams migrating queue, search, and vector workloads, the pattern repeats: dedicated infra gets provisioned before anyone benchmarks the boring option already in the stack.
| Database | Unusual Use | Why It Works | Limitation |
|---|---|---|---|
| PostgreSQL | Job queue via SKIP LOCKED queue pattern | Skips rows locked by other workers, no broker to run | Throughput ceiling below Kafka at high partition counts |
| PostgreSQL + pgvector | Similarity search on vector embeddings | HNSW index sits next to relational data, one round trip | Recall tuning gets fiddly past a few million rows |
| PostgreSQL | tsvector full-text search | Native GIN index, no cluster to operate | Relevance ranking is cruder than a dedicated search engine |
| Redis | Leaderboards, rate limits via sorted sets | O(log N) ordered ops in memory | Volatile, memory-bound |
| SQLite WASM | Offline-first client apps | Runs inside the browser, zero network hop | Single-writer model |
| TimescaleDB | Time-series analytics on Postgres | Hypertables and continuous aggregates | Overkill below high write volume |
| CouchDB | Offline sync for mobile and edge | Built-in multi-master replication | Conflict resolution is manual work |
Each row below gets its own trade-off breakdown, including throughput numbers we've tracked on the SKIP LOCKED pattern against a comparable Kafka setup.
Postgres as a message queue: The SKIP LOCKED pattern
The SKIP LOCKED queue pattern turns a plain Postgres table into a functioning message queue: workers run SELECT... FOR UPDATE SKIP LOCKED to claim rows without blocking on locks already held by other workers. According to PostgreSQL's official documentation, the SKIP LOCKED clause was added in PostgreSQL 9.5 specifically to support this kind of concurrent job-processing workload.
The mechanics stay simple: a jobs table with a status column, an index on (status, created_at), and a transaction that locks a row, processes it, then deletes it or marks it done. No broker, no separate ops surface, no second system to watch for consumer lag.
That result is the ceiling worth knowing before you commit to this pattern. RabbitMQ and Kafka both scale queue throughput horizontally, by adding brokers or partitions. A Postgres queue scales reads by adding replicas, but writes still funnel through one primary, and vacuum pressure on a high-churn jobs table grows with every insert-delete cycle.
| System | Ordering guarantee | Delivery semantics | Ops surface |
|---|---|---|---|
| Postgres SKIP LOCKED | Per-row, no replay | At-least-once | None beyond the existing database |
| RabbitMQ | Per-queue FIFO | At-least-once / exactly-once with plugins | Broker cluster |
| Kafka | Per-partition, replayable | At-least-once / exactly-once | Broker plus ZooKeeper or KRaft cluster |
We reach for Postgres over RabbitMQ or Kafka when job volume stays in the low thousands per second and the team wants audit-friendly SQL against queue state more than replay or partition-level ordering.
Past that ceiling, or once consumers need to replay history, a dedicated broker still wins. Since job state lives in ordinary tables, backing up that queue table is as simple as running a standard PostgreSQL backup and restore process.
Pgvector vs a dedicated vector database
Pgvector adds native vector embeddings support to PostgreSQL, and it replaces a dedicated vector database for most retrieval and search workloads we build today. The real question isn't whether Postgres can do vector search. It's at what row count and query pattern a separate system starts paying for itself.
Pgvector ships two index types. IVFFlat partitions vectors into lists and needs the list count tuned to dataset size, or recall degrades.
HNSW builds a navigable graph instead, trading a slower, memory-heavier build for better recall at a given latency.
One constraint worth knowing upfront: indexed columns cap out at 2,000 dimensions for both IVFFlat and HNSW (pgvector GitHub Issue #461). Higher-dimensional embeddings (some OpenAI or custom models exceed this) need to be truncated, dimensionality-reduced, or stored unindexed, which trades query speed for flexibility.
Creating and querying an HNSW index looks like this:
CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
SELECT id FROM items
ORDER BY embedding <=> '[0.1-0.2, ...]'
LIMIT 10;
According to pgvector's own benchmark suite, HNSW pulls ahead of IVFFlat on recall-per-millisecond once a table passes a few hundred thousand rows. That pattern matches peer-reviewed ANN literature on graph-based indexes over inverted-file methods. An ANN-Benchmarks study found that graph-based methods like HNSW achieve higher recall-QPS tradeoffs than IVF across the GLOVE and SIFT benchmark datasets.
In deployments we've supported, HNSW-indexed pgvector tables holding 1 to 5 million rows typically return top-10 queries in single-digit to low double-digit milliseconds at p95, running on infrastructure a small platform team already operates.
It breaks down in three places: past tens of millions of vectors, where index build time and memory footprint stop fitting on one instance; under high-throughput concurrent writes, where HNSW insert overhead causes index bloat faster than autovacuum reclaims it; and when the vector index needs sharding and replication independent of the relational data next to it.
| System | Index types | Latency at scale | Breaks down when |
|---|---|---|---|
| pgvector | IVFFlat, HNSW | Single-digit to low double-digit ms p95 into the low millions of rows with HNSW | Write-heavy ingestion causes index bloat; dataset outgrows one instance |
| Dedicated vector DB (Pinecone, Weaviate, Milvus) | HNSW, IVF, product quantization | Built for consistent latency past 100M+ vectors, native sharding | Adds a system to operate, sync, and license separately |
| Netguru's default stance | pgvector first | N/A | Migrate only once volume, QPS, or multi-region needs outgrow a single Postgres instance |
Our rule of thumb: start on pgvector unless you already know you're past ten million vectors, need embeddings above 2,000 dimensions indexed, or require the index replicated across regions independently of the source data.
Full-text search with tsvector: Do you still need Elasticsearch?
Tsvector full-text search handles the majority of search workloads we build without needing Elasticsearch at all. PostgreSQL's built-in text search type stores a normalized, weighted list of lexemes per document, and a GIN index on that column gets you ranked, stemmed, stopword-aware search in the same transaction as your writes.
Here's what it looks like in practice:
ALTER TABLE articles ADD COLUMN search_vector tsvector
GENERATED ALWAYS AS (
setweight(to_tsvector('english', title), 'A') ||
setweight(to_tsvector('english', body), 'B')
) STORED;
CREATE INDEX idx_articles_search ON articles USING GIN(search_vector);
SELECT title, ts_rank_cd(search_vector, query) AS rank
FROM articles, plainto_tsquery('english', 'database performance tuning') query
WHERE search_vector @@ query
ORDER BY rank DESC
LIMIT 10;
That query runs in single-digit milliseconds on a few hundred thousand rows with a warm cache, which is fast enough that most teams never notice a difference against a dedicated search engine.
The honest limit isn't correctness, it's relevance tuning depth. ts_rank_cd covers boosting titles over body text via weights, but it does not give you fielded boosting, synonym graphs, or learning-to-rank pipelines out of the box. On recall, plain tsvector matching is exact-lexeme after stemming, so typo tolerance and fuzzy matching need pg_trgm bolted on separately. Elasticsearch ships fuzzy matching and synonym expansion natively, which is where its recall advantage on messy user queries actually comes from, not raw indexing power.
Index size is the second decision axis. GIN indexes on tsvector columns grow with vocabulary size and update frequency, and heavy write workloads cause the same bloat pattern you'd see on any GIN or GiST index, requiring periodic REINDEX or autovacuum tuning to keep query plans fast.
According to DB-Engines' search-engine ranking, Elasticsearch remains the most widely deployed dedicated search engine globally, reflecting real demand for faceted search and multi-tenant index isolation at scale that Postgres doesn't natively provide.
Our rule of thumb: stay on tsvector while search is a feature bolted onto an existing relational dataset and relevance means "reasonable ranking," not a tuned scoring model.
Move to Elasticsearch once you need cross-field boosting, near-real-time faceted aggregation across tens of millions of documents, or a dedicated relevance engineering team maintaining custom analyzers. Most teams never cross that line, and adding Elasticsearch earlier just adds an operational system to keep in sync.
If you cross that line, working with a team providing expert Elasticsearch implementation support can help you avoid the operational pitfalls of a DIY migration.
Redis sorted sets for leaderboards and rate limiting
Redis sorted sets solve two problems that look unrelated until you notice they share the same shape: ranked membership and sliding time windows. A ZSET stores a member with a floating-point score in an order that Redis maintains automatically, giving you O(log N) insertion and O(log N + M) range reads regardless of set size.
For leaderboards, the score is the metric you rank by, points, elo, response time, and ZRANGEBYSCORE or ZREVRANGE gets you a page of ranked results without a sort at read time. Compare that to running ORDER BY score DESC LIMIT 50 against a hot table in Postgres under concurrent writes, where you are paying for an index scan and lock contention on every leaderboard refresh.
Rate limiting is the less obvious use, and it's the one worth stealing. Instead of a fixed bucket counter, use a ZSET per client where the score is the request timestamp.
On each request, ZREMRANGEBYSCORE evicts anything older than your window, then ZCARD gives you the current count against the limit, all inside one round trip.
That's a true sliding window, not the fixed-window approximation most token-bucket implementations fall back to, and it avoids the boundary-burst problem where a client fires twice its quota by timing requests around a window edge.
The trade-off is durability: sorted sets live in memory, so a rate limiter or leaderboard built this way needs Redis persistence (AOF or RDB) configured deliberately, or a cache-miss simply resets state that a relational table would have preserved by default.
SQLite WASM: An embedded database inside the browser
SQLite WASM runs a full relational database inside the browser tab, compiled to WebAssembly and backed by the Origin Private File System, so a web app gets ACID transactions, indexes, and SQL joins without a server round trip. That matters for offline-first tools, local-first note apps, and any client that needs to query thousands of rows faster than IndexedDB's key-value model allows.
We've used SQLite WASM for browser-side analytics scratchpads, where a user filters and aggregates a dataset already downloaded to the client. Running the aggregation as SQL against an in-browser table beats hand-rolled JavaScript reducers once the row count climbs past a few thousand, and it keeps the query logic identical to the server-side Postgres version, easing the mental model for the team.
The catch is persistence and concurrency. The OPFS backend is single-writer per tab, so multi-tab sync needs a coordination layer (a SharedWorker or the browser's BroadcastChannel), and data lives in that browser's storage until the user clears it or the origin's quota evicts it.
This is a different failure mode than CouchDB's offline sync, which replicates changes across devices through a defined protocol rather than trusting local storage to persist.
SQLite WASM fits three cases well: prototyping a query-heavy UI without standing up a backend, running a local cache that mirrors a server schema for offline reads, and processing sensitive data client-side so it never leaves the browser.
It's a poor fit for anything needing durable cross-device state, that's what CouchDB or a syncable backend is for. The SQLite WASM canonical release ships at roughly 800kb uncompressed; the barebones build comes in at 605kb, and gzipped that drops to around 530kb (SQLite User Forum, 2024).
On mobile, that same embedded pattern shows up in libraries like GRDB.swift, a SQLite-backed mobile database layer for iOS apps that need local queries without a server round trip.
TimescaleDB for time-series and IoT metrics
TimescaleDB extends PostgreSQL with automatic time-based partitioning and native columnar compression, making it the default choice for IoT metrics and time-series workloads that need SQL joins alongside high write volume. It is a Postgres extension, not a new engine, so existing tooling, replication, and ORM drivers keep working.
The mechanism is hypertables: TimescaleDB chunks data by time interval automatically, then compresses older chunks into columnar storage in the background.
TimescaleDB achieves up to 98% compression ratio on time-series data using columnar storage, according to TimescaleDB's own engineering writeup. On the write side, third-party benchmarks put its sustained insert rate at roughly 111K rows/sec through a billion-row table (Timescale, via Medium). Both numbers are worth pulling before sizing a cluster, since both vary heavily with chunk interval and column cardinality.
We've seen teams migrate off a hand-rolled sharded Postgres setup for sensor telemetry once index bloat on a single wide table made vacuum runs unpredictable. Continuous aggregates then replace a separate rollup job, refreshing hourly averages incrementally instead of rescanning raw rows on every dashboard query.
The limitation shows up at very high cardinality: millions of distinct device tags per chunk erode the compression benefit and push query planning time up, which is where a dedicated store like InfluxDB or a wide-column database still wins on ingest-only workloads.
TimescaleDB is the right call when the same data needs both time-series rollups and relational joins against device metadata. It consistently ranks among the top few systems by adoption on DB-Engines' time-series DBMS ranking.
Using a database as a feature flag store
A single PostgreSQL table with a JSONB column can replace a feature flag service for teams running fewer than a few hundred flags. It stores flag state, targeting rules, and rollout percentages in one queryable structure.
Index the JSONB column with a GIN index and PostgreSQL evaluates flag lookups in a single query even as the table grows into millions of rows, according to PostgreSQL's official documentation on JSON indexing. That avoids the network round trip a separate flag service would otherwise add to every request.
A minimal schema and lookup query show the pattern in practice:
CREATE TABLE feature_flags (
key TEXT PRIMARY KEY,
config JSONB NOT NULL
);
CREATE INDEX idx_flags_config ON feature_flags USING GIN (config);
-- Evaluate a flag for a given user cohort
SELECT config->'enabled' AS enabled,
config->'rollout_percentage' AS rollout
FROM feature_flags
WHERE key = 'new_checkout_flow'
AND (config->'targeting'->'cohorts') ? 'beta_users';
That query runs inside the same transaction as the write it's gating, so a flag check never depends on an external service being reachable.
We've built this pattern for clients who needed flag evaluation inside the same transaction as the write it was gating — including a 24/7 autonomous retail deployment where a flag check timing out was not an acceptable failure mode.
The build-versus-buy line is not really about flag count. It's about who owns the rollout logic.
| Approach | Best for | Limitation |
|---|---|---|
| PostgreSQL JSONB table | Single team, in-transaction evaluation, simple percentage/cohort rules | No SDK, no audit UI, no built-in gradual rollout math |
| LaunchDarkly | Multi-team orgs, compliance audit trails, real-time streaming updates | Per-seat and per-flag pricing that scales with headcount |
Our rule of thumb: stay on JSONB while one team owns the flags and rollout logic stays percentage-or-cohort simple. Move to LaunchDarkly once flag ownership crosses team boundaries and product managers need self-service without a deploy.
CouchDB offline sync and document-store tricks
CouchDB offline sync solves a problem Firestore and DynamoDB solve poorly out of the box: letting a mobile or field app write locally for hours or days, then reconcile with the server without a custom conflict-resolution layer.
CouchDB's replication protocol treats every node, mobile or server, as a peer, using MVCC revision trees to detect and surface conflicts rather than silently overwriting them. The practical pattern is PouchDB on the client talking to CouchDB (or Cloudant) on the server, syncing over HTTP with incremental checkpoints. We've seen this used in field-service and clinical-data apps where connectivity is intermittent by design, not an edge case to paper over.
Sync mechanics are straightforward to wire up. A live, bidirectional replication looks like this:
const localDB = new PouchDB('local_orders');
const remoteDB = new PouchDB('https://user:pass@cloudant-host/orders');
localDB.sync(remoteDB, { live: true, retry: true })
.on('change', info => console.log('synced', info))
.on('conflict', info => console.log('conflict on doc', info.doc._id));
Each document carries a _rev field. When two clients edit offline and reconnect, CouchDB stores both revisions in a conflict branch instead of picking a winner automatically. Resolving it is explicit, not magic:
db.get(docId, { conflicts: true }).then(doc => {
const losers = doc._conflicts || [];
losers.forEach(rev => db.remove(docId, rev));
});
On typical field connections (think 3G or spotty Wi-Fi), checkpointed sync batches keep round-trip overhead low compared to a full-document push, because only the changed revisions since the last checkpoint transfer.
Firestore offers offline persistence too, but conflict resolution is last-write-wins by default, which quietly loses data in multi-writer scenarios. DynamoDB has no native offline story at all, it assumes a connected client and pushes sync logic entirely onto the application layer.
MongoDB sits in a different niche here: its geospatial queries ($geoWithin, $near with 2dsphere indexes) make it a strong pick when the unconventional need is location-aware querying rather than offline-first sync, something neither CouchDB nor DynamoDB handles natively.
| Database | Unusual use | Why it works | Limitation |
|---|---|---|---|
| CouchDB | Offline-first sync | Peer-to-peer MVCC replication, real conflict surfacing | Slower ad hoc queries than a relational store |
| MongoDB | Geospatial search | Native 2dsphere indexing, no external GIS engine | Index maintenance cost grows with write volume |
| Firestore | Managed offline cache | Zero-ops client SDK | Last-write-wins conflict handling |
| DynamoDB | High-throughput key access | Predictable latency at scale | No offline mode, no geospatial index |
Pick CouchDB when conflict visibility matters more than query flexibility.
FAQ: Unconventional database use cases
Can MongoDB be used like a relational database?
Can pgvector replace a dedicated vector database?
Can I use postgres as a message queue?
Do I still need Elasticsearch if I use postgres full-text search?
What is the best non-relational database for full-stack development?
How do I use a database as a feature flag store?
Choosing the right repurposed database for your stack
Match the access pattern first, not the brand name.
If queries are similarity searches over embeddings, pgvector on Postgres beats standing up a dedicated vector database for most teams under 10M rows. If the data is timestamped and queried by range, TimescaleDB's hypertables and compression outperform a general-purpose table with a manual partitioning scheme.
| Database | Unusual use | Why it works | Limitation |
|---|---|---|---|
| pgvector | Vector embeddings search | HNSW index avoids a separate vector store | Recall drops against purpose-built ANN engines at very high dimensionality; past 10M vectors, the HNSW index itself (60-70GB for 10M vectors at 1536 dimensions) can exceed available shared_buffers (Tensoria: Pinecone vs Qdrant vs Weaviate vs pgvector) |
| TimescaleDB | Time-series analytics | Hypertables auto-partition by time, compression cuts storage | Not built for high-concurrency random-write OLTP |
| Postgres SKIP LOCKED | Job queue | Skips locked rows, no broker to operate | Throughput ceiling below Kafka at very high message volume |
| Redis sorted sets | Leaderboards, rate limiting | O(log N) rank operations | Durability is a tradeoff, not a guarantee |
The decision rule we use: if your team already runs Postgres, exhaust its extensions (pgvector, tsvector, SKIP LOCKED) before adding infrastructure.
Reach for Kafka, a dedicated vector store, or a message broker only once volume or delivery guarantees genuinely outgrow what a single well-indexed database provides.
This kind of pragmatic, extension-first thinking matters even more when building a SaaS product, where infrastructure choices directly affect your cost model and time to market.
If you are weighing whether your current stack can absorb AI features without a rewrite, our AI, Data & Engagement team can audit the fit and help you add AI to your product without over-provisioning infrastructure you don't need yet.
If your data volumes have outgrown these single-database tricks and you're evaluating a dedicated cloud data warehouse, our Snowflake development services can help you plan and execute that migration.
