Сокет Node.Js зависает в пуле соединений ClickhouseDb

Я подключаю свое приложение Node.Js к собственной базе данных Clickhouse на EC2. В первой реализации все работало нормально, когда для каждого запроса создавалось новое соединение, а затем соединение закрывалось в конце этого запроса, но когда я переключился на пул соединений, чтобы уменьшить накладные расходы на создание новых соединений по каждому запросу. , в некоторых запросах отображается ошибка зависания сокета. Это отображается только в том случае, если одновременно выполняется несколько запросов для нагрузочного тестирования.

Точное сообщение об ошибке:

Error: socket hang up
    at connResetException (node:internal/errors:705:14)
    at Socket.socketCloseListener (node:_http_client:467:25)
    at Socket.emit (node:events:525:35)
    at TCP.<anonymous> (node:net:301:12) {
  code: 'ECONNRESET'
}

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

const { createClient } = require("@clickhouse/client");

const clickhouseClients = {};

const clickhouseClient = (dbName) => {
  if (!clickhouseClients[dbName]) {
    clickhouseClients[dbName] = createClient({
      host: process.env.CLICKHOUSE_HOST,
      username: process.env.CLICKHOUSE_USERNAME,
      password: process.env.CLICKHOUSE_PASSWORD,
      database: dbName,
      max_open_connections: 10,
      min_open_connections: 2,
      connection_timeout: 120000,
      request_timeout: 400000,
    });

    clickhouseClients[dbName]
      .ping()
      .then(() =>
        console.info(`Successfully connected to ClickHouse database: ${dbName}`)
      )
      .catch((error) => {
        console.error(`Failed to ping ClickHouse database ${dbName}:`, error);
        throw new Error(
          `Failed to initialize ClickHouse client for database ${dbName}`
        );
      });
  }

  return clickhouseClients[dbName];
};

module.exports = clickhouseClient;

Я также пробовал увеличить максимальное количество открытых соединений, но это не сработало. Я ожидаю, что это как-то связано с «keep_alive.idle_socket_ttl» в конфигурации Clickhouse.

Версия узла: 20.10.0 Версия клиента Clickhouse: 0.2.10

Не нужно этого подозревать, это явно задокументировано. Цитирую: значение Keep_alive.idle_socket_ttl должно быть немного ниже, чем конфигурация сервера/LB. Основная причина заключается в том, что из-за того, что HTTP/1.1 позволяет серверу закрывать сокеты без уведомления клиента, если сервер или балансировщик нагрузки закроет соединение до того, как это сделает клиент, клиент может попытаться повторно использовать закрытый сокет, что приведет к ошибка зависания сокета. . clickhouse.com/docs/en/integrations/language-clients/…

Gimby 10.07.2024 15:18

Вы можете попробовать обновить версию клиента, поскольку текущая версия — 1.3.0.

Geoff Genz 10.07.2024 15:44

@GeoffGenz Обновился до последней версии, проблема все еще остается.

Hardik Aswal 10.07.2024 16:01

@Gimby уменьшил время жизни сокета бездействия до 1500 с 2500 по умолчанию после обновления библиотеки до последней версии 1.3.0. Все еще имею ту же проблему

Hardik Aswal 10.07.2024 16:02
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
72
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

Прежде всего, клиент уже использует HTTP-агент внутри себя, который должен управлять соединениями.

Если у вас много баз данных и запросов, вы можете в конечном итоге открыть слишком много сокетов, больше, чем позволяет LB/CH в их конфигурации (поскольку у каждого клиента будет до 10 сокетов в вашей конфигурации); Я не уверен, является ли это основной причиной проблемы, но потенциально это может быть так.

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

const rows = await client.query({
  query: `SELECT * FROM ${dbName}.${tableName} LIMIT 10`,
  format: format,
})

Точно так же вы можете сделать то же самое в методе вставки:

await client.insert({
  table: `${dbName}.${tableName}`,
  format: 'JSONEachRow',
  values: [{ 
    // ...
  }]
})

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

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

По поводу дополнительных настроек:

  clickhouse_settings: {
    send_progress_in_http_headers: 1,
    http_headers_progress_interval_ms: "110000", // UInt64, should be passed as a string
  },

Если у вас нет (очень) длительных запросов, например, чего-то вроде INSERT FROM SELECT, вам это может не понадобиться; см. https://clickhouse.com/docs/en/integrations/language-clients/javascript#keep-alive-troubleshooting

Эта часть выглядит как висячее обещание

clickhouseClients[dbName]
  .ping()
  .then(() =>
    console.info(`Successfully connected to ClickHouse database: ${dbName}`)
  )
  .catch((error) => {
    console.error(`Failed to ping ClickHouse database ${dbName}:`, error);
    throw new Error(
      `Failed to initialize ClickHouse client for database ${dbName}`
    );
  });

Дополнительно пинг не кидает; см. документацию https://clickhouse.com/docs/en/integrations/language-clients/javascript#ping

Учитывая ваш генератор запросов и dbName/tableName, он, вероятно, также будет работать следующим образом:

const queryResponse = gcpQueryBuilder(
  dbClient,
  `${dbName}.${tableName}`,
  response.customer_id,
  selectedGroupBys,
  startDate,
  endDate,
  filters,
  true
);

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

Версия узла: 20.10.0 Версия клиента Clickhouse: 0.2.10

Пожалуйста, обновите клиент до последней стабильной версии. Начиная с версии 0.2.10, внутреннее управление сокетами было переработано, чтобы избежать проблемы зависания сокета.

Другие вопросы по теме