Skip to content

perf: a one-route Stacks API server pays 8.9 MiB of RSS for router surface it never touches #2590

Description

@chrisbbreuer

Measured on Bun 1.4.1, Apple M3 Pro, three fresh processes each, RSS after a forced GC:

Imported RSS
nothing 11.8 MiB
@stacksjs/bun-router (its barrel) 20.7 MiB
@stacksjs/router 25.9 MiB

So the published router runtime's barrel costs 8.9 MiB, and the Stacks layer on top of it another 5.2 MiB, before a single route is registered. The barrel is 529 KB of JavaScript across dist/index.js and three chunks, and it carries the whole surface: websockets, sessions, caching, container, observability, testing, docs, CLI.

For context, bench/memory at its 25,000 req/s profile (20s load, 40s idle, settled median):

Target Settled idle RSS
Bun.serve baseline 14.3 MiB
Hono 46.7 MiB
Elysia 50.5 MiB
Stacks (stock defaults) 52.4 MiB
Fastify 87.9 MiB
Express 95.0 MiB

Stacks loses that row to Hono by 5.7 MiB and to Elysia by 1.9 MiB - both smaller than the 8.9 MiB the barrel costs on its own. A server that loaded only the routing it uses would take the row outright.

The barrel is loaded because @stacksjs/router's entry point does export * from '@stacksjs/bun-router', deliberately, so userland gets the whole surface from one import. That is a real API decision and not something to quietly reverse - an app that imports @stacksjs/router today can reach any of it.

What would need to happen

  • @stacksjs/bun-router (separate repository) exposes the serve/dispatch path through an entry point that does not pull websockets, sessions, cache, container, testing, docs and the CLI in with it. Its exports map already has subpaths, so this is about what dist/index.js re-exports eagerly rather than about adding a map.
  • Stacks then gets a narrow entry - the equivalent of "give me a server and routes" - that an API-only or serverless deployment can import instead of the full barrel. The existing barrel keeps working unchanged.
  • bench/memory gains a row for it, so the saving is measured rather than asserted.

Filed with numbers rather than fixed here because it spans two repositories and changes a public entry point.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions