为什么流式传输响应比 Node.js / Express 中发送常规响应慢得多?

Why is streaming a response so much slower than sending a regular response in Node.js / Express?

我正在使用 Node.js / Express 服务器查询 Postgres 数据库并将结果作为 CSV 文件发送到浏览器。结果集可能会变得非常大(例如 50+ MB),所以我认为将结果直接从数据库一直传输到浏览器是明智的做法:

const QueryStream = require('pg-query-stream');
const { Transform } = require('json2csv');

const pool = require('./pool-instance');

// ...some request handling code...

const client = await pool.connect();
const stream = client.query(new QueryStream(q.text, q.values));

stream.on('end', () => {
  client.release();
});

const json2csv = new Transform({}, {objectMode: true});
res.set('Content-Type', 'text/csv');
res.set('Content-Disposition', 'attachment;filename=export.csv');

// pipe the query results to the Express response object. 
stream.pipe(json2csv).pipe(res);

这在本地测试时运行良好,但当我通过网络在小型服务器上测试它时,它流式传输一个 1.3 MB 的文件需要 20 多秒。所以,我尝试以更传统的方式做事:

// Just load the full query results in memory
const results = await pool.query(q);

// Create the full csv text string from the query results
const csv = await parseAsync(results.rows);

res.set('Content-Type', 'text/csv');
res.set('Content-Disposition', 'attachment;filename=export.csv');

res.send(csv);

同一文件只用了 2 秒

这是为什么?为什么流媒体方法这么慢?

我遇到了同样的问题,看来原因与 node-pg 无关,而是与 Node Streams 工作流有关。 问题在这里描述:NodeJS Copying File over a stream is very slow

In the Node.js documentation about stream buffering, it says:

Both Writable and Readable streams will store data in an internal buffer that can be retrieved using writable.writableBuffer or readable.readableBuffer, respectively.

The amount of data potentially buffered depends on the highWaterMark option passed into the stream's constructor. For normal streams, the highWaterMark option specifies a total number of bytes. For streams operating in object mode, the highWaterMark specifies a total number of objects....

A key goal of the stream API, particularly the stream.pipe() method, is to limit the buffering of data to acceptable levels such that sources and destinations of differing speeds will not overwhelm the available memory.

根据此处的 QueryStream 构造函数定义:https://github.com/brianc/node-postgres/blob/master/packages/pg-query-stream/src/index.ts#L24

您可以将 highWaterMark 设置覆盖为更高的值(默认为 100)。 此处我选择了 1000,但在某些情况下,您可能希望增大或减小该值。我建议小心使用它,因为如果您在生产中 运行 这可能会导致 OOM 问题。

new QueryStream(query, [], {highWaterMark: 1000});

或者你的情况:

const stream = client.query(new QueryStream(q.text, q.values, {highWaterMark: 1000}));

此外,您应该确保没有其他使用它传输的流具有较低的 highWaterMark 值,这可能会导致进程变慢。

对我来说,这提高了速度,与直接从数据库下载一样快。


另外,我发现在低性能下CPU,我的流还是太慢了。在我的例子中,问题是 Express 的 compression() 使用 gzip 压缩响应。我在反向代理端 (traefik) 设置了压缩,一切正常。