Тестирование пропускной способности базы данных postgres с использованием пула потоков и пула соединений. Но почему у меня только 300 вставок в секунду, когда должно быть 6000?

Я хочу проверить пропускную способность моей системы, которая подключена к базе данных postgresql. Моя система состоит из 2 основных компонентов: ThreadPoolExecutor как newFixedThreadPool с максимум 10 потоками и PGPoolingDataSource, называемый connectionPool, который имеет максимум 10 подключений к базе данных. Я вызываю хранимые процедуры в базе данных postgres, хранимая процедура выполняет простую вставку и возвращает сообщение об ошибке, если вставка не удалась. Выполнение одного вызова этой хранимой процедуры занимает около 20-30 мс.

Система работает следующим образом: основной поток создает задачи сообщений и передает их в пул потоков. Задача сообщения делает следующее: получает соединение из пула соединений и вызывает хранимую процедуру на сервере postgres. Он ждет ответа, а затем задача завершается. Поток в пуле потоков теперь может работать над новой задачей сообщения.

Теперь я думаю, что это должно работать нормально, и в какой-то степени это работает. Это только очень медленно, и я абсолютно не знаю, почему. Используя следующий код, я записываю около 300-500 вставок в секунду, когда должно быть 6000 вставок в секунду. Понятия не имею почему. При использовании системного монитора я вижу, что все процессоры загружены примерно на 20%. Когда я раскомментирую раздел, обозначенный (1), 1 процессор загружается на 100%, а остальные - около 0%, что для меня загадка.

Если кто-нибудь может поделиться светом о том, что я делаю неправильно, это было бы здорово. Это мой сервер postgres настроен неправильно? Когда я использую команду top, она показывает, что java использует около 20% процессора, и есть 8 процессов postgres, каждый из которых использует около 3%. (Я использую Ubuntu 14.04, используя Eclipse).

Вот мой код MainTester, содержащий основную функцию. Он создает пул потоков и пул соединений с базой данных.

