Комментарии 65
Как говорят: «дёшево и сердито». Хорошее решение всегда простое. Спасибо за заметку.
+2
Спасибо за топик. Сам работаю c хранилищами данных, так что интересно узнать, как там с OLTP-системами люди работают, для расширения кругозора, так сказать :)
Еще маленькое лирическое отступление, а то сферический конь получился хромым :)
Все-таки должно быть поле, например, billed (false или true), которое заполняется по-умолчанию false и переводится в true внутри функции.
Тогда функция должна работать только по тем записям, где billed = false. Ошибочка небольшая и вообще топик не о том, но она способствует некоторому непониманию.
Еще маленькое лирическое отступление, а то сферический конь получился хромым :)
Все-таки должно быть поле, например, billed (false или true), которое заполняется по-умолчанию false и переводится в true внутри функции.
Тогда функция должна работать только по тем записям, где billed = false. Ошибочка небольшая и вообще топик не о том, но она способствует некоторому непониманию.
+1
Да, вы правы, так даже нагляднее. Индекс тогда выглядит как:
И выборка идет еще шустрее.
CREATE INDEX billing.calls_subscriber_id_mod_idx ON billing.calls USING btree (subscriber_id % 4, billed);
* This source code was highlighted with Source Code Highlighter.
И выборка идет еще шустрее.
+1
Не знаю, как это реализовано в Postgres, но в оракле я бы стал в данном случае использовать индекс по (billed, subscriber_id % 4), таким образом, чтобы range scan шел сначала по всем записям, у которых billed = 1, а затем уже делил на шарды по модулю.
0
Тьфу, то есть по остатку от деления. И в оракле это будет не subscriber_id%4, а mod(subscriber_id, 4).
+1
Да, вы правы, спешил когда писал.
+1
В постгре еще можно partial индекс создать, тогда вообще проблем никаких:
CREATE INDEX billing.calls_subscriber_id_mod_idx ON billing.calls USING btree (subscriber_id % 4) WHERE billed = FALSE;
* This source code was highlighted with Source Code Highlighter.
+2
На самом деле этот прием я подглядел когда-то давно именно в биллинге (есть такой замечательный биллинг — БИС от петер-сервис, его Мегафон использует). У них отложенное применение скидок, и по таблице вызовов они идут в 8 или 16 потоков используя похожий индекс по полю % 8 или 16.
+1
НЛО прилетело и опубликовало эту надпись здесь
Думаю, там имелось ввиду выражение call_id % 4, имеющее равномерное распределение.
Если брать subscriber_id % 4, то в каждом «шарде» будет разное число записей, зависящее от трафика конкретных абонентов.
Если брать subscriber_id % 4, то в каждом «шарде» будет разное число записей, зависящее от трафика конкретных абонентов.
0
Нет, имелось в виду именно subscriber_id % 4. Смысл в том, чтобы один абонент обрабатывался всегда в одном потоке — иначе мы возможно встрянем на блокировке, например, когда два потока возьмутся баланс одному абоненту обновлять.
+1
Понял. В принципе, если у нас тысячи/миллионы абонентов и мы разделим их на 4 равных по количеству «лагеря», то и суммарный трафик у них будет примерно одинаков. Зато действительно удастся избежать лишних блокировок и потерянных обновлений.
0
Да, использует.
0
Да использует. Но толку от него никакого.
Без индекса:
С индексом:
Без индекса:
Seq Scan on a (cost=0.00..186284.08 rows=47345 width=8) (actual time=0.025..9414.434 rows=2500000 loops=1) Filter: ((num % 4) = 0) Total runtime: 12837.127 ms (3 rows)
С индексом:
Bitmap Heap Scan on a (cost=944.32..47774.81 rows=47345 width=8) (actual time=10434.219..21909.066 rows=2500000 loops=1) Recheck Cond: ((num % 4) = 0) -> Bitmap Index Scan on a_idx (cost=0.00..932.48 rows=47345 width=0) (actual time=10423.375..10423.375 rows=2500000 loops=1) Index Cond: ((num % 4) = 0) Total runtime: 25484.070 ms
0
коммент выше — habrahabr.ru/blogs/postgresql/76309/#comment_2217268
в таком случае использование индекса будет оправдано
в таком случае использование индекса будет оправдано
+1
НЛО прилетело и опубликовало эту надпись здесь
Действительно, вызывает некоторое сомнение эффективность такого индекса
+1
Сейчас стоит задача на постгресе — сделать апдейт 500 000 записей (информация о книгах) из одной таблицы импорта в 3 таблицы живых данных. На девелоперской машине занимает около 15 мин, а на VPS больше часа. Ваш пост открыл мне глаза на то как можно легко и изящно ускорить процесс импорта. Спасибо.
0
pg_try_advisory_lock() как раз для таких задач.
dklab.ru/chicken/nablas/53.html
dklab.ru/chicken/nablas/53.html
+3
Подправьте в статье
CREATE INDEX billing.calls_subscriber_id_mod_idx ON billing.calls USING btree ((subscriber_id % 4));
по крайней мере Postgres 8.4.1 так требует
CREATE INDEX billing.calls_subscriber_id_mod_idx ON billing.calls USING btree ((subscriber_id % 4));
по крайней мере Postgres 8.4.1 так требует
+1
имхо btree индекс с таким плохим распределением не будет хорошо работать, возможно даже без него будет лучше — попробуйте
Если кстати процесса всего 4 и очень хочется индекс, то эффективнее будет сделать четыре индекса вида CREATE INDEX calls_idx$1 ON billing.calls WHERE id%4=$1
Если кстати процесса всего 4 и очень хочется индекс, то эффективнее будет сделать четыре индекса вида CREATE INDEX calls_idx$1 ON billing.calls WHERE id%4=$1
0
CREATE INDEX calls_idx$1 ON billing.calls (id) WHERE id%4=$1
0
Да, тоже правильно. Можно еще bitmap индекс создать. А вообще если мы каждый раз чешем по всей таблице, то индекс выигрыш даст минимальный, а вот если появляется поле billed (как выше в комментариях), то ситуация меняется. В примере billed нет чтобы не отвлекать внимание от сути.
0
если чесать по всей таблице, то sequence scan сильно дешевле перебора по индексу из-за отсутствия random seek. В идеале постгресс сам это понимает и не пользует индекс, на практике за этим надо следить, особенно на больших таблицах и/или функциональных индексах
+1
А вы уверены, что они у вас будут выбираться по индексу?
При большом кол-ве строк и большой выборке база скорее всего будет делать seq scan и фильтровать. Хотя может зависеть от конкретных чисел.
Но это никак не повлияет на распределение задач.
При большом кол-ве строк и большой выборке база скорее всего будет делать seq scan и фильтровать. Хотя может зависеть от конкретных чисел.
Но это никак не повлияет на распределение задач.
0
Выше в комментариях идет дискуссия на этот счет. Более жизнеспособный пример habrahabr.ru/blogs/postgresql/76309/#comment_2217268, при котором индекс дает преимущество. Если идти по всей таблице в 4 потока — выигрыша по сути нет. Если в 16 — есть. Так что все зависит от задачи.
0
А если просто сделать N-таблиц, которые будут выглядеть как одна billing.calls (VIEW)?
0
Нет, это плохо. А если вам 4-х потоков уже не хватает, а нужно 8? Замучаетесь таблицы пересоздавать.
0
В PostgreSQL можно использовать partitions.
0
> А если вам 4-х потоков уже не хватает, а нужно 8?
И тут-то вы лёгким движением руки превратите 4-хъ-ядерник в 8-и? :-)
Вообще-то, ничто не мешает заранее подумать об этом, и раскидывать на большее кол-во таблиц, а просто менять схему выборки, в зависимости от действительного кол-ва ядер.
И тут-то вы лёгким движением руки превратите 4-хъ-ядерник в 8-и? :-)
Вообще-то, ничто не мешает заранее подумать об этом, и раскидывать на большее кол-во таблиц, а просто менять схему выборки, в зависимости от действительного кол-ва ядер.
0
решение кривое, т.к. если сервер не успевает обрабатывать FIFO то на нескольких процессах он должен la сервера поднять и затормозить работу сервера. Плюс ко всему пойди потом разберись если один потом будет сваливаться на ошибке на кривой строке и остальные три будут работать нормально.
Такая задача решается мониторингом длины очереди и контролем при запуске скрипта того что старый завершен.
Для этого можно использовать flock() и lock-pid файлы.
Такая задача решается мониторингом длины очереди и контролем при запуске скрипта того что старый завершен.
Для этого можно использовать flock() и lock-pid файлы.
0
и кстати проблема возникнет опять когда объем данных вырастет в 4 раза :)
0
Я вам об одном сферическом коне, а вы мне о своем:).
>>решение кривое, т.к. если сервер не успевает обрабатывать FIFO
В примере не было FIFO, но мне не жалко, пусть будет.
>>то на нескольких процессах он должен la сервера поднять и затормозить работу сервера.
Угу, три ядра у нас простаивало, а теперь они заняты работой и усиленно тормозят работу сервера, да.
>>Такая задача решается мониторингом длины очереди и контролем при запуске скрипта того что старый завершен.
В примере так и делается. Просто когда скрипт перестает справляться, очередь начинает накапливаться.
>>решение кривое, т.к. если сервер не успевает обрабатывать FIFO
В примере не было FIFO, но мне не жалко, пусть будет.
>>то на нескольких процессах он должен la сервера поднять и затормозить работу сервера.
Угу, три ядра у нас простаивало, а теперь они заняты работой и усиленно тормозят работу сервера, да.
>>Такая задача решается мониторингом длины очереди и контролем при запуске скрипта того что старый завершен.
В примере так и делается. Просто когда скрипт перестает справляться, очередь начинает накапливаться.
+1
а триггером нельзя калькуляцию сделать?
0
плюс еще можно калькуляцию делать не одним запросом для всех, а для каждого свой запрос, тогда субд сама все раскидает как сумеет.
0
… я про асинхронный вызов запросов :)
0
ээ, а как вы себе это представляете?
0
Представляют так — сейчас бд у вас принимает запросы синхронно, друг за другом. А можно накидать ей кучу запросов без ожидания их завершения и пусть сама разгребает по мере своих способностей.
Можно использовать например драйвер developer.postgresql.org/pgdocs/postgres/libpq-async.html
Можно использовать например драйвер developer.postgresql.org/pgdocs/postgres/libpq-async.html
0
Каких конкретно запросов накидать?
0
SELECT calculate(subscriber_id, duration) FROM billing.calls WHERE subscriber_id = 1
0
А абонентов у вас 5 миллионов. Откуда знаете какой звонил за последние 5 минут?
0
SELECT DISCTINCT subscriber_id FROM billing.calls
foreach result as item
SELECT calculate(subscriber_id, duration) FROM billing.calls WHERE subscriber_id = %item%
foreach result as item
SELECT calculate(subscriber_id, duration) FROM billing.calls WHERE subscriber_id = %item%
+1
Это в один поток. Я сейчас молчу про потери при лишнем seq scan'е и сортировке таблицы. А если захотите асинхронно по каждому пустить — придется вытаскивать результат SELECT DISTINCT на клиента. А потом еще затрачивать время на открытие нового connection к базе для запуска очередного запроса.
0
нет.
distinct я указал для понимания, есть масса способов его обойти enable_seqscan = off.
открывается pconnect, узнаете из индекса который уже в памяти все id клиентов, в той же tcp сессии накидываете запросы SELECT calculate(subscriber_id, duration) FROM billing.calls WHERE subscriber_id = %item% по количеству клиентов и закрываете соединение. скрипт завершается. база начинает работать. т.к. каждый запрос оформлен отдельно, субд сама решает где у нее свободное ядро и кладет запрос на него.
distinct я указал для понимания, есть масса способов его обойти enable_seqscan = off.
открывается pconnect, узнаете из индекса который уже в памяти все id клиентов, в той же tcp сессии накидываете запросы SELECT calculate(subscriber_id, duration) FROM billing.calls WHERE subscriber_id = %item% по количеству клиентов и закрываете соединение. скрипт завершается. база начинает работать. т.к. каждый запрос оформлен отдельно, субд сама решает где у нее свободное ядро и кладет запрос на него.
0
вы изначально скриптом пытаетесь решить задачу которую должна решать субд короче.
0
Я пытаюсь быстро решить поставленную задачу. А вот что вы пытаетесь сделать для меня до сих пор загадка.
>>в той же tcp сессии накидываете
Для каждого запроса вам в любом случае придется открывать новое соединение с БД. Ну никак без этого — одно соединение = выполнение в 1 поток. Так что это заглохнет сразу же. Вы можете сейчас мне что-то ответить, но лучше объясните — какой смысл во всех этих телодвижениях, когда проблема решается примерно за 1 минуту созданием одного индекса и трех дополнительных джобов?
>>в той же tcp сессии накидываете
Для каждого запроса вам в любом случае придется открывать новое соединение с БД. Ну никак без этого — одно соединение = выполнение в 1 поток. Так что это заглохнет сразу же. Вы можете сейчас мне что-то ответить, но лучше объясните — какой смысл во всех этих телодвижениях, когда проблема решается примерно за 1 минуту созданием одного индекса и трех дополнительных джобов?
0
Зачем для каждого запроса открывать новое соединение я не пойму. Преимущество асинхронного подхода в том что т.к. скрипту не нужны результаты выполнения запросов, он может сразу отдать все запросы (по запросу на клиента) бд, а бд сама все сделает как надо. Делается это все в ту же минуту, огорода никакого нет, зависимости от количества ядер нет.
В mysql аналог примерно такой
INSERT DELAYED INTO result (uid, summa) SELECT uid, sum(mins) FROM log WHERE uid=$userId GROUP BY uid
Скрипт отдает задание БД и завершается почти мгновенно.
В mysql аналог примерно такой
INSERT DELAYED INTO result (uid, summa) SELECT uid, sum(mins) FROM log WHERE uid=$userId GROUP BY uid
Скрипт отдает задание БД и завершается почти мгновенно.
0
Не бывает запроса без соединения с БД, хоть какого-нибудь. Вы по ссылке которую мне дали ходили? developer.postgresql.org/pgdocs/postgres/libpq-async.html
int PQsendQuery(PGconn *conn, const char *command);
Что такое *conn по вашему? На каждое новое соединение форкается новый процесс.
int PQsendQuery(PGconn *conn, const char *command);
Что такое *conn по вашему? На каждое новое соединение форкается новый процесс.
0
вы про персистент-коннекты слышали?
0
И что? Договаривайте уж.
0
так сказал же уже несколько раз выше — установили соединение, делаете запрос, зачем отконнекчиваться — делаете еще запрос, и так пока не закончатся запросы, потом закрываете соединение.
0
Вы понимаете, что если вы новое соединение создавать не будете, то запросы будут выполняться синхронно?
0
да, действительно вы правы — в исходниках увидел что асинхронность в либе кривая. Тогда надо смотреть на цену локального коннекта. Можно конечно сделать пачки по uid, но тогда получится нечто среднее между вашим и моим вариантом.
а асинхронные коммиты смотрели как работают — они тоже в одной нити будут выполнятся если пачку селектов засунуть в один коммит и если каждый селект обернуть асинхронним коммитом?
developer.postgresql.org/pgdocs/postgres/wal-async-commit.html
а асинхронные коммиты смотрели как работают — они тоже в одной нити будут выполнятся если пачку селектов засунуть в один коммит и если каждый селект обернуть асинхронним коммитом?
developer.postgresql.org/pgdocs/postgres/wal-async-commit.html
0
Цена коннекта большая — форкается новый процесс. Я руководствуюсь простым правилом — если есть простое решение, я использую его. Вне зависимости от результата, вы предлагаете очень сильно все усложнить.
0
мне архитектура такого подхода к обработке инфы не нравится. мне нравится модульность. то что скрипт ждет выполнение каждой команды мне кажется совершенно лишним. я бы, вероятно, сделал на mysql с insert delayed, или, что скорее всего такую задачу решил бы вообще не на реляционной субд.
0
Это замедлит вставку, а при ошибке, вставка вообще не пройдет. Если вставлять быстро (COPY в postgres), то триггер игнорируется. Вообщем-то с триггерами я всегда руководствуюсь простым правилом — если есть возможность избежать использования триггеров — выбирай ее всегда.
0
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Распараллеливание длительных операций