Späť na blog

ICU Collation Version Drift: Keď Upgrade Databázy Rozbije Tvoje Indexy

|
| postgresql, debugging, unicode, indexes, icu

Bugy v sortovani su najhorsie, lebo sa skryvaju na prvy pohlad. “Rovnaký query, rozdielne výsledky po upgrade z Ubuntu 20.04 na 22.04.” Príčina: ICU library verzia sa zmenila, pravidlá porovnávania stringov sa mierne posunuli a tvoje btree indexy sú teraz corrupt (nesprávne zoradené) pre novú collation verziu.

Prostredie: PostgreSQL 14+, ICU collation (nie libc), OS alebo container base image upgrade

Problém

Tichá Korupcia Dát

-- Pred OS upgrade (ICU 67):
SELECT name FROM users WHERE name > 'Müller' ORDER BY name LIMIT 5;
-- Vracia: Muñoz, Murphy, Murray, Myers, ...

-- Po OS upgrade (ICU 72):
SELECT name FROM users WHERE name > 'Müller' ORDER BY name LIMIT 5;
-- Vracia: Murphy, Murray, Muñoz, Myers, ...  -- Iné poradie!

-- Alebo horšie, index scan vracia nesprávne výsledky:
SELECT * FROM users WHERE name = 'Müller';
-- Vracia 0 riadkov!  (ale dáta TAM SÚ)

-- Seq scan to nájde:
SET enable_indexscan = off;
SELECT * FROM users WHERE name = 'Müller';
-- Vracia očakávaný riadok

Čo Sa Zmenilo?

ICU collation určuje poradie triedenia stringov.
Rôzne ICU verzie majú rôzne pravidlá:

ICU 67 (Ubuntu 20.04):
  'ü' sa triedi ako: ue ekvivalent
  'Müller' < 'Muñoz'

ICU 72 (Ubuntu 22.04):
  'ü' sa triedi ako: u + kombinujúci umlaut
  'Muñoz' < 'Müller'

Tvoj btree index bol postavený s ICU 67 poradím triedenia.
Po upgrade PostgreSQL používa ICU 72 pre porovnania.
Index binárne vyhľadávanie používa nové pravidlá na dátach
zoradených starými pravidlami.
Výsledok: Index vracia nesprávne riadky alebo minie platné zhody!

Príčina

Ako Btree Indexy Fungujú

Btree index na 'name' stĺpci (zoradený podľa ICU 67):

      [Mueller]
      /       \
   [Muller]  [Murphy]
   /    \        \
[Miller][Müller][Murray]

Binárne vyhľadávanie s ICU 72:
Hľadám 'Müller' > 'Muñoz'?
  - ICU 72 hovorí: Müller > Muñoz (rozdielne od ICU 67!)
  - Ide nesprávnou vetvou v strome
  - Minie skutočný 'Müller' záznam

Index je štrukturálne validný ale sémanticky corrupt.

PostgreSQL Collation Versioning

-- PostgreSQL 14+ sleduje collation verzie
SELECT collname, collversion, collprovider
FROM pg_collation WHERE collname = 'en-US-x-icu';

-- collname   | collversion | collprovider
-- en-US-x-icu| 67.1       | i

-- Po ICU upgrade sa detekuje mismatch:
-- WARNING: collation "en-US-x-icu" has version mismatch
-- DETAIL: The collation in the database was created using version 67.1,
-- but the operating system provides version 72.1.
-- HINT: Rebuild affected indexes with REINDEX.

-- Ale warning sa objaví len keď sa collation POUŽÍVA
-- Nie proaktívne pri štarte!

Diagnostika

Skontroluj Collation Version Mismatche

-- Nájdi všetky collations s version mismatchmi
SELECT
    c.collname,
    c.collversion as db_version,
    pg_collation_actual_version(c.oid) as os_version,
    CASE
        WHEN c.collversion != pg_collation_actual_version(c.oid)
        THEN 'MISMATCH - TREBA REINDEX'
        ELSE 'OK'
    END as status
FROM pg_collation c
WHERE c.collprovider = 'i'  -- Len ICU collations
AND c.collversion IS NOT NULL;

Nájdi Ovplyvnené Indexy

-- Nájdi indexy používajúce ICU collations (možno treba rebuild)
SELECT
    n.nspname as schema,
    c.relname as index_name,
    t.relname as table_name,
    a.attname as column_name,
    coll.collname as collation,
    coll.collversion as indexed_version,
    pg_collation_actual_version(coll.oid) as current_version
FROM pg_index i
JOIN pg_class c ON c.oid = i.indexrelid
JOIN pg_class t ON t.oid = i.indrelid
JOIN pg_namespace n ON n.oid = c.relnamespace
JOIN pg_attribute a ON a.attrelid = t.oid
JOIN pg_collation coll ON coll.oid = a.attcollation
WHERE coll.collprovider = 'i'
AND coll.collversion != pg_collation_actual_version(coll.oid);

Over Index Korupciu

-- Porovnaj index scan vs seq scan výsledky
SET enable_indexscan = on;
SET enable_seqscan = off;
SELECT count(*) FROM users WHERE name LIKE 'M%';  -- Používa index
-- Vracia: 1523

