Этот баннер — требование Роскомнадзора для исполнения 152 ФЗ.
«На сайте осуществляется обработка файлов cookie, необходимых для работы сайта, а также для анализа использования сайта и улучшения предоставляемых сервисов с использованием метрической программы Яндекс.Метрика. Продолжая использовать сайт, вы даёте согласие с использованием данных технологий».
Политика конфиденциальности
|
|
|
Подсчет строк по фильтру в большой таблице
|
|||
|---|---|---|---|
|
#18+
Добрый вечер, Имеется некий сервис который хранит статистику о сайтах и их пользователях. Общее число записей в таблице пользователей - 100 миллионов, у самого крупного сайта - около 10 миллионов пользователей. Выгялдит таблица примерно так: CREATE TABLE client ( id bigint NOT NULL, ... country character varying(255), ip character varying(255), lang character varying(255), os integer, registrationdate timestamp(6) without time zone, valid boolean, site_id bigint, ... CONSTRAINT client_pkey PRIMARY KEY (id), CONSTRAINT site_fk FOREIGN KEY (site_id) REFERENCES site (id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE NO ACTION, ... ) CREATE INDEX date_index ON client USING btree (site_id, valid, registrationdate); CREATE INDEX site_index ON client USING btree (site_id, valid); Нужно считать колличество валидных уникальных клиентов сайта у которых дата регистрации больше определенной даты (выборка от 3-5 миллионов записей). На данный момент делаем так: SELECT count(distinct (id)) FROM client WHERE site_id IN (1540369841275273216) AND valid=true AND registrationDate>'20-Jul-12'; *сайтов может быть несколько по этому используется IN; но даже с учетом индексов запрос выполняется 3-8 минут, что неприемлемо. Какой вариант подсчета записей лучше реализовать, что б можно было получить значение максимально быстро? Пока в голову приходит только создание в базе sequence для каждого сайта и тригера который будет инкриментить/декрементить соответствующий sequence при добавлении/удалении из таблицы клиентов. Тогда можно будет сразу получить значение из sequence, но насколько это скажется на производительности высоконагруженного сервера с огромной базой? Также при удалении 1000 клиентов одним запросом тригер выполнится 1000 раз, вместо того что б один раз сделать +1000 для последовательности. Возможно есть варианты получше? ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 02.11.2016, 19:26 |
|
||
|
Подсчет строк по фильтру в большой таблице
|
|||
|---|---|---|---|
|
#18+
Denys Den, Вопросы по производительности полезно сопровождать планом исполнения запроса, желательно также с информацией о буферах. Короче: EXPLAIN (analyze, buffers) … ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 02.11.2016, 21:53 |
|
||
|
Подсчет строк по фильтру в большой таблице
|
|||
|---|---|---|---|
|
#18+
Denys Den, а зачем тут distinct в запросе если id - primary key? уберите - будет быстрее. в общем случае если старые данные не обновляются - нужно делать агрегацию например каждый день/месяц и брать уже подсчитанные данные и объединять их с неагрегированными данными за последний день/месяц. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 03.11.2016, 05:17 |
|
||
|
Подсчет строк по фильтру в большой таблице
|
|||
|---|---|---|---|
|
#18+
vyegorov, конечно, но первый пост получился и так достаточно большой и я написал что суть не в производительности запроса а в реализации механизма подсчета, хотя если можно заставить этот запрос выполняться быстрее 1 секунды, то вот: Результат EXPLAIN: Aggregate (cost=2458240.56..2458240.57 rows=1 width=8) -> Bitmap Heap Scan on client (cost=248140.23..2434057.23 rows=9673333 width=8) Recheck Cond: (site_id = '1549722331764490240'::bigint) Filter: (valid AND (registrationdate > '2012-07-20 00:00:00'::timestamp without time zone)) -> Bitmap Index Scan on site_index (cost=0.00..245721.90 rows=9673333 width=0) Index Cond: ((site_id = '1549722331764490240'::bigint) AND (valid = true)) EXPLAIN ( analyze on, buffers on ): Aggregate (cost=2458240.56..2458240.57 rows=1 width=8) (actual time=139690.103..139690.103 rows=1 loops=1) Buffers: shared hit=7 read=2079135 written=1, temp read=14664 written=14664 -> Bitmap Heap Scan on client (cost=248140.23..2434057.23 rows=9673333 width=8) (actual time=1124.318..129998.275 rows=10000000 loops=1) Recheck Cond: (site_id = '1549722331764490240'::bigint) Rows Removed by Index Recheck: 88209812 Filter: (valid AND (registrationdate > '2012-07-20 00:00:00'::timestamp without time zone)) Heap Blocks: exact=40592 lossy=2000225 Buffers: shared read=2079135 written=1 -> Bitmap Index Scan on site_index (cost=0.00..245721.90 rows=9673333 width=0) (actual time=1116.813..1116.813 rows=10000000 loops=1) Index Cond: ((site_id = '1549722331764490240'::bigint) AND (valid = true)) Buffers: shared read=38318 written=1 Planning time: 110.237 ms Execution time: 139690.545 ms ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 03.11.2016, 15:06 |
|
||
|
Подсчет строк по фильтру в большой таблице
|
|||
|---|---|---|---|
|
#18+
Alexius, действительно без distinct секунд на 30 стало быстрее, но все равно 2 с лишним минуты. По поводу агрегации неплохой вариант, количество новых клиентов за день достается за милисекунды, может лучше разово посчитать для всех сайтов текущее количество клиентов, а потом при каждом запросе считать количество с момента последнего подсчета и сумировать с предыдущим? Но проблема в том что старые данные обновляются и если старый клиент станет невалидным или его удалят из базы то у нас при этом подходе общее колличество не уменьшится, колличество будет только суммироваться, но не уменьшаться. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 03.11.2016, 15:23 |
|
||
|
Подсчет строк по фильтру в большой таблице
|
|||
|---|---|---|---|
|
#18+
Denys Den, какое значение `work_mem`? поднимите его для этого запроса, метров до 64 индекс `(site_id, registrationdate)` должен помочь данные холодные уж очень. какое значение `shared_buffers`, `max_wal_size`, `checkpoint_timeout` и сколько памяти на сервере? ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 03.11.2016, 16:25 |
|
||
|
Подсчет строк по фильтру в большой таблице
|
|||
|---|---|---|---|
|
#18+
vyegorov, на сервере 32 гигабайта оперативной памяти, до 16-ти из них занято. Индекс `(site_id, registrationdate)` есть, как показывает EXPLAIN он не используется (точнее оптимизатор постгрес его использует в случаях где количество клиентов небольшое, для больших сайтов он использует индекс по site_id и затем на этой выборке применяет Filter по valid и registrationdate, возможно так действительно работает быстрее так как индекс тоже достаточно большой). shared_buffers = 2048MB max_wal_size, checkpoint_timeout и work_mem не установлены, используются стандартные значения, на сколько я понял: wal_buffers = -1 (sets based on shared_buffers) checkpoint_timeout = 5min ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 03.11.2016, 16:58 |
|
||
|
Подсчет строк по фильтру в большой таблице
|
|||
|---|---|---|---|
|
#18+
vyegorovDenys Den, какое значение `work_mem`? поднимите его для этого запроса, метров до 64 индекс `(site_id, registrationdate)` должен помочь данные холодные уж очень. какое значение `shared_buffers`, `max_wal_size`, `checkpoint_timeout` и сколько памяти на сервере? либо снесите site_index кху йам либо пересоберите статистику для date_index а лучше всего -- сделайте и то и другое.. (там соотношенийе 1:10 -- вы в 10 раз больше читаете, чем надо) и проверьте, что правильное приведение условия по времнени к таймтсп(6) ешё в строке запроса не (?) переключает индекс на нужный (с использованием условия еше в индекс скане, а не в фильтре). (вообще бы прибивал нахер пользователей х.з. каких строк вместо единственно правильных литералов timestamp'yyyy-mm-dd hh...' ) ЗЫ есть странная идея -- сунуть сам id в date_index последним полем --- а то ж это угробище не дотумкает, что там ИОС напрашивается (при свежой визибилити-меп) зы2 валид должен быть или первым полем, или вообще условием на частичном индексе, как правило ну и т.п. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 03.11.2016, 17:01 |
|
||
|
Подсчет строк по фильтру в большой таблице
|
|||
|---|---|---|---|
|
#18+
qwwq ЗЫ есть странная идея -- сунуть сам id в date_index последним полем --- а то ж это угробище не дотумкает, что там ИОС напрашивается (при свежой визибилити-меп) или попробуйте ему помочь дотумкать : Код: sql 1. 2. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 03.11.2016, 17:06 |
|
||
|
Подсчет строк по фильтру в большой таблице
|
|||
|---|---|---|---|
|
#18+
qwwq, >либо снесите site_index кху йам >либо пересоберите статистику для date_index Удалил site_index, перегенерил date_index, ничего особо не изменилось. EXPLAIN: "Aggregate (cost=2525259.89..2525259.90 rows=1 width=8)" " -> Bitmap Heap Scan on client (cost=315159.56..2501076.56 rows=9673333 width=8)" " Recheck Cond: ((site_id = '1549722331764490240'::bigint) AND (registrationdate > '2015-05-02 00:00:00'::timestamp without time zone))" " Filter: valid" " -> Bitmap Index Scan on date_index (cost=0.00..312741.23 rows=9673333 width=0)" " Index Cond: ((site_id = '1549722331764490240'::bigint) AND (valid = true) AND (registrationdate > '2015-05-02 00:00:00'::timestamp without time zone))" Не пойму зачем он делает Bitmap Heap Scan, наверное в нем вся загвоздка. >или попробуйте ему помочь дотумкать : >SELECT --count(distinct (id)) >count(1) FROM client WHERE Не знаю как это работает и что означает, но запрос выполнился за 2 с. EXPLAIN показал что используется только индекс скан без Bitmap Heap Scan: "Aggregate (cost=433657.89..433657.90 rows=1 width=0)" " -> Index Only Scan using date_index on client (cost=0.57..409474.56 rows=9673333 width=0)" " Index Cond: ((site_id = '1549722331764490240'::bigint) AND (valid = true) AND (registrationdate > '2015-05-02 00:00:00'::timestamp without time zone))" " Filter: valid" ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 03.11.2016, 19:05 |
|
||
|
Подсчет строк по фильтру в большой таблице
|
|||
|---|---|---|---|
|
#18+
Denys Den, за 2 с -- потому что индекс был в памяти, большей частью. а означает -- пж не понимает, что ид нот нулл всегда -- и в плане всегда держит чтение самой записи. а мы понимаем, что любая запись , удовлетворяющая where должна быть подсчитана. поэтому берем count (1) , а для такого запроса оптимайзер уже может применить ios -- т.е. забить на чтение самих записей (никаких полей не содержащихся в индексе в выводе не остаётся) , если в визибилити меп узел индекса помечен как живой. Т.е. экономия на 9 лямах (9673333) произвольных доступов. а хип скан, скорее всего делает эти доступы более правильными кучками (читал но забыл). а делать их не надо вообще, если индекс свежий. вот отсюда и 2 сек. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 03.11.2016, 19:26 |
|
||
|
Подсчет строк по фильтру в большой таблице
|
|||
|---|---|---|---|
|
#18+
в мепе скорее всего страницы индекса помечены как полностью живые или грязные. ну да товарисчи счас подробно распишут. надеюсь. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 03.11.2016, 19:30 |
|
||
|
Подсчет строк по фильтру в большой таблице
|
|||
|---|---|---|---|
|
#18+
Denys Den, не совсем понял, почему вы считаете что "не изменилось" у вас раньше всё уходило в песок Код: sql 1. а читать эти записи теперь не обязательно (если только на страницах в нужными 10 лямами не лежит дополнительно ненужных 90 лямов) ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 03.11.2016, 19:40 |
|
||
|
Подсчет строк по фильтру в большой таблице
|
|||
|---|---|---|---|
|
#18+
Denys Den, Не надо `EXPLAIN`, надо `EXPLAIN (analyze, buffers)`. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 03.11.2016, 19:48 |
|
||
|
Подсчет строк по фильтру в большой таблице
|
|||
|---|---|---|---|
|
#18+
vyegorov, я его тоже уже приводил: EXPLAIN ( analyze, buffers): Aggregate (cost=2458240.56..2458240.57 rows=1 width=8) (actual time=139690.103..139690.103 rows=1 loops=1) Buffers: shared hit=7 read=2079135 written=1, temp read=14664 written=14664 -> Bitmap Heap Scan on client (cost=248140.23..2434057.23 rows=9673333 width=8) (actual time=1124.318..129998.275 rows=10000000 loops=1) Recheck Cond: (site_id = '1549722331764490240'::bigint) Rows Removed by Index Recheck: 88209812 Filter: (valid AND (registrationdate > '2012-07-20 00:00:00'::timestamp without time zone)) Heap Blocks: exact=40592 lossy=2000225 Buffers: shared read=2079135 written=1 -> Bitmap Index Scan on site_index (cost=0.00..245721.90 rows=9673333 width=0) (actual time=1116.813..1116.813 rows=10000000 loops=1) Index Cond: ((site_id = '1549722331764490240'::bigint) AND (valid = true)) Buffers: shared read=38318 written=1 Planning time: 110.237 ms Execution time: 139690.545 ms ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 03.11.2016, 20:54 |
|
||
|
Подсчет строк по фильтру в большой таблице
|
|||
|---|---|---|---|
|
#18+
Denys Den, Я попросил сделать индекс на `(site_id, registrationdate)`, без `valid`. Индексировать bool-колонки смысла нет, т.к. селективность очень маленькая, я бы убрал поле `valid` из всех индексов. Я предпочитаю добавлять такие колонки в WHERE условия: Код: sql 1. Это частичный индекс — если можно обойтись полным, то лучше таковой и использовать. Также я просил увеличить `work_mem`. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 03.11.2016, 21:26 |
|
||
|
Подсчет строк по фильтру в большой таблице
|
|||
|---|---|---|---|
|
#18+
vyegorov... Индексировать bool-колонки смысла нет, т.к. селективность очень маленькая, я бы убрал поле `valid` из всех индексов. какбе это не совсем верное утверждение. 1. может быть распределение 1:100 и поиск на стороне 1. хотя тут лучше частичный индекс , как вы и предлагаете 2. для 5 бул-полей индекс (b1,b2,b3,b4,b5) уже вполне себе селективен. а для 10 -- потрясающе селективен. 3. булев префикс даже если само булево условие не селективно, усекает количество доступов вдвое . Т.е. если все запросы будут содержать булево условие -- иметь булево поле в начале индекса не вредно. (да и составить туфтовое OR для условия не содержащего булево поле не так сложно -- будет битмап--OR. даже если оптимайзер сам этого не сделает) но в любом случае хотелось бы посмотреть планы с аналайзами и буферами после дропа инд-а по сайту. они то уж точно должны быть другими, если не по времени выполнения, то по деталям. ан ТС уперся на своем "ничего ,, не поменялось". ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 03.11.2016, 21:46 |
|
||
|
Подсчет строк по фильтру в большой таблице
|
|||
|---|---|---|---|
|
#18+
vyegorovDenys Den, Я попросил сделать индекс на `(site_id, registrationdate)`, без `valid`. Индексировать bool-колонки смысла нет, т.к. селективность очень маленькая, я бы убрал поле `valid` из всех индексов. смысл добавлять valid в индекс есть даже в случае частичного индекса, даже когда он зафиксирован. без этого ios работать не будет (это может в 9.6 починили, не проверял еще). ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 04.11.2016, 08:17 |
|
||
|
Подсчет строк по фильтру в большой таблице
|
|||
|---|---|---|---|
|
#18+
qwwq... но в любом случае хотелось бы посмотреть планы с аналайзами и буферами после дропа инд-а по сайту. они то уж точно должны быть другими, если не по времени выполнения, то по деталям. ан ТС уперся на своем "ничего ,, не поменялось". Вот результат EXPLAIN ( analyze on, buffers on ) после дропа индекса по сайту: Aggregate (cost=2525210.64..2525210.65 rows=1 width=8) (actual time=141612.989..141612.989 rows=1 loops=1) Buffers: shared read=2090089, temp read=14664 written=14664 -> Bitmap Heap Scan on ckient (cost=315127.23..2501029.72 rows=9672366 width=8) (actual time=3254.863..132744.122 rows=10000000 loops=1) Recheck Cond: ((site_id = '1549722331764490240'::bigint) AND (registrationdate > '2012-07-20 00:00:00'::timestamp without time zone)) Rows Removed by Index Recheck: 88554710 Filter: valid Heap Blocks: exact=32726 lossy=2008091 Buffers: shared read=2090082 -> Bitmap Index Scan on count_index (cost=0.00..312709.14 rows=9672366 width=0) (actual time=3217.411..3217.411 rows=10000000 loops=1) Index Cond: ((site_id = '1549722331764490240'::bigint) AND (valid = true) AND (registrationdate > '2012-07-20 00:00:00'::timestamp without time zone)) Buffers: shared read=49265 Planning time: 0.250 ms Execution time: 141613.199 ms ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 08.11.2016, 13:29 |
|
||
|
Подсчет строк по фильтру в большой таблице
|
|||
|---|---|---|---|
|
#18+
vyegorovТакже я просил увеличить `work_mem`. Вот результаты после увеличения work_mem до 64 MB: Aggregate (cost=2525210.64..2525210.65 rows=1 width=8) (actual time=142649.610..142649.611 rows=1 loops=1) Buffers: shared hit=7 read=2090082, temp read=14664 written=14664 -> Bitmap Heap Scan on follower (cost=315127.23..2501029.72 rows=9672366 width=8) (actual time=4471.210..132165.383 rows=10000000 loops=1) Recheck Cond: ((site_id = '1549722331764490240'::bigint) AND (registrationdate > '2012-07-20 00:00:00'::timestamp without time zone)) Rows Removed by Index Recheck: 56041728 Filter: valid Heap Blocks: exact=769795 lossy=1271022 Buffers: shared read=2090082 -> Bitmap Index Scan on count_index (cost=0.00..312709.14 rows=9672366 width=0) (actual time=4201.615..4201.615 rows=10000000 loops=1) Index Cond: ((site_id = '1549722331764490240'::bigint) AND (valid = true) AND (registrationdate > '2012-07-20 00:00:00'::timestamp without time zone)) Buffers: shared read=49265 Planning time: 233.452 ms Execution time: 142650.789 ms ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 08.11.2016, 13:43 |
|
||
|
Подсчет строк по фильтру в большой таблице
|
|||
|---|---|---|---|
|
#18+
Denys Den, Хм... можно поднять до 160, чтобы всё влезло, но погоды не сделает. А покажите ваши настройки и инфу по таблице/индексам: Код: sql 1. 2. 3. 4. 5. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 08.11.2016, 15:16 |
|
||
|
Подсчет строк по фильтру в большой таблице
|
|||
|---|---|---|---|
|
#18+
vyegorovDenys Den, Хм... можно поднять до 160, чтобы всё влезло, но погоды не сделает. А покажите ваши настройки и инфу по таблице/индексам: Код: sql 1. 2. 3. 4. 5. select name,setting,context from pg_settings where source not in ('default','override'): "max_connections";"100";"postmaster" "max_stack_depth";"2048";"superuser" "shared_buffers";"262144";"postmaster" "work_mem";"65536";"user" select relname,pg_relation_size(oid),reltuples,null::text def from pg_class where oid='clients'::regclass union all select relname,pg_relation_size(r.oid),reltuples,pg_get_indexdef(r.oid) from pg_class r join pg_index i on i.indexrelid=r.oid where i.indrelid='clients'::regclass; "client";16718372864;1e+08;"" "client_pkey";2246197248;1e+08;"CREATE UNIQUE INDEX follower_pkey ON client USING btree (id)" "regid_index";5905137664;1e+08;"CREATE INDEX regid_index ON client USING btree (regid)" "welcome_index";2246197248;1e+08;"CREATE INDEX welcome_index ON client USING btree (wmdelivered)" "date_index";7371243520;1e+08;"CREATE INDEX date_index ON client USING btree (site_id, valid, registrationdate, city, country, alias_id, browser)" "full_index";5641027584;1e+08;"CREATE INDEX full_index ON client USING btree (site_id, valid, city, country, alias_id, browser)" "date_index";4061192192;1e+08;"CREATE INDEX count_index ON client USING btree (site_id, valid, registrationdate)" ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 10.11.2016, 14:36 |
|
||
|
|

start [/forum/topic.php?fid=53&fpage=83&tid=1996884]: |
0ms |
get settings: |
10ms |
get forum list: |
13ms |
check forum access: |
4ms |
check topic access: |
4ms |
track hit: |
58ms |
get topic data: |
12ms |
get forum data: |
3ms |
get page messages: |
58ms |
get tp. blocked users: |
2ms |
| others: | 295ms |
| total: | 459ms |

| 0 / 0 |
