Всем читающим это сообщение доброго времени суток.Возник вопрос с производительностью сервера 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;
}
.. а также замени PQexecParams на PQprepare + PQexecPrepared
> .. а также замени 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
разберись, сколько у тебя транзакций в первом и во втором случаях.
> разберись, сколько у тебя транзакций в первом и во втором случаях.А как это сделать?
Насколько я понимаю, в случае скрипта - 1 транзакция, а в случае libpq - 10000.
Но даже в этом случае, деградация в 141 раз - это за гранью добра и зла.
Всего 140 операций вставки в секунду (в таблицу с одним столбцом int8) - это что-то странное. Т.к. Постгрес используется на нагруженных проектах, то значит что-то я делаю не правильно.Через libpq после каждой операции должен выполняться автокоммит, он и выполняется. Не выполнять его нельзя, т.к. запросы приходят от обработчиков сетевых соединений - каждый запрос - атомарная транзакция, которая добавляет/изменяет данные в БД.
>> разберись, сколько у тебя транзакций в первом и во втором случаях.
> А как это сделать?
> Насколько я понимаю, в случае скрипта - 1 транзакция, а в случае
> libpq - 10000.
> Но даже в этом случае, деградация в 141 раз - это за
> гранью добра и зла.Т.е то, что в 10 000 раз большее число транзакций выполняется всего в 141 раз медленнее - это уже плохо? Отличная логика, правда несколько странная :-)
> Всего 140 операций вставки в секунду (в таблицу с одним столбцом int8)
> - это что-то странное. Т.к. Постгрес используется на нагруженных проектах, то
> значит что-то я делаю не правильно.Посмотри, какую нагрузку на диски дает твой тест.
140 операций в секунду, это примерно и есть средняя производительность жесткого диска на случайных операциях. Или ты считаешь, что транзакция завершается "в воздух", а не записью на жесткий диск? Если транзакция не завершилась записью на диск - значит это не транзакция, а только её подобие.> Через libpq после каждой операции должен выполняться автокоммит, он и выполняется.
> Не выполнять его нельзя, т.к. запросы приходят от обработчиков сетевых соединений -
> каждый запрос - атомарная транзакция, которая добавляет/изменяет данные в БД.Должен, можно, нельзя... Это всё условности.
>>> разберись, сколько у тебя транзакций в первом и во втором случаях.
>> А как это сделать?
>> Насколько я понимаю, в случае скрипта - 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 после каждой операции должен выполняться автокоммит, он и выполняется.
>> Не выполнять его нельзя, т.к. запросы приходят от обработчиков сетевых соединений -
>> каждый запрос - атомарная транзакция, которая добавляет/изменяет данные в БД.
> Должен, можно, нельзя... Это всё условности.Автокоммит должен выполняться по логике приложения. После поступления запроса данные должны сразу быть доступны в БД для процесса обработки.
> Такое ощущение, что в каждой транзакции вставляется
> не 8 байтов, а десятки мегабайтов. На сервере стоят быстрые винты,
> тест производительности показывает 2.2 GB/s.=
> Время: 73 сек, процессор всё время ожидает операции ввода/вывода. На сохранение 80000
> байт на диск не похоже.Вы не понимаете, как работают жесткие диски и меряете их производительность не в тех единицах, а также смешиваете в одну кучу синхронные транзакции с гарантированной записью на диск и асинхронные транзакции с негарантированным сохранением. Определитесь с вашими потребностями, и дальше разбирайтесь например с этим: https://www.postgresql.org/docs/8.3/static/wal-async-commit....
>> Такое ощущение, что в каждой транзакции вставляется
>> не 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сек. Не знаете ли вы, где могут возникать блокировки, в какую сторону копать?
> Замерил использование диска iostat'ом - запись 183464 блока (~90МБ), чтение 200 блоков
> (100кБ). Возникло два вопроса:
> 1. Это нормально, если при фиксации транзакции в 8 байт сохраняется более
> килобайта информации?1) Диски это блочные устройства. Они технически не пишут байты, а пишут блоки 512 байт / 4096 байт. Аналогично - файловые системы пишут блоки/страницы, а не байты.
2) БД это не просто файлы.
> 2. При скорости 2.2Гб в сек на запись 90Мб не могут писаться
> 70сек."Могут - не могут". Устроили тут...
Еще раз:
Вы не понимаете, как работают жесткие диски и меряете их производительность не в тех единицах.
Линейная запись != случайная запись. В первом случае вы можете писать 2.2Гб/сек (при этом будет сколько-то-там операций в сек), во втором - будете ограничены ~200 операций в секунду, что даст на порядки меньшую скорость записи в байтах/сек.
Всё это будет 100% загрузки жесткого диска.>Не знаете ли вы, где могут возникать блокировки, в какую сторону копать?
Вроде уже всё разжевал.