Dark mode switch icon Light mode switch icon

Обновление glibc и сломанные индексы PotgreSQL

• 2 минут чтобы прочитать

Если вы используете PosgtreSQL в качестве СУБД, то не спешите обновлять ваши сервера: можете получить на ровном месте очень нетривиальные к отслеживанию ошибки.

Обновление glibc

Один из самых важных компонент при обновлении ОС это пакет glibc, который используется почти всем софтом в системе.

1 Августа 2018 года вышла версия 2.28, в которой среди крупных изменений числится:

The localization data for ISO 14651 is updated to match the 2016 Edition 4 release of the standard, this matches data provided by Unicode 9.0.0. This update introduces significant improvements to the collation of Unicode characters. This release deviates slightly from the standard in that the collation element ordering for lowercase and uppercase LATIN script characters is adjusted to ensure that regular expressions with ranges like [a-z] and [A-Z] don’t interleave e.g. A is not matched by [a-z]. With the update many locales have been updated to take advantage of the new collation information. The new collation information has increased the size of the compiled locale archive or binary locales.

Изменились порядок сортировки строк и особенности классификации некоторых символов, что соответственно напрямую отразилось на работе PostgreSQL с поддержкой правил сортировки строк (COLLATIONS).

Примеры сортировки в PostgreSQL с разными версиями (g)libc

Найти и проверить одинаковые версии PostgreSQL на дистрибутивах с разными версиями glibc мне не удалось, однако glibc это лишь реализация Standard C library. Другой реализацией является musl, используемый в дистрибутивах Alpine в Docker. Давайте проверим, отличаются ли реализации порядка сортировки между glibc и libc.

Создадим composer.yaml:

version: '3.9'

services:
  db_1:
    image: postgres:16.3
    user: postgres
    environment:
      POSTGRES_PASSWORD: postgres
      POSTGRES_USER: postgres
  db_2:
    image: postgres:16.3-alpine
    user: postgres
    environment:
      POSTGRES_PASSWORD: postgres
      POSTGRES_USER: postgres

и поднимем контейнеры с помощью

docker compose up -d --force-recreate --renew-anon-volumes

и запустим psql в одном из них (для подключения к другому просто замените db_1 на db_2)

docker compose exec -ti db_1 psql

Теперь сделаем запросы и сравним результаты. Вариант сортировки в PostgeSQL, в дистрибутиве Debian:

postgres=# SELECT name FROM unnest(ARRAY[
    'а', 'д', 'е', 'ё', 'ж', 'я'
]) name ORDER BY name;
 name 
 ------
 а
 д
 е
 ё
 ж
 я
(6 rows)

Вариант сортровки в PostgreSQL, в дистрибутиве Alpine:

postgres=# SELECT name FROM unnest(ARRAY[
    'а', 'д', 'е', 'ё', 'ж', 'я'
]) name ORDER BY name COLLATE "C";
 name 
 ------
 а
 д
 е
 ж
 я
 ё
(6 rows)

Как видим, сортровка строк отличается в разных дистрибутивах с разными версиями стандартной библиотеки. Это только цветочки - на самом деле роль играет и сортировка строк установленная по умолчанию в системе, и сам алгоритм в версии стандартной библиотеки. Проблема однако гораздо серъёзнее, чем просто буква “ё” съехавшая в конец списка телефонной книги. Сортировка строк влияет как на запросы типа SELECT ... ORDER BY ..., так и на b-tree индексы на текстовых полях. Это значит, что существующие индексы перестают работать корректно: кроме снижения скорости запросов мы также можем получить неконсистентность данных, т.к. уникальность в таблицах реализована через UNIQUE INDEX.

Именно с этим мы и столкнулись - обновление серверов СУБД прошло корректно и без ошибок, однако мы заметили появившиеся ошибки вида Duplicate key violation. И действительно, при наличии UNIQUE INDEX данные всё же дублировались, причём далеко не все запросы показывали наличие дублирования!

Что делать?

Очевидное решение: ребилд индексов, затрагивающих текстовые поля. Сделать это нужно после того как репликация и синхронизация данных со старого сервера на новый прошла, но до того как пускать трафик на новый сервер.

А если поймали ошибки с дублированием строк? Очевидно, при перестроении индекса вы получите ошибку, т.к. фактически данные у вас дублируются. Тогда придётся сначала решить эту проблему, избавившись от дубликатов данных. Если трафика много, то лучшим решением будет полностью остановить трафик на БД и сначала решить проблему с данными, а затем уже пересобирать индекс. Будьте готовы к такому исходу, когда придётся полностью откатиться к последнему бэкапу (вы же делаете бэкапы?).

Другой варант решения — добавить уникальный префикс/суффикс к строковым значениям в дублирующихся записях, что позволит быстро решить проблему, пересобрать индекс и дать системе работать дальше. В этом случае вам всё равно придётся разбираться с последствиями: скорее всего в других таблицах уже появились данные, ссылающиеся на дубликаты, поэтому просто так и удалить не получится, но по крайней мере вы выиграете время на приведение системы в порядок.

Как предотвратить?

Отработать процесс обновления БД

Учитывать подобный риск и проговаривать его с командой поддержки инфраструктуры

Не обновлять glibc

Доволно радикальный подход, который может означать отказ от обновления системы вообще. Такое подходит далеко не всем, но стоит держать его как один из действенных способов избежать проблем.

Перестать зависеть от glibc

Переключить PostgreSQL на ICU для работы с сортировкой строк. В этом случае вы будете также зависеть от сторонней библиотеки (libicu), но её обновление не требуется при обновлении всей системы.

Впервые опубликовано от Эдуард Сухарев