@fastify/under-pressure

repository·main·Indexed 19 days ago

https://github.com/fastify/under-pressure

A process load measuring plugin for Fastify that monitors system resources—including event loop delay, heap usage, RSS memory, and event loop utilization—and automatically handles 'Service Unavailable' responses when thresholds are exceeded. It provides tools for custom pressure handlers, health check functions, a status route for load balancers, and methods like fastify.isUnderPressure() and fastify.memoryUsage() to inspect system state.

Tokens
3.1K
Snippets
13
Records
14
Agent score
15%

What's inside @fastify/under-pressure

  1. Expose a status route for health checks

    main

    You can enable a /status route (or a custom path) that returns { status: 'ok' }. This is useful for load balancers like AWS ELB.

    To customize the route, use the exposeStatusRoute option. You can provide an object to configure routeOpts (Fastify route options), routeSchemaOpts (request schema), routeResponseSchemaOpts (to merge custom response fields), and url (the path).

    fastify.register(require('@fastify/under-pressure'), {
      maxEventLoopDelay: 1000,
      exposeStatusRoute: {
        routeOpts: {
          logLevel: 'debug',
          config: {
            someAttr: 'value'
          }
        },
        routeSchemaOpts: {
          hide: true
        },
        url: '/alive'
      }
    })
  2. Configure process load thresholds

    main

    Register @fastify/under-pressure to monitor system resources. You can set thresholds for event loop delay, heap usage, RSS memory, and event loop utilization.

    If a threshold is set to 0 (the default), that specific check is disabled. When a threshold is exceeded, the plugin automatically handles the request by returning a Service Unavailable error. You can customize the error message and the retryAfter (in seconds) header.

    const fastify = require('fastify')()
    
    fastify.register(require('@fastify/under-pressure'), {
      maxEventLoopDelay: 1000,
      maxHeapUsedBytes: 100000000,
      maxRssBytes: 100000000,
      maxEventLoopUtilization: 0.98,
      message: 'Under pressure!',
      retryAfter: 50
    })
  3. Configure the metric sampling interval

    main

    Use the sampleInterval option (in milliseconds) to set how often metrics are sampled.

    Note: The default value varies by Node.js version. In Node 8 and 10, it is 5ms; in Node 11.10.0 and above, it is 1000ms due to the availability of monitorEventLoopDelay.

    fastify.register(require('@fastify/under-pressure'), {
      sampleInterval: 500
    })
  4. Use fastify.isUnderPressure() to skip heavy tasks

    main

    You can check the current pressure status within your route handlers using fastify.isUnderPressure(). This is useful for skipping complex computations or non-essential tasks when the system is under load.

    fastify.get('/', (request, reply) => {
      if (fastify.isUnderPressure()) {
        // skip complex computation
      }
      reply.send({ hello: 'world'})
    })
  5. Add custom data to the status route

    main

    To include extra information (like database status or custom metrics) in the status route response, implement the healthCheck function and use routeResponseSchemaOpts to define the schema for the additional fields. Note that the status field will always be present.

    fastify.register(underPressure, {
      exposeStatusRoute: {
        routeResponseSchemaOpts: {
          extraValue: { type: 'string' },
          metrics: {
            type: 'object',
            properties: {
              eventLoopDelay: { type: 'number' },
              rssBytes: { type: 'number' },
              heapUsed: { type: 'number' },
              eventLoopUtilized: { type: 'number' },
            },
          },
        }
      },
      healthCheck: async (fastifyInstance) => {
        return {
          extraValue: await getExtraValue(),
          metrics: fastifyInstance.memoryUsage(),
        }
      },
    })
  6. Implement a custom pressureHandler

    main

    Instead of the default Service Unavailable response, you can provide a pressureHandler function. This allows you to log the specific reason for the pressure (the type and value) and decide how to respond to the client.

    If the handler does not call reply.send(), the request will proceed normally. The handler can be defined globally during registration or specifically for a single route via the route config object.

    const fastify = require('fastify')()
    const underPressure = require('@fastify/under-pressure')()
    
    fastify.register(underPressure, {
      maxHeapUsedBytes: 100000000,
      pressureHandler: (request, reply, type, value) => {
        if (type === underPressure.TYPE_HEAP_USED_BYTES) {
          fastify.log.warn(`too many heap bytes used: ${value}`)
        } else if (type === underPressure.TYPE_RSS_BYTES) {
          fastify.log.warn(`too many rss bytes used: ${value}`)
        }
    
        reply.send('out of memory')
      }
    })
  7. Implement custom health checks

    main

    The healthCheck property accepts an async function used to verify external resources (e.g., database connectivity).

    • The function should return a Promise resolving to a boolean or an object.
    • By default, the service is considered unhealthy until this function returns true.
    • The check is triggered either at a regular interval (configured via healthCheckInterval in ms) or every time the status route is called.
    fastify.register(require('@fastify/under-pressure'), {
      healthCheck: async function (fastifyInstance) {
        // Check if db connection is healthy
        return true
      },
      healthCheckInterval: 500
    })
  8. Use a custom error class for pressure events

    main

    By default, the plugin throws a standard error when thresholds are met. You can provide a customError class to change the error type thrown.

    class CustomError extends Error {
      constructor () {
        super('Custom error message')
        Error.captureStackTrace(this, CustomError)
      }
    }
    
    fastify.register(require('@fastify/under-pressure'), {
      maxEventLoopDelay: 1000,
      customError: CustomError
    })
  9. Expose Status Route Configuration

    main

    You can expose a GET route that returns the current health status of the service. This is useful for load balancers or orchestrators (like Kubernetes).

    exposeStatusRoute can be:

    • A string: The URL path (e.g., '/status').
    • An object: Allows customizing the route.

    Object properties:

    • url: The path for the route.
    • routeOpts: Standard Fastify route options (e.g., prefix, schema).
    • routeSchemaOpts: Customization for the response schema.
    • routeResponseSchemaOpts: Customization for the properties of the successful response object.
    fastify.register(require('@fastify/under-pressure'), {
      exposeStatusRoute: {
        url: '/health',
        routeOpts: {
          schema: { /* ... */ }
        }
      }
    })
  10. Register the @fastify/under-pressure plugin

    main

    Register @fastify/under-pressure to monitor system resources (Event Loop delay, Heap usage, RSS, and Event Loop Utilization) and external health checks. When thresholds are exceeded, the plugin can automatically reject requests with a 503 Service Unavailable error or trigger a custom pressureHandler.

    Configuration Options

    OptionTypeDefaultDescription
    sampleIntervalnumber1000Interval in milliseconds to sample memory and event loop metrics.
    maxEventLoopDelaynumber0Maximum allowed event loop delay in milliseconds.
    maxHeapUsedBytesnumber0Maximum allowed heap used in bytes.
    maxRssBytesnumber0Maximum allowed RSS (Resident Set Size) in bytes.
    maxEventLoopUtilizationnumber0Maximum allowed event loop utilization (0 to 1).
    healthCheckfunctionfalseAn async function (fastify) => Promise<boolean> to check external dependencies.
    healthCheckIntervalnumber-1How often to run the healthCheck in milliseconds.
    customErrorErrorFST_UNDER_PRESSUREA custom error object to throw when pressure is detected.
    messagestring'Service Unavailable'Custom error message if customError is not provided.
    pressureHandlerfunctionundefinedA custom function to handle pressure. Receives (req, reply, type, value).
    retryAfternumber10Seconds to set in the Retry-After header when using the default handler.
    exposeStatusRouteobject | stringfalseConfiguration to expose a GET route for health status. See Expose Status Route.

    Pressure Handler Signature

    If you provide a pressureHandler, it is called when any threshold is exceeded. The type parameter will be one of the following constants:

    • TYPE_EVENT_LOOP_DELAY
    • TYPE_HEAP_USED_BYTES
    • TYPE_RSS_BYTES
    • TYPE_HEALTH_CHECK
    • TYPE_EVENT_LOOP_UTILIZATION
    const fastify = require('fastify')()
    
    fastify.register(require('@fastify/under-pressure'), {
      maxHeapUsedBytes: 100 * 1024 * 1024, // 100MB
      maxEventLoopDelay: 100,
      healthCheck: async (fastify) => {
        // check database connection, etc.
        return true
      }
    })