You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Docker环境下NodeJS API高负载超时问题排查与解决

问题描述

我有一个运行在集群模式下的Node.js API,使用PostgreSQL连接池处理I/O操作。本地运行一切正常,但用Gatling做压力测试时,测试进行到一半会出现大量超时。我已经验证过查询性能没问题,问题应该出在配置或代码的错误上。

docker-compose.yml 配置

version: '3.4'

services:
  app1: &app
    container_name: rinha-backend-api-01
    build:
      context: ../../
      dockerfile: Dockerfile
    hostname: app1
    environment:
      DB_URL: "postgres://postgres:123456578@postgres:5432/postgres"
      DB_POOL: 35
      REQ_TIMEOUT: 2000
      NODE_ENV: production
      CLUSTER: true
      CLUSTER_WORKERS: 5
      HTTP_PORT: 8080
    expose:
      - "8080"
    depends_on:
      - postgres
    ulimits:
      nproc: 1000000
      nofile:
        soft: 1000000
        hard: 1000000
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: '0.6GB'
  
  app2:
    <<: *app
    container_name: rinha-backend-api-02
    hostname: app2
  
  postgres:
    container_name: rinha-backend-db
    image: postgres:15.4 
    environment:
      POSTGRES_DB: postgres
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: 123456578
    volumes:
      - ./postgresql.conf:/docker-entrypoint-initdb.d/postgresql.conf
    command: postgres -c config_file=/docker-entrypoint-initdb.d/postgresql.conf
    ports:
      - 5432:5432
    healthcheck:
      test: pg_isready -U $$POSTGRES_USER
      interval: 5s
      timeout: 5s
      retries: 20
      start_period: 10s
    deploy:
      resources:
        limits:
          cpus: '0.4'
          memory: '1.5GB'
    
  nginx:
    container_name: rinha-backend-nginx
    image: nginx:alpine
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - app1
      - app2
    ports:
      - "9999:9999"
    ulimits:
      nproc: 1000000
      nofile:
        soft: 1000000
        hard: 1000000
    deploy:
      resources:
        limits:
          cpus: '0.1'
          memory: '0.3GB'

nginx.conf 配置

worker_processes auto; # create CPU number x 1.5 workers, ex: 4 cpus = 6 workers
worker_rlimit_nofile 500000; # n of file descriptors, each db connection is a socket that is a fd

events {
    worker_connections 1024; # how many simultaneous connections each worker can take
}

http {
    access_log off;
    error_log /dev/null emerg;

    upstream api {
        server app1:8080;
        server app2:8080;
        keepalive 500;
    }

    server {
        listen 9999;

        location / {
            proxy_buffering off;
            proxy_set_header Connection "";
            proxy_http_version 1.1;
            proxy_set_header Keep-Alive "";
            proxy_set_header Proxy-Connection "keep-alive";
            proxy_pass http://api;
        }
    }
}

postgres.conf 配置

listen_addresses = '*'
max_connections = 350
superuser_reserved_connections = 3
unix_socket_directories = '/var/run/postgresql'
shared_buffers = 512MB
work_mem = 4MB
maintenance_work_mem = 256MB
effective_cache_size = 1GB
wal_buffers = 64MB
checkpoint_timeout = 10min
checkpoint_completion_target = 0.9
random_page_cost = 4.0
effective_io_concurrency = 2
autovacuum = on
log_statement = 'none'
log_duration = off
log_lock_waits = on
log_error_verbosity = terse
log_min_messages = panic
log_min_error_statement = panic

index.js 代码

import http from 'node:http';
import cluster from 'node:cluster';
import handler from './handler.js';

const PORT = process.env.HTTP_PORT || 8080;
const TIMEOUT = Number(process.env.REQ_TIMEOUT) || 5000;
const numForks = Number(process.env.CLUSTER_WORKERS) || 5;