public class MainTester {
public static ThreadPoolExecutor threadPoolExecutor;
    public static PGPoolingDataSource connectionPool;

public static void main(String[] args) {

    establishConnectionPool(10);
    threadPoolExecutor = (ThreadPoolExecutor) 
    Executors.newFixedThreadPool(10);

    Operator operator = new Operator(1, 2, 30);
        operator.run();
// i created an other thread here before.
//Now I just use the main thread to run the operator
}


private static void establishConnectionPool(int nrOfConnections)
    {
        connectionPool = new PGPoolingDataSource();
        connectionPool.setDataSourceName("ConnectionPool");
        connectionPool.setServerName(dbServerName);
        connectionPool.setDatabaseName(dbName);
        connectionPool.setUser(dbUser);
        connectionPool.setPassword(dbPassword);
        connectionPool.setMaxConnections(nrOfConnections);
    }

Это мой код оператора. Он порождает задачи сообщений и передает их в пул потоков. Я хочу, чтобы он работал в течение 2 минут, а затем проверил количество вставленных сообщений. Я хочу, чтобы очередь пула потоков всегда была заполнена, поэтому я проверяю, имеет ли очередь пула потоков менее 1000 задач. Если их меньше, я создаю новые задачи для пула потоков.

public class Operator implements Runnable{

private int minutesToRun = 2;

private void run () {

    long startTime = System.currentTimeMillis();

    while (System.currentTimeMillis() - startTime < minutesToRun * 60 * 1000 + 10) {

            while(MainTester.threadPoolExecutor.getQueue().size() < 1000) {
                MessageTask messageTask = new MessageTask(QueueOperation.SEND, 1, 1, 1, "abc");
                MainTester.threadPoolExecutor.execute(messageTask);
            }

            try { // (1)
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    }
}

}

(1) когда я не сплю здесь, системный монитор показывает, что 1 процессор на 100%, остальные на 0%. Это не имеет смысла для меня. Конечно, этот метод полностью занял бы один процессор, но потоки из пула потоков должны работать на другом процессоре, нет?

Вот мой код для задачи сообщения:

public class MessageTask implements Runnable {

private QueueOperation operation;
private int senderId;
private int receiverId;
private int queueId;
private String message; 


public MessageTask (QueueOperation op, int senderId, int receiverId, int queueId, String message)
{
    operation = op;
    this.senderId = senderId;
    this.receiverId = receiverId;
    this.queueId = queueId;
    this.message = message;
}

@Override
public void run() {

    Connection connection = null;
    try {
        connection = MainTester.connectionPool.getConnection();
    } catch (SQLException e) {
        e.printStackTrace();
    }

    try{

        Statement statement = connection.createStatement();

        String dbStoredProcedure = "SELECT send(" + senderId + "," + receiverId + "," + queueId + "," + "'"+message+"'"+ ");";;

        ResultSet resultSet = statement.executeQuery(dbStoredProcedure);
        resultSet.next();
        String dbResponse = resultSet.getString(1);
    }

    catch (SQLException e) {
    }

    finally {
        try {
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

}

Итак, мои вопросы: почему это так медленно? Почему все 8 моих процессоров загружены только на 20%? Возможно, я неправильно настроил свой сервер postgresql? Я ничего не менял в конфигурации по умолчанию. Я неправильно понял, как работает пул потоков? Или пул соединений не работает так, как я предполагал?


person Caligula    schedule 01.11.2015    source источник
comment
Почему вы используете хранимую процедуру для выполнения простого INSERT? Когда вы вызываете SP из SELECT, всегда есть неотъемлемая медлительность из-за переключения контекста. Кроме того, отсутствие переменных связывания еще больше замедляет синтаксический анализ, который должен выполнить Postgres перед выполнением оператора.   -  person Mick Mnemonic    schedule 01.11.2015
comment
Важные вопросы для параллельной скорости вставки: 1. когда происходят COMMIT? При каждой вставке? 2. есть ли в таблице первичный ключ? # 1 хорошо, чтобы избежать блокировки, но плохо для производительности. №2 наоборот.   -  person Daniel Vérité    schedule 01.11.2015
comment
И какая нагрузка на диск?   -  person Alexei Kaigorodov    schedule 01.11.2015
comment
Таким образом, вызов sp занимает около 20 мс, что означает, что в лучшем случае вы можете получить около 50 вызовов в секунду на поток. Вы используете 10 потоков... Откуда у вас номер 6000?   -  person Voo    schedule 01.11.2015
comment
Вы должны закрыть наборы результатов, закрыть оператор и затем закрыть соединение. Также почему одно соединение делает только одну вставку? Это безумие. Одно соединение (на резьбу) может использоваться для нескольких вставок.   -  person Alexandros    schedule 01.11.2015
comment
@Alexandros соединение исходит из пула. Когда он закрыт, физическое соединение на самом деле не закрывается: оно просто возвращается в пул.   -  person JB Nizet    schedule 01.11.2015
comment
@ Джей Би Низе. А причина закрытия соединения есть? Если у вас максимум 10 потоков, то достаточно ровно 10 подключений. Зачем пул на сто подключений, а не пул на 10 подключений?   -  person Alexandros    schedule 01.11.2015
comment
Причина закрытия соединения — вернуть его в пул. Если вы не закроете его, каждый поток будет заблокирован после 10 вставок. Пул имеет 10 подключений. Вы читали код?   -  person JB Nizet    schedule 01.11.2015


Ответы (1)


Когда вы измеряете время выполнения хранимой процедуры, вы, вероятно, не учитываете, сколько времени занимает фиксация. Вы также, кажется, сосредотачиваетесь на ЦП и полностью игнорируете дисковый ввод-вывод и стоимость очистки диска.

300 транзакций в секунду — вполне разумное число для типичной системы с базовым SSD. Так что я бы сказал, что вы делаете коммит после каждой вставки.

Для более быстрого результата вам необходимо:

  • пакетная обработка транзакций, выполняющих множественные вставки;
  • включить commit_delay и установить synchronous_commit = off (имеет некоторый риск потери данных); или
  • получить более быстрый диск

Дополнительные сведения см. в разделе Как повысить производительность вставки в PostgreSQL.

Если вы не возражаете против потери всего набора данных после сбоя, вы также можете использовать unlogged таблицы. Но, действительно, после нечистого выключения ваши данные сотрутся. Прошло. Неисправимый. Так что убедитесь, что вы имеете в виду это.

person Craig Ringer    schedule 02.11.2015