Email or username:

Password:

Forgot your password?
Top-level
Joinny Hash

Если COALESCE для обыгрывания NULL-значений делать, наверное, вообще вся работа индексов по одному месту пойдёт. В 15й версии для этого даже специальную опцию сделали, но, уверен, под капотом там тот же COALESCE будет. А на работе 14, так что я даже не пробовал.

Это такая скучная херня, дрочиться с работающими SQL'никами ради 5-10% выигрыша производительности, но так успокаивает и умиротворяет, радует, когда в итоге получается, когда нет срочных задач, само собой.

35 comments
Joinny Hash

Увидел что начиная с 12 версии можно пилить свои методы табличного доступа и загорелся идеей fine tuned custom приколов заточенных под специфику данных. Теоретически можно накостылить эдакий clickhouse внутри постгреса. Вот на старом проекте одну задачу можно было бы изящно утащить в постгрю, сделать бешенно быстрый парсинг и CDC. Вышло бы охуенно, очень смешно и не совсем понятно.

Roman

@strizhechenko а где поглядеть такое?

Joinny Hash

Обнаружил что не могут использоваться в случае если в запросе к полю применяется функция. Но и для этого есть обходной путь — можно индекс строить по этой же функции, а не по самому столбцу. При добавлении строки — правильно, будет вычисляться эта функция и результат запишется в индекс. Но при поиске, оно, скорее всего будет перепроверяться (re-check cond в explain). Ну, хоть не всю таблицу сканировать, конечно, но может это вычисление как-то можно избежать.

Joinny Hash

=> SELECT amname, amhandler FROM pg_am WHERE amtype = 'i';
amname | amhandler
−−−−−−−−+−−−−−−−−−−−−−
btree | bthandler
hash | hashhandler
gist | gisthandler
gin | ginhandler
spgist | spghandler
brin | brinhandler
(6 rows)

На этом книжку можно закрывать и в постгресе дальше не копаться. ОКР? Ну вот треснула бы жопа сделать btreehandler и spgisthandler?

sattellite

@strizhechenko и вот самый сложный продукт с открытым кодом, в котором патчи принимаются годами, чтобы соответствовать всем нормам. И на тебе, сэкономили на символах и теперь не вписывается в общий вид. А может они с них начинали и тогда общего видения не было?

Joinny Hash

@sattellite хз, обработчики индексных методов доступа кажутся неотъемлемой частью индекса, более того, название обработчиков однозначно ссылается на типы индексов, по крайней мере должно ссылаться :) вряд ли делалось без оглядки на оные сверху.

Joinny Hash

Почитал про методы доступа, которые Index Scan, Index Only Scan, Bitmap Scan, Seq Scan. Общий вывод, который делаю - на этапе проектирования системы крайне желательно иметь представление какого рода запросы в ней будут преобладать и оптимизировать систему под них. К примеру OLAP-нагрузка с комбинированными запросами может перекашиваться в сторону Bitmap Scan, использующий несколько независимых индексов на отдельные колонки — такой системе нужно мало воркеров, но с большим work_mem.

Joinny Hash

... это позволит избегать загрубления битмапов и лишнего recheck cond, что сократит I/O.

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

Joinny Hash

... особенно если таблица широкая.

Если же таблица небольшая (10-20 строк, 3-5 столбцов, например, но точное число сильно зависит от, надо бы поиграться), редко меняется, но часто читается, может оказаться выгодным вообще не создавать для неё индексов – при частом чтении, она будет лежать целиком в буферном кэше, не будет вытесняться и расходы на последователное чтение будут ничтожны.

Joinny Hash

Про ширину таблиц и индексы. Я вот всё ещё не могу для себя сформулировать, насколько плохим может оказаться влияние created_at и modified_at колнок, которые частенько включают в базовую модель ORM. Обычно весь их смысл - служебный, для переливки в другие БД, витрины итд. Но чтобы по этим колонкам выгребать данные эффективно - нужны идексы по ним. Хоп - и вставка подорожала. В приложении семантику на них завязать тоже редко получается — обычно важна дата изменения конкретного столбца.

