Наиболее эффективный способ выполнения крупномасштабной замены JSONB в postgres

#postgresql #jsonb

Вопрос:

У меня есть почти 2 миллиона записей, в которых требуется простая замена строки, выполненная для значения в массиве идентификаторов чего-то вроде этой частичной записи

     {
      "id": "a851375d-f6a0-4fba-ba69-c584901de73c",
      "source": "MARC",
      "title": "Voice Content and Usability [electronic resource] / So, Preston.",
      "identifiers": [
        {
          "value": "9781952616020",
          "identifierTypeId": "fcca2643-406a-482a-b760-7a7f8aec640e"
        },
        {
          "value": "(CaSebORM)9781098128852",
          "identifierTypeId": "7e591197-f335-4afb-bc6d-a6d76ca3bace"
        },
        {
          "value": "5429852",
          "identifierTypeId": "d5989db8-ddc3-4f06-a465-73e99ec262d3"
        },
        {
          "value": "ocm00000001saf9781098128852",
          "identifierTypeId": "7e591197-f335-4afb-bc6d-a6d76ca3bace"
        }
]
}
 

Мне нужно обнаружить и удалить «ocm0000000» из конечного значения, и я ищу наиболее эффективный способ сделать это. Когда я попытался просто поместить jsonb в строку, выполните простую замену строки, например

 update my.table
set jsonb = REPLACE(jsonb::text, 'ocm0000000', '')::jsonb
 

это потребовало от центрального процессора невероятных усилий и забрало все соединения для нашего кластера RDS, вмешиваясь в другие операции, что сделало необходимым остановить процесс.

Каков наилучший подход для выполнения такого рода замены, когда затрагивается так много строк?

Комментарии:

1. «забрал все связи» — это неправдоподобное утверждение. Инструкции ОБНОВЛЕНИЯ не порождают спонтанно новые соединения. (В настоящее время они даже не используют распараллеливание). Что вы увидели в своем мониторинге, что заставляет вас так думать?

2. Администраторы базы данных связались со мной, чтобы сообщить мне об этом. Когда я посмотрел на мониторинг, я увидел скачок примерно с 30 операций ввода-вывода до > 50 000 операций ввода-вывода, и другие транзакции в системе для других пользователей не проходили-предположительно, это зависшие соединения. Когда я убил свой PID, произошли другие транзакции, IOPs вернулся в нормальное состояние, и все было как раньше. В любом случае, мне интересно, как лучше всего решить проблему с данными, подобную этой.

Ответ №1:

Ваше текущее ОБНОВЛЕНИЕ перепишет каждую строку, даже те, которые не нужно изменять. Вам следует добавить предложение WHERE, чтобы предотвратить это. Это особенно важно, если вы хотите разбить обновление на пакеты.

 set jsonb = REPLACE(jsonb::text, 'ocm0000000', '')::jsonb
where jsonb::text like '%ocm0000000%'
 

Самый простой способ закончить это-объявить окно обслуживания и просто сделать это или сделать это в нерабочее время. Мне потребовалось менее 2 минут, чтобы обновить 2 миллиона копий вашей записи примера на небольшом ноутбуке, но с SSD-накопителями. Вы сказали, что это было только частично, но я не знаю, насколько больше будет ваш реальный послужной список. Может быть, вы сможете клонировать наш сервер и использовать его, чтобы проверить, сколько времени это займет. Вы также можете временно увеличить количество операций ввода-вывода, чтобы ускорить процесс.

Если вы хотите запускать его пакетами, вы можете добавить предложение where, чтобы ограничить диапазон ctid, который будет обновляться:

 set jsonb = REPLACE(jsonb::text, 'ocm0000000', '')::jsonb 
where jsonb::text like '%ocm0000000%' 
and ctid between '(0,0)' and '(9999,99)'; --first ~80MB.
 

Затем вы подождете, пока система отдышится, а затем повторите, опустив окно CTID ниже. Повторяйте до тех пор, пока весь стол не будет накрыт. Вам нужно будет идти только до тех пор, пока первое число в ctid не станет таким же большим, как текущие страницы в таблице. Затем запустите его еще раз без условия ctid, чтобы убедиться, что ничего не проскользнуло мимо.

Комментарии:

1. Самое ужасное в этом конкретном запросе то, что он затрагивает почти каждую строку, поэтому отсутствие » ГДЕ » является преднамеренным. Но запуск его пакетами-это то, что я искал, и тогда я смогу использовать «ГДЕ», чтобы избежать сканирования всего. Ваш ответ заставляет меня понять, что еще один механизм, который я мог бы использовать, — это временная таблица, в которой перечислены идентификаторы с порядковым номером, чтобы контролировать, сколько из них обрабатывается одновременно

2. Обратите внимание, что сканирование диапазона CTID будет сканировать всю таблицу каждый раз, пока V14 не научится работать лучше. Это не изменит фрагменты за пределами этого региона, но все равно будет их читать. Таким образом, это приведет к тому, что ввод-вывод будет выполняться небольшими партиями, но ввод-вывод для чтения будет выполняться снова и снова, так что может быть еще хуже, чем в оригинале, если проблема заключается не только в вводе-выводе, а во всем вводе-выводе. Возможно, вы могли бы выбрать какой-нибудь индексированный пользовательский столбец (не CTID) для сканирования диапазона, чтобы получить более эффективное чтение.