Разбивка таблицы для пакетного обновления метки времени до метки времени - PullRequest
1 голос
/ 03 октября 2019

Короткая версия

  • Postgres 11.4 развернут на RDS.
  • Существует ли встроенный или простой способ разделения строк в таблице для пакетного обновления?
  • Как только у вас есть схема сегментов, как вы запускаете цикл в SQL для обработки каждого сегмента с небольшой паузой, чтобы сервер мог перевести дыхание?
  • Есть ли необходимость в групповой работе или я беспокоюсь без веской причины?

Подробная версия:

Мы собираем данные для некоторыхвремя, и используя поля timestamptz. Я сделал ошибку, я должен был использовать метку времени. Мы собираем много данных из разных мест, а затем сами вычисляем UTC , прежде чем отправлять данные в Postgres. Насколько я понимаю, данные timestamp и timestamptz - это одни и те же 8 байтов в любом случае, что дает timestamptz магическое (и невидимое) преобразование AT TIME ZONE. Это означает, что данные не отличаются, это то, как Postgres обрабатывает эти данные, что отличается. В нашем случае это означает, что мы облажаемся, помещая данные в Postgres как UTC, а затем снова выводим их на локальный уровень. Данные нашего сервера не имеют единого часового пояса, поэтому мы устанавливаем его в UTC, как это делает Postgres. Чтобы упростить отчетность, аналитические таблицы обычно имеют избыточный столбец для local_dts и utc_dts. Таким образом, мы можем запускать отчеты, в которых сравниваются «утра понедельника с 8 до 11» для разных учреждений в разных часовых поясах. Различные объекты имеют разные часовые пояса, поэтому мы используем «локальное» значение, которое их локальное для таких запросов. Но если нам нужна единая временная шкала, тогда мы используем UTC. Проще говоря: строки в одной и той же таблице могут быть из источников с разными часовыми поясами.

Хорошо, это фон, у меня теперь есть 10 с миллионов строк, которые я собираюсь обновить. Изменения структуры выглядят просто:

-- Change the data type, this is instantaneous.
ALTER TABLE assembly
   ALTER COLUMN created_dts 
   SET DATA TYPE timestamp;

-- Reset the default, it's probably not necessary, but the ::timestamptz is misleading/confusing here otherwise.
ALTER TABLE assembly
   ALTER COLUMN created_dts 
   SET DEFAULT '-infinity'::timestamp

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

Мой вопрос заключается в том, как это сделатьобновление эффективно без перетаскивания сервера? Я представляю, как можно группировать вещи по 5 тысяч строк за раз или тому подобное. Для простоты предположим, что все наши серверы настроены на работу в США и Центральной. Когда мы изначально выдавали данные как UTC, они снова были преобразованы Postgres, поэтому теперь данные отключены из-за смещения времени нашего сервера и UTC. (Я думаю.) Если это так, простейшее обновление может выглядеть так:

SET TIME ZONE 'UTC'; -- Tell Postgres we're in UTC to line up the data with the UTC clock it's set to.
UPDATE analytic_scan 
  SET created_dts = created_dts at time zone 'US/Central' -- Tell Postgres to convert the value back to where we started.

Это похоже на работу (?), Исключая очевидное упущение в работе с летним временем. Я мог бы добавить предложение WHERE, чтобы справиться с этим, но это не меняет моего вопроса. И теперь вопрос, у меня есть количество записей, таких как:

analytic_productivity           728,708
analytic_scan                 4,296,273
analytic_sterilizer_load        136,926
analytic_sterilizer_loadinv     327,700
record_changes_log           17,949,132

Итак, не массово, но не ничего. Есть ли способ разумно нарезать данные в SQL, чтобы

  • Каждая строка обновлялась один раз
  • Ни одна строка не обновлялась более одного раза
  • Не слишком много строкобновляются за один раз

Во всех таблицах есть поле PK UUID ID, у пары есть сгенерированный столбец идентификаторов, подобный фрагменту из этой таблицы отчетов:

CREATE TABLE IF NOT EXISTS "data"."analytic_productivity" (
    "id" uuid NOT NULL DEFAULT NULL,
    "pg_con_id" integer GENERATED BY DEFAULT AS IDENTITY UNIQUE,
    "data_file_id" uuid NOT NULL DEFAULT NULL,
    "start_utc" timestamptz NOT NULL DEFAULT '-infinity',
    "start_local" timestamptz NOT NULL DEFAULT '-infinity',
    "end_utc" timestamptz NOT NULL DEFAULT '-infinity',
    "end_local" timestamptz NOT NULL DEFAULT '-infinity')

У меня была идея использовать подстроку или хеш UUID::text для создания меньших партий:

select * from analytic_sterilizer_loadinv 
  where left(id::text,1) = 'a'

Это кажется медленным и ужасным. Хеш выглядит немного лучше:

select abs(hashtext(id::text))  % 64,
       count(*)

  from analytic_sterilizer_loadinv 

Размеры блоков не такие уж четные, но они могут быть достаточно хорошими, и я могу увеличить количество блоков, если это необходимо. К сожалению, я не знаю, как запустить мой код в цикле в SQL с использованием сегментов. Если кто-то должен указать, как, я был бы благодарен. И, если есть простая встроенная функция разбиения на блоки, я хотел бы знать об этом.

Я не продумал четкую проблему того, как обращаться с входящими данными, которые могут попасть вмодификации, если не считать блокировки всей таблицы. Что я мог бы сделать.

1 Ответ

1 голос
/ 03 октября 2019

Если вы можете себе это позволить, не делайте UPDATE партиями, но все сразу. Основным недостатком является то, что это приведет к раздуванию таблиц, и вы должны запустить VACUUM (FULL) для таблиц впоследствии, что приведет к простоям.

Я бы написал код клиента для обновления в пакетном режиме, например, вБаш:

typeset -i part=0

# PostgreSQL client time zone
export PGTZ=UTC

while [ $part -lt 64 ]
do
    psql <<-EOF
        UPDATE data.analytic_productivity
        SET created_dts = created_dts at time zone 'US/Central'
        WHERE abs(hashtext(id::text)) % 64 = '$part'
EOF
    psql -c "VACUUM data.analytic_productivity"

    part=part+1
done
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...