Мнения?

Ale berada di Kuala Lumpur!

@strizhechenko Добавить без индексов про запас, если будут провисания — навесить индексы пост-фактум или подумать над архитектурой ещё

Joinny Hash

@ale да просто хранить все данные в одной JSON'ине, чоуж.

Ale berada di Kuala Lumpur! replied to Joinny

@strizhechenko Ну тбх иногда это тоже viable вариант!

fgntfg :verified: replied to Joinny

@strizhechenko @ale бля, я вот такое вот видел. База, в ней таблица, в таблице lob, в нем json, и вот только там данные. Программисты говорят что им нормально.

Ale berada di Kuala Lumpur! replied to fgntfg

@fgntfg @strizhechenko Мы на одном проекте складывали всякую вторичную инфу в JSON-колонку в постгресе. Думали, мол, ну вот сейчас со схемой определимся и отрефакторим нормально, но так и осталось по сей день кажется, лол.

Joinny Hash replied to Ale berada di Kuala Lumpur!

@ale @fgntfg у нас тоже есть extra_fields, по которым не надо ничего искать, но которые можно и нужно прокинуть абсолютно прозрачно в другую систему. Сберегло от необходимости правок бэка первой системы раз пять уже.

Ale berada di Kuala Lumpur! replied to Joinny

@strizhechenko @fgntfg Ну если таких полей много и они только там нужны, то норм, да. Но я бы такое поле тогда к конкретной другой системе прибивал в названии, типа meta_someotherservicename — чтобы было меньше соблазна его в других целях использовать.

Joinny Hash replied to Ale berada di Kuala Lumpur!

@ale @fgntfg к счастью несмотря на то, что эти строки могут синхронизироваться с двумя системами, одна строка синхронизируется строго с одной из них и это заранее известно. Поэтому для второй системы это поле используется как кэш дополнительных данных от неё же, чтобы пореже её API долбить запросами :)

Чувство прекрасного у коллеги трещит, её корёжит, но та таблица по прежнему компактна :D

Joinny Hash

Про статистику.

Для оценки селективности и кардинальности используется статистика. Она состоит из n_distinct - число уникальных значений, MCV + MCF - самых популярных значений и их частоты (при больших разбросах там аж гистограмы подрубаются с бакетами), доли null значений, средний размер полей вариативной длины, корелляцию — насколько совпадает физическое расположение строк в таблице с их порядком выдачи из индекса, чем меньше, тем хуже, но в случае с SSD не ясно, _насколько_ хуже, типа проблема ж не в рандомном чтении без префетчей, а в (перепро)чтении лишних страниц, как я понял.

- бро твоего планировщика и позволяет автоматически дрочить диск оптимальным способом, если она, конечно, есть и правдива. Статистика собирается полуслучайным образом, типа берём рандомные то ли 300, то ли 30 000 страниц, берём из них рандомные 30 000 (?) строк и АНАЛИЗИРУЕМ. Вакуум такой стоит, аж буферные кэши вытесняются.

Если честно, я порой охреневаю, какой это ебовый овер(?)килл и сколько всего порождает простая запись в табличку. И всё это нужно ведь прочитать, обсчитать, проанализировать перед непосредственным запуском читающего запроса. Вроде у планировщика есть кэш запросов, но ведь в него ещё и попадать надо. А ещё к нему надо обратиться перед планированием, вдруг есть чо, а если нет - это ж ещё одни накладные расходы. Зато SQL простой и декларативный язык, невероятно ведь круто описать что ты хочешь получить, не заморачиваясь вопросом "как".

Про статистику.

Для оценки селективности и кардинальности используется статистика. Она состоит из n_distinct - число уникальных значений, MCV + MCF - самых популярных значений и их частоты (при больших разбросах там аж гистограмы подрубаются с бакетами), доли null значений, средний размер полей вариативной длины, корелляцию — насколько совпадает физическое расположение строк в таблице с их порядком выдачи из индекса, чем меньше, тем хуже, но в случае с SSD не ясно, _насколько_ хуже, типа проблема...

