
How to Protect Your Postgres Indexes From OS-Level glibc Updates Without Manual Reindexing
Stop worrying about silent index corruption and learn how to leverage immutable ICU collations to safeguard your Postgres database against platform updates.
How to Protect Your Postgres Indexes From OS-Level glibc Updates Without Manual Reindexing
Your database is lying to you, and it’s likely your operating system's fault. You could run a query for a record you *know* exists, and Postgres will shrug its shoulders and return nothing, even though the data is sitting right there in the table. This isn't a ghost in the machine; it’s the silent, creeping rot of index corruption caused by something as mundane as a standard OS package update.
If you’ve ever upgraded a Debian or Ubuntu server and suddenly found your B-tree indexes behaving like they’ve had one too many drinks, you’ve hit the glibc collation trap.
The "Silent Killer": Why glibc Updates Break Indexes
Postgres doesn't actually know how to sort text. When you define a column as TEXT or VARCHAR with a locale like en_US.UTF-8, Postgres outsources the sorting logic to the C library of the host OS—usually glibc.
The problem? glibc changes its mind.
Between versions (say, glibc 2.28 to 2.31), the maintainers might decide that a "space" should be sorted differently, or that a specific emoji belongs elsewhere. When glibc updates, the sort order changes. But your B-tree index—which was built using the *old* sort order—is now physically ordered incorrectly. Postgres traverses the tree, expects a value to be on the "left" branch, doesn't find it, and gives up.
I’ve seen teams lose entire weekends to REINDEX DATABASE commands because a routine apt-get upgrade bumped the system's glibc version.
The Old Way: The "Reindex and Pray" Strategy
Until recently, the only "fix" was to monitor for library updates and manually rebuild every index. It’s tedious, resource-intensive, and leaves a window where your data is effectively invisible.
-- The "Oh No" command you're trying to avoid
REINDEX INDEX CONCURRENTLY idx_users_on_last_name;Doing this on a multi-terabyte table is about as fun as eating a bowl of thumb-tacks. We need a way to make our indexes immutable and independent of the OS’s whims.
The Solution: ICU Collations
Postgres 10 introduced support for ICU (International Components for Unicode) collations. Unlike the default libc provider, ICU is much more stable and, starting with Postgres 15, allows us to track versions.
By using ICU, we can decouple our database's sorting logic from the operating system's glibc.
Step 1: Create an ICU-based Collation
First, you need to make sure your system has the ICU library installed (it almost certainly does). Then, you define a collation in Postgres specifically using the icu provider.
CREATE COLLATION nd_icu (
provider = 'icu',
locale = 'en-US-u-kn-true', -- 'kn-true' for natural numeric sorting!
deterministic = false
);By explicitly naming our provider as icu, we stop relying on the default system locale.
Step 2: Use the Collation in Your Schema
You can apply this to specific columns or entire tables. I prefer being explicit at the column level for critical search fields.
CREATE TABLE customers (
id SERIAL PRIMARY KEY,
last_name TEXT COLLATE "nd_icu",
email TEXT
);
CREATE INDEX idx_customers_last_name ON customers (last_name);Now, even if glibc gets an update, the idx_customers_last_name index remains stable because it's looking at the ICU provider, not the system's C library.
The Postgres 15+ Superpower: Version Tracking
Postgres 15 introduced a feature that finally treats this problem like the first-class citizen it is. It now records the version of the collation library when an index is created.
If the library updates, Postgres will notice the version mismatch and warn you, rather than just silently failing.
-- Check if any of your indexes are out of sync with the library version
SELECT
indrelid::regclass AS table_name,
indexrelid::regclass AS index_name,
collname,
indexcollversion,
current_version
FROM pg_index
JOIN pg_collation ON pg_index.indcollation = pg_collation.oid
CROSS JOIN LATERAL pg_collation_actual_version(pg_collation.oid) AS current_version
WHERE indexcollversion <> current_version;This doesn't *prevent* the need for a reindex, but it prevents the corruption. It’s the difference between a "Check Engine" light and your car exploding on the highway.
The Holy Grail: Postgres 17 and the builtin Provider
If you are lucky enough to be running (or moving to) Postgres 17, the game has changed entirely. Postgres 17 introduced immutable, built-in collations.
This is the ultimate "set it and forget it" solution. Postgres now ships with its own sorting logic that is guaranteed to never change, regardless of OS updates or library changes.
To use it, you use the builtin provider:
CREATE COLLATION my_stable_sort (
provider = 'builtin',
locale = 'en_US'
);When you use the builtin provider, the sort order is hard-coded into the Postgres binary itself. You could move your data files from a Debian server to a RedHat server to a Windows machine, and the index would remain perfectly valid. No reindexing. No corruption. No headaches.
Migrating Without the Pain
If you're already stuck with a libc collation and want to move to icu or builtin to avoid future glibc drama, you can't just ALTER the column and call it a day. You need to do it safely.
1. Create a new collation (as shown above).
2. Add a new index using that collation concurrently.
3. Validate that the new index works.
4. Drop the old index.
-- 1. Create the stable index
CREATE INDEX CONCURRENTLY idx_customers_last_name_stable
ON customers (last_name COLLATE "nd_icu");
-- 2. Drop the old, fragile index
DROP INDEX CONCURRENTLY idx_customers_last_name;Why Should You Care?
It’s easy to ignore this until it hits you. But look at it this way: OS updates are usually handled by sysadmins or automated scripts. Database indexes are handled by DBAs or Developers. When these two worlds collide via a glibc update, the person who gets blamed is usually the dev whose "query is broken."
By switching to ICU (on PG 15/16) or the Builtin Provider (on PG 17), you are effectively future-proofing your data integrity. You are making your database portable, resilient, and—most importantly—predictable.
Stop letting apt upgrade dictate your weekend plans. Go immutable.