SET enable_indexscan = off;
SET enable_seqscan = on;
SELECT count(*) FROM users WHERE name LIKE 'M%';  -- Full scan
-- Vracia: 1547  -- Rozdielne! Index je corrupt

-- Skontroluj validitu indexu s amcheck
CREATE EXTENSION IF NOT EXISTS amcheck;
SELECT bt_index_check('users_name_idx');
-- Možno prejde! Index je štrukturálne validný, len sémanticky nesprávny

Riešenie

Možnosť 1: Reindexuj Ovplyvnené Indexy

-- Reindexuj konkrétny index
REINDEX INDEX CONCURRENTLY users_name_idx;

-- Reindexuj všetky indexy na tabuľke
REINDEX TABLE CONCURRENTLY users;

-- Reindexuj celú databázu (opatrne - trvá dlho!)
REINDEX DATABASE CONCURRENTLY mydb;

-- Po reindexe updatni uloženú collation verziu
ALTER COLLATION "en-US-x-icu" REFRESH VERSION;

Možnosť 2: Naplánovaný Reindex Po Upgrades

#!/bin/bash
# Spusti po OS upgrades ktoré menia ICU

# Skontroluj mismatche
psql -c "
SELECT collname FROM pg_collation
WHERE collprovider = 'i'
AND collversion != pg_collation_actual_version(oid);
" | grep -q 'en-US-x-icu' && {
    echo "Detekovaný ICU version mismatch, reindexujem..."
    psql -c "REINDEX DATABASE CONCURRENTLY mydb;"
    psql -c "ALTER COLLATION \"en-US-x-icu\" REFRESH VERSION;"
}

Možnosť 3: Pripni ICU Verziu v Containeri

# Dockerfile: Pripni presnú ICU verziu
FROM postgres:15

# Nainštaluj konkrétnu ICU verziu
RUN apt-get update && apt-get install -y \
    libicu67 \
    && rm -rf /var/lib/apt/lists/*

# Alebo použi distroless/immutable base ktorý sa nezmení

Možnosť 4: Použi Deterministickú Collation

-- Vytvor deterministickú collation (žiadne locale-špecifické pravidlá)
CREATE COLLATION "en_US_posix" (provider = libc, locale = 'POSIX');

-- Alebo použi C collation (byte-order porovnanie)
CREATE INDEX users_name_c_idx ON users (name COLLATE "C");

-- Nevýhoda: Menej lingvisticky korektné triedenie
-- Výhoda: Nikdy sa nemení medzi verziami

Prevencia

Pre-Upgrade Checklist

#!/bin/bash
# Spusti PRED OS upgrade

echo "=== ICU Collation Verzie Pred Upgrade ==="
psql -c "
SELECT collname, collversion, pg_collation_actual_version(oid)
FROM pg_collation WHERE collprovider = 'i';"

echo "=== Indexy Používajúce ICU Collations ==="
psql -c "
SELECT c.relname as index_name, t.relname as table_name
FROM pg_index i
JOIN pg_class c ON c.oid = i.indexrelid
JOIN pg_class t ON t.oid = i.indrelid
JOIN pg_attribute a ON a.attrelid = t.oid
JOIN pg_collation coll ON coll.oid = a.attcollation
WHERE coll.collprovider = 'i';"

echo "=== Ulož pre post-upgrade verifikáciu ==="

Checklist

## ICU Collation Version Drift

### Symptómy
- [ ] Queries vracajú rozdielne výsledky po OS upgrade
- [ ] Index scany minia riadky ktoré seq scany nájdu
- [ ] LIKE/comparison queries sa správajú neočakávane
- [ ] "collation version mismatch" warningy v logoch

### Diagnostika
- [ ] Skontroluj pg_collation pre version mismatche
- [ ] Identifikuj indexy používajúce ICU collations
- [ ] Porovnaj index scan vs seq scan výsledky
- [ ] Over zmenu ICU library verzie

### Riešenia
- [ ] REINDEX ovplyvnené indexy
- [ ] ALTER COLLATION ... REFRESH VERSION
- [ ] Zváž C alebo POSIX collation pre stabilitu
- [ ] Pripni ICU verziu v container images

### Prevencia
- [ ] Dokumentuj ICU verzie pred upgrades
- [ ] Zahrň reindex do OS upgrade runbooku
- [ ] Monitoruj collation version warningy
- [ ] Testuj queries po upgrades

Záver

Lekcia: Unicode je komplikovaný a “upgrade OS” môže ticho poškodiť tvoje indexy. PostgreSQL 14+ na toto upozorňuje, ale len keď použiješ collation - nie proaktívne.

Kľúčové princípy:

  1. Kontroluj collation verzie pred a po OS upgrades
  2. REINDEX po ICU library updates
  3. Použi deterministickú collation pre version-stabilné indexy
  4. Pripni závislosti v kontajneroch aby si sa vyhol prekvapivým zmenám

Súvisiace články

Súvisiace články

Citujte tento článok

Ak na článok odkazujete, pridajte pôvodnú URL a uveďte autora.

Michal Drozd. "ICU Collation Version Drift: Keď Upgrade Databázy Rozbije Tvoje Indexy". https://www.michal-drozd.com/sk/blog/icu-collation-version-drift/ (Publikované 15. marca 2025).