The OpenNET Project / Index page

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




Версия для распечатки Пред. тема | След. тема
Новые ответы [ Отслеживать ]
Низкая производительность из 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)
    • Низкая производительность из 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)
        • Низкая производительность из 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)
            • Низкая производительность из 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сек. Не знаете ли вы, где могут возникать блокировки, в какую сторону копать?




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

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