PlanetScale just dropped TIN, a Postgres extension for full-text search that claims to leave ParadeDB, pg_textsearch, and built-in GIN in the dust. The numbers are eye-popping: 25× throughput on mixed queries, 541× over GIN on conjunctions, 57× better write concurrency. If true, this is the first Postgres-native search index that doesn't force you to choose between ACID compliance and not timing out.
But every benchmark in the announcement comes from PlanetScale's own fork of ParadeDB's benchmarking tool, run on hardware they provisioned, with parameters they tuned. No independent reproduction. No third-party validation. Not even a Jepsen report. Before you rewrite your search layer, here's what the vendor isn't emphasizing.
The Reality: ctid as the Secret Sauce
TIN's architectural bet is using Postgres' ctid — the physical tuple identifier (block, offset) — as its native document ID instead of assigning sequential integers per segment [1]. Most search indexes (including ParadeDB and pg_textsearch) map logical doc IDs to ctid at query time, requiring a lookup for every match. TIN skips that hop entirely because Postgres index APIs already return ctid [1].
This enables two-level bitmap compression: a dense page-level bitmap (256 bits, fits in AVX2) and tiny per-page offset bitmaps. Common terms approach 1 bit per posting; rare terms ~25 bits [1]. Vectorized AND/OR on page bitmaps means conjunction/disjunction queries become register operations. POPCNT handles counts. Matched ctids are already in heap order, so Postgres fetches sequentially — a free I/O win on NVMe [1].
MVCC correctness comes from intersecting page bitmaps against Postgres' visibility map, only hitting the heap for not-all-visible pages. A per-segment liveness bitmap handles VACUUM'd tuples [1]. Segment merges don't renumber documents — ctid (190,17) means the same thing everywhere — so merging transfers bitmap ownership without recompression [1].
It's clever systems engineering. It's also untested outside PlanetScale's benchmark harness.
The Pain Point: You're Tired of ElasticSearch Bills and ParadeDB Lock-in
Teams running Postgres have three bad options today: built-in tsvector/tsquery (slow, limited query DSL), ParadeDB (fast but a separate Rust extension with its own operational surface), or shipping data to ElasticSearch/OpenSearch (cost, sync lag, dual-write risk). TIN promises to keep search inside Postgres with BM25 ranking, phrase/wildcard/fuzzy queries, and COUNT(*) support — all transactionally consistent [2].
The benchmark corpus: 85 GB Stack Exchange export, 150M documents, 1,719 synthetic queries sampled from 2-15 term substrings [2]. Test environment: AWS i7i.8xlarge, 8 vCPUs, 32 GB RAM, Postgres 18.6 in containers [2]. Index build: TIN 8m10s (50.7 GB, 32 GB RAM) vs ParadeDB 19m20s (64 GB RAM) vs pg_textsearch 26m49s (128 GB RAM) vs GIN 2h09m (64 GB RAM) [2].
Read-only mixed queries: TIN 199 QPS, p99 256ms. ParadeDB 7.9 QPS, p99 6.7s. GIN and pg_textsearch failed [2]. With 1K updates/sec: TIN 172 QPS, 271K updates completed.
ParadeDB 6 QPS, 193K updates. pg_textsearch stalled at 735 updates [2]. Wikipedia corpus (8 GB, fits in RAM): TIN 10,260 QPS on COUNT(*) disjunctions. ParadeDB 291.
GIN 1.4 [2].
These are real numbers from a real benchmark tool. They're also exactly what the vendor wants you to see.
Failure Modes: What the Benchmarks Don't Show
First, the benchmarker is ParadeDB's own tool, forked by PlanetScale to add pre-warming and byte/WAL metrics [2]. ParadeDB has incentive to make their own tool favor their engine; PlanetScale has incentive to tune it for TIN. The parameter changes — max_parallel_workers=8, shared_buffers=24GB, maintenance_work_mem=24GB — are reasonable but hand-picked [2]. No default-config runs shown.
Second, the workload is synthetic. Substrings sampled from the corpus, interpreted three ways. No real query logs. No user sessions.
No mixed read/write patterns beyond a constant 1K updates/sec. No vacuum pressure, no long-running transactions, no replication lag scenarios.
Third, TIN v1.0.2 is GA but brand new. No production hardening stories. No upgrade/downgrade path documented. No story for cross-version Postgres compatibility (tested on 18.6 only).
The extension API surface is small — CREATE EXTENSION tin; CREATE INDEX ... USING tin(col); WHERE col ==> 'terms' — but that's also the entire escape hatch if it breaks [2].
Fourth, the RAM requirements for competitors' index builds (64-128 GB) were relaxed only for build phase, then reset to 32 GB for queries [2]. That's fair for comparing query performance, but it hides the operational reality: pg_textsearch needed 128 GB just to build. If your CI/CD or staging environment can't spare that, you're not evaluating it fairly.
Fifth, no durability or crash-recovery benchmarks. WAL bytes written are measured but not analyzed. How does TIN behave after unclean shutdown? How long does recovery take? The blog doesn't say.
The Blueprint: Evaluate Before You Migrate
Week 1: Reproduce the benchmark yourself. The ParadeDB Benchmarker is open source [3]. PlanetScale's fork is on GitHub [2]. Spin up an i7i.8xlarge (or your actual instance class), load your data corpus, run the suite. Compare TIN against your current stack — not just ParadeDB.
Test your actual query mix: phrase, fuzzy, wildcard, regex. Measure build time, index size, vacuum impact, replication lag.
Week 2: Stress the MVCC edge cases. Run long-running REPEATABLE READ transactions while TIN indexes update. Verify COUNT(*) accuracy under concurrent deletes. Check visibility map intersection correctness with pg_visibility extension. This is where ctid-based designs either shine or subtly corrupt.
Week 3: Operational dry run. Simulate primary failover. Measure replica catch-up time with TIN indexes. Test pg_dump/pg_restore round-trip. Verify logical replication (if you use it) doesn't choke on TIN's internal catalogs. Document the rollback plan: DROP INDEX; DROP EXTENSION; — but know how long re-indexing takes on your data volume.
Decision gate: If TIN matches within 2× of claimed QPS on your queries with your concurrency profile, and recovery/upgrade drills pass, pilot it on a read replica for non-critical search paths.
Sources
- Introducing TIN: full-text search for Postgres — PlanetScale Blog
- [PostgreSQL 18 Documentation: Chapter
- Full Text Search](https://www.postgresql.org/docs/current/textsearch-intro.html)
- Poking around PostgreSQL full-text search: a beginners primer — Remi Mercier
- ParadeDB Benchmarker — GitHub



