The OpenNET Project / Index page

[ новости /+++ | форум | теги | ]

форумы  помощь  поиск  регистрация  вход/выход  слежка  RSS
"Низкая производительность из libpq"
Вариант для распечатки  
Пред. тема | След. тема 
Форум Программирование под UNIX (PostgreSQL)
Изначальное сообщение [ Отслеживать ]

"Низкая производительность из libpq"  +/
Сообщение от koteg (ok) on 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;
}


Ответить | Правка | Cообщить модератору

Оглавление

Сообщения по теме [Сортировка по времени | RSS]


1. "Низкая производительность из libpq"  +/
Сообщение от PavelR (??) on 08-Июн-16, 16:25 
.. а также замени PQexecParams на PQprepare + PQexecPrepared
Ответить | Правка | ^ к родителю #0 | Наверх | Cообщить модератору

3. "Низкая производительность из libpq"  +/
Сообщение от koteg (ok) on 08-Июн-16, 17:27 
> .. а также замени 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 | Наверх | Cообщить модератору

2. "Низкая производительность из libpq"  +/
Сообщение от PavelR (??) on 08-Июн-16, 16:28 
разберись, сколько у тебя транзакций в первом и во втором случаях.
Ответить | Правка | ^ к родителю #0 | Наверх | Cообщить модератору

4. "Низкая производительность из libpq"  +/
Сообщение от koteg (ok) on 08-Июн-16, 17:35 
> разберись, сколько у тебя транзакций в первом и во втором случаях.

А как это сделать?

Насколько я понимаю, в случае скрипта - 1 транзакция, а в случае libpq - 10000.
Но даже в этом случае, деградация в 141 раз - это за гранью добра и зла.
Всего 140 операций вставки в секунду (в таблицу с одним столбцом int8) - это что-то странное. Т.к. Постгрес используется на нагруженных проектах, то значит что-то я делаю не правильно.

Через libpq после каждой операции должен выполняться автокоммит, он и выполняется. Не выполнять его нельзя, т.к. запросы приходят от обработчиков сетевых соединений - каждый запрос - атомарная транзакция, которая добавляет/изменяет данные в БД.

Ответить | Правка | ^ к родителю #2 | Наверх | Cообщить модератору

5. "Низкая производительность из libpq"  +/
Сообщение от PavelR (??) on 08-Июн-16, 19:26 
>> разберись, сколько у тебя транзакций в первом и во втором случаях.
> А как это сделать?
> Насколько я понимаю, в случае скрипта - 1 транзакция, а в случае
> libpq - 10000.
> Но даже в этом случае, деградация в 141 раз - это за
> гранью добра и зла.

Т.е то, что в 10 000 раз большее число транзакций выполняется всего в 141 раз медленнее - это уже плохо? Отличная логика, правда несколько странная :-)

> Всего 140 операций вставки в секунду (в таблицу с одним столбцом int8)
> - это что-то странное. Т.к. Постгрес используется на нагруженных проектах, то
> значит что-то я делаю не правильно.

Посмотри, какую нагрузку на диски дает твой тест.
140 операций в секунду, это примерно и есть средняя производительность жесткого диска на случайных операциях. Или ты считаешь, что транзакция завершается "в воздух", а не записью на жесткий диск? Если транзакция не завершилась записью на диск - значит это не транзакция, а только её подобие.

> Через libpq после каждой операции должен выполняться автокоммит, он и выполняется.
> Не выполнять его нельзя, т.к. запросы приходят от обработчиков сетевых соединений -
> каждый запрос - атомарная транзакция, которая добавляет/изменяет данные в БД.

Должен, можно, нельзя... Это всё условности.

Ответить | Правка | ^ к родителю #4 | Наверх | Cообщить модератору

6. "Низкая производительность из libpq"  +/
Сообщение от koteg (ok) on 08-Июн-16, 20:27 
>>> разберись, сколько у тебя транзакций в первом и во втором случаях.
>> А как это сделать?
>> Насколько я понимаю, в случае скрипта - 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 после каждой операции должен выполняться автокоммит, он и выполняется.
>> Не выполнять его нельзя, т.к. запросы приходят от обработчиков сетевых соединений -
>> каждый запрос - атомарная транзакция, которая добавляет/изменяет данные в БД.
> Должен, можно, нельзя... Это всё условности.

Автокоммит должен выполняться по логике приложения. После поступления запроса данные должны сразу быть доступны в БД для процесса обработки.

Ответить | Правка | ^ к родителю #5 | Наверх | Cообщить модератору

7. "Низкая производительность из libpq"  +/
Сообщение от PavelR (??) on 08-Июн-16, 20:41 
> Такое ощущение, что в каждой транзакции вставляется
> не 8 байтов, а десятки мегабайтов. На сервере стоят быстрые винты,
> тест производительности показывает 2.2 GB/s.

=
> Время: 73 сек, процессор всё время ожидает операции ввода/вывода. На сохранение 80000
> байт на диск не похоже.

Вы не понимаете, как работают жесткие диски и меряете их производительность не в тех единицах, а также смешиваете в одну кучу синхронные транзакции с гарантированной записью на диск и асинхронные транзакции с негарантированным сохранением. Определитесь с вашими потребностями, и дальше разбирайтесь например с этим: https://www.postgresql.org/docs/8.3/static/wal-async-commit....


Ответить | Правка | ^ к родителю #6 | Наверх | Cообщить модератору

8. "Низкая производительность из libpq"  +/
Сообщение от koteg (ok) on 08-Июн-16, 21:11 
>> Такое ощущение, что в каждой транзакции вставляется
>> не 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сек. Не знаете ли вы, где могут возникать блокировки, в какую сторону копать?

Ответить | Правка | ^ к родителю #7 | Наверх | Cообщить модератору

9. "Низкая производительность из libpq"  +/
Сообщение от PavelR (??) on 08-Июн-16, 21:29 
> Замерил использование диска iostat'ом - запись 183464 блока (~90МБ), чтение 200 блоков
> (100кБ). Возникло два вопроса:
> 1. Это нормально, если при фиксации транзакции в 8 байт сохраняется более
> килобайта информации?

1) Диски это блочные устройства. Они технически не пишут байты, а пишут блоки 512 байт / 4096 байт. Аналогично - файловые системы пишут блоки/страницы, а не байты.

2) БД это не просто файлы.

> 2. При скорости 2.2Гб в сек на запись 90Мб не могут писаться
> 70сек.

"Могут - не могут". Устроили тут...

Еще раз:

Вы не понимаете, как работают жесткие диски и меряете их производительность не в тех единицах.

Линейная запись != случайная запись. В первом случае вы можете писать 2.2Гб/сек (при этом будет сколько-то-там операций в сек), во втором - будете ограничены ~200 операций в секунду, что даст на порядки меньшую скорость записи в байтах/сек.
Всё это будет 100% загрузки жесткого диска.

>Не знаете ли вы, где могут возникать блокировки, в какую  сторону копать?

Вроде уже всё разжевал.

Ответить | Правка | ^ к родителю #8 | Наверх | Cообщить модератору

Архив | Удалить

Рекомендовать для помещения в FAQ | Индекс форумов | Темы | Пред. тема | След. тема




Партнёры:
PostgresPro
Inferno Solutions
Hosting by Hoster.ru
Хостинг:

Закладки на сайте
Проследить за страницей
Created 1996-2024 by Maxim Chirkov
Добавить, Поддержать, Вебмастеру