Joinny Hash

Про

любопытно работает. По сути это здоровенная хэш-таблица с горячими и холодными значениями, при переполнении общего размера выделенного под неё память сперва вытесняются холодные (реже читаемые). А любопытен кейс, когда для очень горячего значени набор строк не влезает в эту память — его вытесняют, оставляя эту память для более холодных значений, потому что от части строк толку нет - остальные всё равно придётся с диска заново тянуть.

Joinny Hash replied to Joinny

Задумался, а как мне это всё в работе поможет и чот хз. Пока самое эффективное, что я делал, было избавление от insert ignore on conflict + update (разбивка по группам, почти равномерная по модулю от autoincrement integer id) всего что не заигнорилось, заменив это на вставку чанками по 1000 строк с чередованием этих групп и последующим выравниванием недавно вставленного с помощью хитрожопой математики. Благодаря этому получилось для 90% вставляемых строк создать всего одну версию строки, изначально, ещё при вставке выбрав им нужную группу. Дисковое I/O в итоге сократилось на 40% где-то, но помимо этого оно ещё и сгладилось (благодаря вставке чанками), а лаг репликации сократился без лишних апдейтов под той же транзакцией.

Всё это было бы не нужно, если бы все-все-все команды коллег взялись и организовали унифицированный экспорт данных с временными метками изменений, а все-все-все пользователи нашего сервиса писали бы SQL-запросы с учётом этих меток (CDC), чтобы не тянуть каждый раз много данных, и ещё все старые запущенные кампании поправили под это требование. Ух зажили бы.

Задумался, а как мне это всё в работе поможет и чот хз. Пока самое эффективное, что я делал, было избавление от insert ignore on conflict + update (разбивка по группам, почти равномерная по модулю от autoincrement integer id) всего что не заигнорилось, заменив это на вставку чанками по 1000 строк с чередованием этих групп и последующим выравниванием недавно вставленного с помощью хитрожопой математики. Благодаря этому получилось для 90% вставляемых строк создать всего одну версию строки, изначально,...

Joinny Hash replied to Joinny

TIL что NOT EXISTS / EXISTS ... вырождаются в anti/semi-join, типа LEFT JOIN ... WHERE key IS NULL и при наличии индекса на key, всё выглядит не так уж страшно.

А ещё задумался, что при массовой вставке строк с использованием моделей явно стоит перепроверить, какой стоит в системе (и махнуть его на tsm, если не нужна сверхточность). Беда только в том, что в случае managed postgres, прямого доступа к БД нет и проверить это несколько сложновато (хотя, по-любому в psql есть встроенный remote cat и доступ к файловой системе), а потом ещё и с командой, которая постгрес крутит этот вопрос обкашливать, потом выяснится, что clocksource глобальный на всех, что кому-то эта точность нужна и вообще чего суету навёл, нормально ж и стабильно живём.

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

TIL что NOT EXISTS / EXISTS ... вырождаются в anti/semi-join, типа LEFT JOIN ... WHERE key IS NULL и при наличии индекса на key, всё выглядит не так уж страшно.

