Низкая производительность из libpq, koteg, 08-Июн-16, 14:12 [смотреть все]Всем читающим это сообщение доброго времени суток.Возник вопрос с производительностью сервера Postgres 9.5 при работе через libpq из приложения, написанного на С. Тестировался вызов (10000 раз) хранимой ф-ции из скрипта PGSQL и из приложения. Результаты: Скрипт: 503 msec. (19880.71570 операций вставки в секунду) Приложение: 71.241997 sec. (140.36664 операций вставки в секунду) Разница в 141.63418 раз!!!111 В чём может быть проблема такой деградации? Описание системы:
$ uname -a Linux localhost.dev.resolute.ru 3.9.4 #3 SMP Tue May 28 14:26:24 EDT 2013 x86_64 x86_64 x86_64 GNU/Linux$ /usr/web/bin/pg_ctl --version pg_ctl (PostgreSQL) 9.5.3
Вызываемая процедура:
create or replace function test_job( in v_i_id int8, out v_o_id int8 ) as $$ begin select n into v_o_id from test_t where n = 100; insert into test_t(n) values (v_i_id); end; $$ language plpgsql
Тестовый скрипт:
do $$ declare v_o_res int8; begin for i in 1..10000 loop select test_job(i::int8) into v_o_res; end loop; end; $$
Код приложения (С)
#include <stdio.h> #include <libpq-fe.h> #include <sys/time.h>#define uint64_t unsigned long long uint64_t htonll(uint64_t host_longlong) { int x = 1; if(*(char *)&x == 1) return ((((uint64_t)htonl(host_longlong)) << 32) + htonl(host_longlong >> 32)); else return host_longlong; } int main() { PGconn *conn; const char *keywords[7] = {"host", "port", "dbname", "user", "password", "client_encoding", NULL}; const char *values[7] = {"/usr/web/run", "5434", "_login", "_schema", "_passwd", "UTF8", NULL}; int rowCount, colCount, i, j; PGresult *res; ExecStatusType status; struct timeval tv; conn = PQconnectdbParams(keywords, values, 0); if (PQstatus(conn) == CONNECTION_BAD) { printf("Не удается подключиться к базе данных\n%s\n", PQerrorMessage(conn)); return 1; } gettimeofday(&tv, NULL); long long t = tv.tv_sec*1000000 + tv.tv_usec; const char *paramValues[1]; int paramLengths[1]; int paramBinary[1]; for(i=0; i<10000; i++) { long long id = htonll(i); paramValues[0] = (char*)&id; paramLengths[0] = 8; paramBinary[0] = 1; res = PQexecParams(conn, "select test_job($1::int8)", 1, // кол-во параметров NULL, // backend узнает тип параметров из текста запроса paramValues, paramLengths, paramBinary, 0 // результат вернуть как текст ); status = PQresultStatus(res); if((status != PGRES_COMMAND_OK)&&(status != PGRES_TUPLES_OK)) { printf("ERROR: %s\n", PQresultErrorMessage(res)); PQclear(res); return 0; } //printf("%s\n", PQgetvalue(res, 0, 0)); } gettimeofday(&tv, NULL); t = (long long)(tv.tv_sec*1000000 + tv.tv_usec) - t; printf("\ntime: %f sec\n", ((float)t)/1000000); PQclear(res); PQfinish(conn); return 0; }
|
- Низкая производительность из libpq, PavelR, 16:25 , 08-Июн-16 (1)
.. а также замени PQexecParams на PQprepare + PQexecPrepared
- Низкая производительность из libpq, koteg, 17:27 , 08-Июн-16 (3)
> .. а также замени PQexecParams на PQprepare + PQexecPrepared Заменил. Полегчало, но не сильно, стало на 1 секунду хуже... Таперь код такой:
const char* stmtName = "TEST_JOB"; Oid oidTypes[1] = {20}; // int8 OID=20, int8[] OID=1016 res = PQprepare(conn, stmtName, "select test_job($1::int8)", 1, oidTypes); for(i=0; i<10000; i++) { long long id = htonll(i); paramValues[0] = (char*)&id; paramLengths[0] = 8; paramBinary[0] = 1; res = PQexecPrepared(conn, stmtName, 1, paramValues, paramLengths, paramBinary, 0 ); status = PQresultStatus(res); if((status != PGRES_COMMAND_OK)&&(status != PGRES_TUPLES_OK)) { printf("ERROR: %s\n", PQresultErrorMessage(res)); PQclear(res); return 0; } }
Время выполнения 72.068604 sec
- Низкая производительность из libpq, PavelR, 16:28 , 08-Июн-16 (2)
разберись, сколько у тебя транзакций в первом и во втором случаях.
- Низкая производительность из libpq, koteg, 17:35 , 08-Июн-16 (4)
> разберись, сколько у тебя транзакций в первом и во втором случаях.А как это сделать? Насколько я понимаю, в случае скрипта - 1 транзакция, а в случае libpq - 10000. Но даже в этом случае, деградация в 141 раз - это за гранью добра и зла. Всего 140 операций вставки в секунду (в таблицу с одним столбцом int8) - это что-то странное. Т.к. Постгрес используется на нагруженных проектах, то значит что-то я делаю не правильно. Через libpq после каждой операции должен выполняться автокоммит, он и выполняется. Не выполнять его нельзя, т.к. запросы приходят от обработчиков сетевых соединений - каждый запрос - атомарная транзакция, которая добавляет/изменяет данные в БД.
- Низкая производительность из libpq, PavelR, 19:26 , 08-Июн-16 (5)
>> разберись, сколько у тебя транзакций в первом и во втором случаях. > А как это сделать? > Насколько я понимаю, в случае скрипта - 1 транзакция, а в случае > libpq - 10000. > Но даже в этом случае, деградация в 141 раз - это за > гранью добра и зла.Т.е то, что в 10 000 раз большее число транзакций выполняется всего в 141 раз медленнее - это уже плохо? Отличная логика, правда несколько странная :-) > Всего 140 операций вставки в секунду (в таблицу с одним столбцом int8) > - это что-то странное. Т.к. Постгрес используется на нагруженных проектах, то > значит что-то я делаю не правильно. Посмотри, какую нагрузку на диски дает твой тест. 140 операций в секунду, это примерно и есть средняя производительность жесткого диска на случайных операциях. Или ты считаешь, что транзакция завершается "в воздух", а не записью на жесткий диск? Если транзакция не завершилась записью на диск - значит это не транзакция, а только её подобие. > Через libpq после каждой операции должен выполняться автокоммит, он и выполняется. > Не выполнять его нельзя, т.к. запросы приходят от обработчиков сетевых соединений - > каждый запрос - атомарная транзакция, которая добавляет/изменяет данные в БД. Должен, можно, нельзя... Это всё условности.
- Низкая производительность из libpq, koteg, 20:27 , 08-Июн-16 (6)
>>> разберись, сколько у тебя транзакций в первом и во втором случаях. >> А как это сделать? >> Насколько я понимаю, в случае скрипта - 1 транзакция, а в случае >> libpq - 10000. >> Но даже в этом случае, деградация в 141 раз - это за >> гранью добра и зла. > Т.е то, что в 10 000 раз большее число транзакций выполняется всего > в 141 раз медленнее - это уже плохо? Отличная логика, правда > несколько странная :-) 140 операций записи 8ми байт + служебной информации в секунду. Т.е. 1120 байт полезной информации можно сохранить за секунду.... маловато... >> Всего 140 операций вставки в секунду (в таблицу с одним столбцом int8) >> - это что-то странное. Т.к. Постгрес используется на нагруженных проектах, то >> значит что-то я делаю не правильно. > Посмотри, какую нагрузку на диски дает твой тест. > 140 операций в секунду, это примерно и есть средняя производительность жесткого диска > на случайных операциях. Или ты считаешь, что транзакция завершается "в воздух", > а не записью на жесткий диск? Если транзакция не завершилась записью > на диск - значит это не транзакция, а только её подобие.
нагрузка на диск идёт непропорциональная. Такое ощущение, что в каждой транзакции вставляется не 8 байтов, а десятки мегабайтов. На сервере стоят быстрые винты, тест производительности показывает 2.2 GB/s. Вот как грузит CPU и IO Постгрес (синие палки - операции IO, зелёные - CPU): Как вставить картинку - не знаю, поэтому дал ссылки: http://s017.radikal.ru/i421/1606/e1/5f78641fa4c4.jpg Время: 73 сек, процессор всё время ожидает операции ввода/вывода. На сохранение 80000 байт на диск не похоже. А вот работа диспетчера заданий (это и есть целевое приложение) на mongodb (сервер этот же) - запись в БД и обработка 10000 заданий (всего около 50000 атомарных операций с БД, каждая запись примерно 96 байт), плюс запуск 10000 процессов php-fpm (обработчиков заданий). http://s018.radikal.ru/i523/1606/c0/37650c524261.jpg Время: ~6.3 сек (дебаговая сборка без оптимизации. С оптимизацией 5.5 сек), грузится практически только процессор, сохранение на диске занимает мизерное время. Судя по всему, я что-то не так делаю, но не знаю в какую сторону рыть. >> Через libpq после каждой операции должен выполняться автокоммит, он и выполняется. >> Не выполнять его нельзя, т.к. запросы приходят от обработчиков сетевых соединений - >> каждый запрос - атомарная транзакция, которая добавляет/изменяет данные в БД. > Должен, можно, нельзя... Это всё условности. Автокоммит должен выполняться по логике приложения. После поступления запроса данные должны сразу быть доступны в БД для процесса обработки.
- Низкая производительность из libpq, PavelR, 20:41 , 08-Июн-16 (7)
> Такое ощущение, что в каждой транзакции вставляется > не 8 байтов, а десятки мегабайтов. На сервере стоят быстрые винты, > тест производительности показывает 2.2 GB/s.= > Время: 73 сек, процессор всё время ожидает операции ввода/вывода. На сохранение 80000 > байт на диск не похоже. Вы не понимаете, как работают жесткие диски и меряете их производительность не в тех единицах, а также смешиваете в одну кучу синхронные транзакции с гарантированной записью на диск и асинхронные транзакции с негарантированным сохранением. Определитесь с вашими потребностями, и дальше разбирайтесь например с этим: https://www.postgresql.org/docs/8.3/static/wal-async-commit....
- Низкая производительность из libpq, koteg, 21:11 , 08-Июн-16 (8)
>> Такое ощущение, что в каждой транзакции вставляется >> не 8 байтов, а десятки мегабайтов. На сервере стоят быстрые винты, >> тест производительности показывает 2.2 GB/s. > = >> Время: 73 сек, процессор всё время ожидает операции ввода/вывода. На сохранение 80000 >> байт на диск не похоже. > Вы не понимаете, как работают жесткие диски и меряете их производительность не > в тех единицах, а также смешиваете в одну кучу синхронные транзакции > с гарантированной записью на диск и асинхронные транзакции с негарантированным сохранением. > Определитесь с вашими потребностями, и дальше разбирайтесь например с этим: https://www.postgresql.org/docs/8.3/static/wal-async-commit.... Спасибо за совет, посмотрю асинхронные транзакции, завтра отпишусь. Замерил использование диска iostat'ом - запись 183464 блока (~90МБ), чтение 200 блоков (100кБ). Возникло два вопроса: 1. Это нормально, если при фиксации транзакции в 8 байт сохраняется более килобайта информации? 2. При скорости 2.2Гб в сек на запись 90Мб не могут писаться 70сек. Не знаете ли вы, где могут возникать блокировки, в какую сторону копать?
- Низкая производительность из libpq, PavelR, 21:29 , 08-Июн-16 (9)
> Замерил использование диска iostat'ом - запись 183464 блока (~90МБ), чтение 200 блоков > (100кБ). Возникло два вопроса: > 1. Это нормально, если при фиксации транзакции в 8 байт сохраняется более > килобайта информации?1) Диски это блочные устройства. Они технически не пишут байты, а пишут блоки 512 байт / 4096 байт. Аналогично - файловые системы пишут блоки/страницы, а не байты. 2) БД это не просто файлы. > 2. При скорости 2.2Гб в сек на запись 90Мб не могут писаться > 70сек. "Могут - не могут". Устроили тут... Еще раз: Вы не понимаете, как работают жесткие диски и меряете их производительность не в тех единицах. Линейная запись != случайная запись. В первом случае вы можете писать 2.2Гб/сек (при этом будет сколько-то-там операций в сек), во втором - будете ограничены ~200 операций в секунду, что даст на порядки меньшую скорость записи в байтах/сек. Всё это будет 100% загрузки жесткого диска. >Не знаете ли вы, где могут возникать блокировки, в какую сторону копать? Вроде уже всё разжевал.
|