if (cluster.isPrimary && process.env.CLUSTER === 'true') {
    console.log(`index.js: Primary ${process.pid} is running`);
    for (let i = 0; i < numForks; i++)
        cluster.fork();
    cluster.on('exit', (worker, code, signal) => {
        console.log(`index.js: worker ${worker.process.pid} died: code ${code} signal ${signal}`);
    })
} else {
    const server = http.createServer(handler)
        .listen(PORT, () => console.log(`index.js:${process.pid} is running at ${PORT}`));
    
    if (process.env.USE_TIMEOUT === 'true') {
        server.setTimeout(TIMEOUT)
        console.log(`Starting with timeout as ${TIMEOUT}ms`)

        server.on('timeout', (socket) => {
            console.warn(`Timing out connection`);
            socket.end();
        })
    }
}

handler.js 代码

import { parse } from 'node:url';
import { DEFAULT_HEADERS } from './utils/server_utils.js';
import { routes } from './routes/personRoute.js';
import PG from './providers/postgres/db/db.js';
import { generateInstance } from './factories/personFactory.js'

const db = new PG();
const personService = generateInstance({ db });

const personRoutes = routes({ personService });

const allRoutes = {
    ...personRoutes,
    default: (req, res) => {
        res.writeHead(404, DEFAULT_HEADERS);
        res.end();
    }
}

async function handler(req, res) {
    const { url, method } = req;
    let { pathname } = parse(url, true);
    pathname = pathname.split('/')[1];
    const route = allRoutes[pathname] ? allRoutes[pathname][method] : allRoutes.default;
    
    await route(req, res);
}

export function closeDB() {
    db.pool.end();
}

export default handler;

personRoute.js 路由代码

const routes = ({ personService }) => ({
    person: {
        POST: async (req, res) => {
            try {
                const data = await once(req, 'data');
                const item = JSON.parse(data);
                validatePersonPost(item);
                const person = new Person(item);
                const id = person.id;
        
                await personService.create(person);
                const headers = {
                    ...DEFAULT_HEADERS,
                    'Location': `/person/${id}`
                };
                res.writeHead(201, headers);
                return res.end();
            } catch (err) {
                if (err.code === PG_DUPLICATE_ENTRY_ERR_CODE) {
                    res.writeHead(422, DEFAULT_HEADERS);
                    return res.end();
                }
                if (err instanceof CreatePersonValidationError) {
                    res.writeHead(400, DEFAULT_HEADERS);
                    res.write(err.message);
                    return res.end();
                }
                res.writeHead(500, DEFAULT_HEADERS);
                return res.end();
            }
        },
        GET: async (req, res) => {
            const { url } = req;
            const { pathname, query } = parse(url, true);
            const params = pathname.split('/');

            if (params.length > 2) {
                try {
                    const id = params[2];
                    validatePersonByIdGET(id, res);
                    const person = await personService.findById(id);
                    if (!person)
                        res.writeHead(404, DEFAULT_HEADERS);
                    else
                        res.write(JSON.stringify(person));
                    return res.end();
                } catch (err) {
                    if (err instanceof GetPersonValidationError) {
                        res.writeHead(400, DEFAULT_HEADERS);
                        res.write(err.message);
                        return res.end();
                    }
                    res.writeHead(500, DEFAULT_HEADERS);
                    return res.end();
                }
            } else {
                try {
                    const term = query['t'];
                    if (term) {
                        const people = await personService.findByTerm(term);
                        res.write(JSON.stringify(people));
                        return res.end();
                    } 
                } catch (err) {
                    res.writeHead(500, DEFAULT_HEADERS);
                    return res.end();
                }
            }
            res.writeHead(400, DEFAULT_HEADERS);
            return res.end()
        }
    },
    'person-count': {
        GET: async (req, res) => {
            try {
                const count = await personService.count();
                res.write(JSON.stringify(count));
                return res.end();
            } catch (err) {
                res.writeHead(500, DEFAULT_HEADERS);
                return res.end();
            }
        }
    }
});

export { routes };

内容的提问来源于stack exchange,提问作者Vinicius Bass

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 04:45:16