Deno CompressionStream 每块都 flush,检查分块导致的压缩体积变化
Deno CompressionStream 曾在每次写入后同步 flush,使分块增加压缩开销。2.9.7 调整写入策略,验收需同时检查压缩长度和完整解压结果。
把相同字节流分成更多小块后,Deno CompressionStream 生成的 gzip 文件明显变大,可以检查编码器是否在每次写入后都做了同步 flush。输入内容相同,分块方式仍可能改变压缩输出。
下面的验证代码同时消费输出,避免单独等待 writer 而被流背压阻塞:
async function compress(bytes: Uint8Array, size: number) {
const input = new ReadableStream<Uint8Array>({
start(controller) {
for (let offset = 0; offset < bytes.length; offset += size) {
controller.enqueue(bytes.slice(offset, offset + size));
}
controller.close();
},
});
return new Uint8Array(await new Response(
input.pipeThrough(new CompressionStream('gzip')),
).arrayBuffer());
}
const bytes = new TextEncoder().encode('repeatable text '.repeat(4096));
const one = await compress(bytes, bytes.length);
const small = await compress(bytes, 64);
console.log(one.length, small.length);
旧实现把每一块写入后都同步 flush。对于 deflate 类编码,这会增加用于 flush 的空块,也影响后续跨块匹配,所以块越多,额外开销越明显。不是网络在重复传输数据,也不需要修改输入文本来避开。
Deno 2.9.7 包含写入路径修补:每块输入不再触发 flush,流结束时仍完成编码尾部。处理覆盖 deflate、deflate-raw 和 gzip。其他压缩器或不同版本可以选择不同的合法编码,因此“相同输入必须总是产生完全相同的压缩字节”不能当作通用规范结论。
升级后用同一输入分别测试单块和多种块大小,检查压缩长度以及解压后的内容。验收不能只比较文件变小:两份输出都必须完整解压回原字节,尤其要检查关闭流后的尾部没有丢失。业务如果保存压缩结果的散列,也应知道合法编码变化可能使散列改变,即使解压内容保持一致。
这一修补针对批量字节压缩的写入策略,不是让所有低延迟响应都停止 flush。SSE 等场景可能需要及时发出小块;服务器响应缓冲和 standalone CompressionStream 的编码策略应分别处理。
大文件验证可以使用流式消费,上面的全量 arrayBuffer() 只是有限样本。不要为测试压缩比把生产大文件整份读入内存,也不要把压缩流取消时资源回收的问题与每次写入的 flush 行为混为一谈。


