Я подключаю свое приложение 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
🤔 А знаете ли вы, что...
Node.js - это среда выполнения JavaScript, разработанная на основе JavaScript V8 движка, созданного Google.
Я не думаю, что в вашем случае использования вам нужен отдельный клиент для каждой базы данных, поскольку это может привести к увеличению накладных расходов, а вы подключаетесь к одному и тому же хосту.
Прежде всего, клиент уже использует 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, внутреннее управление сокетами было переработано, чтобы избежать проблемы зависания сокета.