10.1.4 regression: path spelling validation calls readdir() on every request, O(n) latency for large directories
Evaluación
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Aptitud para principiantes
- 52/100
- Tipo de issue
- Error
- Claridad
- Bastante claro
- Estado de actividad
- Activo
- Stack tecnológico
- javascript, node.js
- Área
- backend, performance
Línea de trabajo
Start with getPathSpellingStatus() and compare its behavior between versions 10.1.3 and 10.1.4. Run the supplied repro.mjs against both versions, then trace the default wildcard route to identify where readdir() is called. Done means restoring request latency that does not scale with directory size while preserving the spelling-validation behavior where it is needed.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Prerequisites
- I have written a descriptive issue title
- I have searched existing issues to ensure the regression has not already been reported
Last working version
10.1.3
Stopped working in version
10.1.4
Node.js version
24.21.0
Operating system
Linux
Operating system version (i.e. 20.04, 11.3, 10)
Ubuntu, kernel 6.8.0 (app runs in node:24-alpine; static root is an ext4 bind mount)
💥 Regression Report
Since 10.1.4 (the fix for GHSA-r799-r9gc-m956), getPathSpellingStatus() runs on every request served through the default wildcard: true route. For each path segment it calls readdir() on the parent directory and then entries.includes(segment). So every request costs time proportional to the number of entries in each directory along the path.
We serve product photos from one flat directory with ~815k files. After upgrading from 10.1.3 to 10.1.4, median latency for /static/product/photo/.webp in production went from ~3 ms to ~1.8 s (p90 ~4 s, p99 ~10 s). That includes 304 Not Modified responses and requests for non-existent files. Files in small directories under the same root are unaffected (~7 ms). A single stat() of the same file takes <1 ms on the host and inside the container, so the filesystem is not the bottleneck.
The spelling check only matters on case-insensitive filesystems. On case-sensitive ones (ext4, the default on Linux), a case alias cannot resolve to a different file, yet the full directory scan still runs on every request.
It is also an amplification vector: any unauthenticated, well-formed request under a large directory triggers a full readdir() of it.
Steps to Reproduce
// repro.mjs — node repro.mjs 10.1.3|10.1.4 (after installing that version) import Fastify from 'fastify' import fastifyStatic from '@fastify/static' import { mkdirSync, writeFileSync } from 'node:fs' import { join } from 'node:path' import { tmpdir } from 'node:os'const root = join(tmpdir(), 'fastify-static-large-dir')
const dir = join(root, 'photo')
mkdirSync(dir, { recursive: true })
for (let i = 0; i < 300_000; i++) writeFileSync(join(dir,f${i}.webp), '')const app = Fastify()
await app.register(fastifyStatic, { root, prefix: '/static/' })
await app.ready()
const n = 20
const t = performance.now()
for (let i = 0; i < n; i++) {
const res = await app.inject(/static/photo/f${i * 1000 + 7}.webp)
if (res.statusCode !== 200) throw new Error(String(res.statusCode))
}
console.log(process.argv[2], 'avg ms/request:', ((performance.now() - t) / n).toFixed(1))
Results (macOS, 300k files, sequential requests):
| version | avg per request |
|---|---|
| 10.1.3 | 1.9 ms |
| 10.1.4 | 419 ms |
Expected Behavior
Serving a file should not scale with the size of its directory, as in 10.1.3. Some possible approaches:
Detect once per root at startup whether the filesystem is case-insensitive (e.g. stat an upper-cased variant of a known entry), and skip the spelling check on case-sensitive filesystems.
Or cache the directory listing per directory, invalidated by mtime, and use a Set instead of Array#includes.
Or provide an option to opt out of the spelling check, for deployments on a known case-sensitive filesystem.
As a workaround we pinned @fastify/[email protected].
- Lenguaje dominante
- JavaScript
- Estrellas
- 498
- Forks
- 132
- Merge medio
- 8 d 20 h
- PR fusionados (30 d)
- 2
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Sin plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de fastify/fastify-static
-
bug: directory index route root priority is overwritten when wildcard is falsePosiblemente ocupada @TalhaHunter101 la tomó hace 17 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 84/100
fastify/fastify-static#617 ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
fastify/fastify-static#612 · 1 comentario ·
-
Use built-in glob from `node:fs`Posiblemente ocupada @Ankitraj-sharma la tomó hace 29 días. Abiertogood first issue
Dificultad 3/5 1-2 días Aptitud para principiantes 25/100
fastify/fastify-static#551 · 8 comentarios · 1 reacción ·
-
feature request
Dificultad 5/5 Más de una semana Aptitud para principiantes 25/100
fastify/fastify-static#432 · 2 comentarios ·
-
discussion
Dificultad 5/5 Más de una semana Aptitud para principiantes 25/100
fastify/fastify-static#385 · 2 comentarios ·
Todos los issues de fastify/fastify-static
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 88/100
micromatch/picomatch#243 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
Los mantenedores suelen responder en 1 día
-
enhancement
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
b310-digital/teammapper#1515 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 64/100
c1overzz/dsh-bili-scroll#153 ·
Los mantenedores suelen responder en 1 día
-
01 type: bug 21 status: confirmed 41 lvl: easy 62 scope: viewer
Dificultad 1/5 Menos de una hora Aptitud para principiantes 90/100
Los mantenedores suelen responder en 1 día