У меня странная проблема с производительностью базы данных Postgrtes 10.
У меня есть такая таблица:
CREATE TABLE articles
(
article_id bigint NOT NULL,
content jsonb NOT NULL,
-- few other fields...
CONSTRAINT articles_pkey PRIMARY KEY (article_id)
)
У нас есть библиотека Python (использующая драйвер psycopg2) для доступа к этой таблице. Он выполняет простые запросы к таблице, один из них используется для массовой загрузки контента и выглядит так:
select content from articles where id between $1 and $2
Я заметил, что с некоторыми идентификаторами он работает довольно быстро, в то время как с другими он в 4-5 раз медленнее. Следует отметить, что в этой таблице есть две основные категории идентификаторов: первая - диапазон 270000000-500000000, а вторая - диапазон 10000000000-100030000000. Для первого диапазона это «быстро», а для второго - «медленно». Помимо больших абсолютных чисел, извлекающих их из int
до bigint
, значения во втором диапазоне являются более «разреженными», что означает, что в первом диапазоне значения являются почти последовательными (с очень небольшим количеством «дырок» пропущенных значений) в то время как в во втором диапазоне гораздо больше «дырок» (среднее заполнение - 30%).
Чтобы исключить возможное влияние кода другой библиотеки, я провел синтетические тесты со следующим запросом:
select count(*), sum(length(content::text))
from storage.articles where article_id between %s and %s
Я изменил длину запрошенных патронов, чтобы выровнять итоговую длину JSONB на запрос в среднем.
Дополнительно я вычисляю скорость обработки как возвращенную count(*)
, деленную на время выполнения. Для первого диапазона это 800-1050 строк / с, а для второго - 120-250 строк / с. Другой показатель - это длина обработанного JSON за раз. Поскольку основная сложность в запросе заключается в обработке данных JSONB, я предполагаю, что они должны быть в среднем одинаковыми в обоих случаях. Но нет: это 7-10 МБ / с против 1,5-2,5 МБ / с.
Итак, вопрос воспроизведен. Хотя в абсолютных значениях он стал быстрее (из-за отсутствия передачи по сети), существенная разница в производительности сохраняется. Я сравнил планы выполнения для запросов в обоих диапазонах, они выглядят эквивалентно (EXPLAIN ANALYZE
):
Aggregate (cost=8046.71..8046.72 rows=1 width=8) (actual time=2924.410..2924.411 rows=1 loops=1)
-> Index Scan using articles_pkey on articles (cost=0.57..8000.02 rows=4669 width=107) (actual time=11.482..61.258 rows=5000 loops=1)
Index Cond: ((article_id >= 461995000) AND (article_id <= 461999999))
Planning time: 1.028 ms
Execution time: 2924.650 ms
Aggregate (cost=6216.75..6216.76 rows=1 width=8) (actual time=17704.635..17704.636 rows=1 loops=1)
-> Index Scan using articles_pkey on articles (cost=0.57..6180.69 rows=3606 width=107) (actual time=0.154..563.578 rows=3059 loops=1)
Index Cond: ((article_id >= '100017010000'::bigint) AND (article_id <= '100017019999'::bigint))
Planning time: 0.404 ms
Execution time: 17704.746 ms
Единственное отличие, которое я вижу, это преобразование в bigint
, которое должно быть одноразовым.
Я что-то упустил? Как исследовать (и исправить) это?
Обновление 1: Добавлено EXPLAIN
с BUFFERS
:
Aggregate (cost=8635.91..8635.92 rows=1 width=16) (actual time=6625.993..6625.995 rows=1 loops=1)
Buffers: shared hit=26847 read=3914
-> Index Scan using articles_pkey on articles (cost=0.57..8573.35 rows=5005 width=107) (actual time=21.649..1128.004 rows=5000 loops=1)
Index Cond: ((article_id >= 438000000) AND (article_id <= 438005000))
Buffers: shared hit=4342 read=671
Planning time: 0.393 ms
Execution time: 6626.136 ms
Aggregate (cost=5533.02..5533.03 rows=1 width=16) (actual time=33219.100..33219.102 rows=1 loops=1)
Buffers: shared hit=6568 read=7104
-> Index Scan using articles_pkey on articles (cost=0.57..5492.96 rows=3205 width=107) (actual time=22.167..12082.624 rows=2416 loops=1)
Index Cond: ((article_id >= '100021000000'::bigint) AND (article_id <= '100021010000'::bigint))
Buffers: shared hit=50 read=2378
Planning time: 0.517 ms
Execution time: 33219.218 ms
Обновление 2: Больше экспериментов:
Я провел еще несколько тестов, чтобы использовать описанный выше подход ко всем данным в таблице. Используя простую программу, я перебираю границы сегмента, чтобы в конечном итоге обработать все данные в обоих диапазонах. Во время этого я анализирую результат EXPLAIN ANALYZE и собираю ряд следующих метрик:
- Буфер ударил / читает для таблицы.
- Буфер хиты / чтения для индекса.
- Количество строк (от
Index Scan...
)
- Продолжительность исполнения
На основании вышеуказанных метрик я вычисляю унаследованные метрики:
- Скорость чтения с диска: (чтение индекса + чтение таблицы) * 8192 / продолжительность.
- Коэффициент чтения: (чтение индекса + чтение таблицы) / (чтение индекса + чтение таблицы + совпадения индекса + совпадения таблицы)
- Скорость передачи данных: (чтение индекса + чтение таблицы + совпадение индекса + совпадение таблицы) * 8192 / длительность
Поскольку «плотность» идентификаторов различается в «малых» и «больших» диапазонах, я отрегулировал размер кусков, чтобы в обоих случаях получать около 5000 строк на каждую итерацию, хотя мои эксперименты показывают, что размер кусков на самом деле не очень важно.
Опубликованный выше результат был подтвержден для целых первого и второго диапазонов (поэтому он был вызван не только случайно кэшированными данными).
Чтобы устранить влияние кэша, я перезапустил сервер Postgres с очищающими буферами:
postgresql stop; sync; echo 3 > /proc/sys/vm/drop_caches; postgresql start
После этого я повторил тест и получил почти такую же картинку.
Я рендерил серии из последнего теста в графики и выкладывал их здесь:
!['Small' numbers range](https://i.stack.imgur.com/3Zfml.png)
Это картинка для «малого» диапазона. Скорость чтения с диска в тесте составляет около 10-11 МБ / с.
Вопрос: почему частота обращений очень высока (коэффициент чтения низкий), хотя я очищал буферы памяти?
!['Big' numbers range](https://i.stack.imgur.com/VXdID.png)
Это картинка для "большого" диапазона. Это выглядит безумно для меня. В большинстве случаев скорость чтения с диска составляла около 2 МБ / с, но иногда она возрастала до 26–30 МБ / с. Коэффициент чтения также сильно различался.
Во время тестов я проверил скорость чтения диска с iotop
и обнаружил, что его показания очень близки к рассчитанным мной. Я не могу сказать, что отслеживал это все время, но я подтвердил это, когда оно было 2 МБ / с и 22 МБ / с во втором диапазоне и 10 МБ / с в первом диапазоне. Я также проверил с htop
, что процессор не был узким местом и составлял около 3% во время тестов.
Эта проблема воспроизводима как на главном, так и на подчиненном серверах. Мои тесты проводились на ведомом устройстве, в то время как не было никакой другой нагрузки на СУБД или дисковой активности на хосте, не связанном с СУБД.
Мое единственное предположение состоит в том, что различные фрагменты данных читаются с разной скоростью из-за виртуализации или чего-то еще, но ... почему это так строго связано с этими диапазонами? Почему это одинаково на двух разных машинах?
Есть идеи?