А ещё задумался, что при массовой вставке строк с использованием моделей явно стоит перепроверить, какой стоит в системе (и махнуть его на tsm, если не нужна сверхточность). Беда только в том, что в случае managed postgres, прямого доступа к БД нет и проверить это несколько сложновато (хотя, по-любому в psql есть встроенный...

Joinny Hash replied to Joinny

Хотя, наверное, клокдрифтинг между ядрами жёстко что-нибудь ломает, если где-то есть завязки на физические (таймдельты в многопроцессных системах), а не логические (чисто X было раньше Y) часы.

Joinny Hash replied to Joinny

Хэш-таблицы с шириной строго являющейся степенью двойки - это довольно ловкий ход, позволяющий вместо остатка от деления (дорого) просто дёргать младшие N-битов хэша (дёшево).

Joinny Hash replied to Joinny

И снова к ширине строк таблиц. У меня в обоих проектах есть статусы некоторых объектов. В целом это короткие varchar, обычно до 16 символов длиной (хотя разок уткнулись, семантично ну никак не могли название придумать короче 18 символов). В python это, само собой, . Понятное дело, хочется там, где можно избавиться от TOAST'а, шоб всё было быстро и весело, все таблицы помещались в одну страницу итд, и вкатить enum'ы и в базе, 4 байта вместо 16 звучит привлекательно, а на 20 000 000 строк ещё привлекательнее, минус 76мб из веса таблицы, да ещё и дополнительная защита от дурака.

Но судя по документации они очень неюзабельны — в миграциях alter type с добавлением новых значений можно юзать, а вот почистить от неактуального - херушки (и оно как бы справедливо, в значениях-то указатели), звучит как дохерища ручной работы. Опять же, если не меняется - есть не просит.

Бля, я чувствую как во мне просыпается техлид lingualeo.

И снова к ширине строк таблиц. У меня в обоих проектах есть статусы некоторых объектов. В целом это короткие varchar, обычно до 16 символов длиной (хотя разок уткнулись, семантично ну никак не могли название придумать короче 18 символов). В python это, само собой, . Понятное дело, хочется там, где можно избавиться от TOAST'а, шоб всё было быстро и весело, все таблицы помещались в одну страницу итд, и вкатить enum'ы и в базе, 4 байта вместо 16 звучит привлекательно, а на 20 000 000 строк ещё...

Joinny Hash replied to Joinny

Индексы по enum-полям ещё наверняка обосраться насколько выгодная тема, они ж скорее всего в int4_ops сводятся вместо text_ops.

Mahury replied to Joinny

@strizhechenko о, и енум тоже надо повторить. а то "ну это же просто, как вы это не понимаете", но задачки почему то не решаются, а как прикручивать из объяснений непонятно совсем.

Mahury replied to Joinny

@strizhechenko я про свою учёбу. что енум надо повторить. а то протыкал, но в голове вообще не отложилось.

Joinny Hash replied to Joinny

Чем мне нравится - так это тем, что он имеет стандартизированные базовые блоки для операций над данными. Никаких велосипедов! А я их видел много и выгоды от этого кастома было мало (но иногда была, когда использовалась выверенная бенчмарками хэш-функция для специфической структуры данных для большого паттерн-матчинга с кучей доп. правил, которая при росте объёмов выигрывала у компилированной гигарегулярки/схожей реализации в постгре).

Joinny Hash replied to Joinny

Дочитал до реализации Merge Join (странное название, отдаёт тавтологией, но пусть). Кажется вот оно, место где понимание способов сортировки двух массивов имеет хоть какой-то приближённый смысл в этой нашей айтишечке. И это немного перекликается с предыдущим постом треда — одна (ну почти) реализация на способ (нет велосипедов), система даже сама оптимальный способ выберет (спасибо планировщику). Но тогда непонятно, зачем всё это знать рядовому разработчику, если это забота DBA? Рядовому достаточно знать как навесить индекс хотя бы на одно используемое в запросе поле, можно даже за тип индекса не заморачиваться, дефолтный btree 99% потребностей закрывает и,обычно, ничем не хуже hash, + знать что много индексов замедляют вставку. Всё!

Дочитал до реализации Merge Join (странное название, отдаёт тавтологией, но пусть). Кажется вот оно, место где понимание способов сортировки двух массивов имеет хоть какой-то приближённый смысл в этой нашей айтишечке. И это немного перекликается с предыдущим постом треда — одна (ну почти) реализация на способ (нет велосипедов), система даже сама оптимальный способ выберет (спасибо планировщику). Но тогда непонятно, зачем всё это знать рядовому разработчику, если это забота DBA? Рядовому достаточно...

sharikov

@strizhechenko там еще кстати не по всем функциям можно строить , функция должна быть детерменирована, а в случае с самописной функцией, так еще и с промаркирована IMMUTABLE

Go Up