Bootsnap

repository·main·Indexed 23 days ago

https://github.com/rails/bootsnap

A library to optimize Ruby application boot times by caching expensive computations, including $LOAD_PATH scanning, Ruby code compilation (ISeq), and YAML parsing. It provides a CLI for precompiling caches in production environments and supports manual configuration via Bootsnap.setup or environment variables for both Rails and non-Rails applications.

Tokens
3.8K
Snippets
7
Records
26
Agent score
83%

What's inside bootsnap

  1. How Bootsnap optimizes Ruby performance

    main

    Bootsnap optimizes Ruby application boot times through two primary mechanisms:

    1. Path Pre-Scanning: Modifies Kernel#require and Kernel#load to eliminate expensive $LOAD_PATH scans. It uses a cache to immediately resolve the full expanded path of a required file, avoiding multiple failed filesystem lookups.
    2. Compilation Caching:
      • Caches Ruby bytecode compilation results via RubyVM::InstructionSequence.load_iseq.
      • Caches YAML.load_file and JSON.load_file results by converting them into a faster MessagePack (or Marshal) format.

    Path entries are classified as stable (e.g., Ruby install prefix, Gem.path, or Bundler.bundle_path) which do not expire, or volatile (everything else) which are re-scanned every 30 seconds.

  2. When to avoid using Bootsnap

    main

    Avoid using Bootsnap in the following scenarios:

    • Alternative Ruby Engines: Bootsnap relies heavily on MRI-specific features; parts of it are disabled on other engines.
    • Non-local Filesystems: Bootsnap requires a fast filesystem for its cache directory (defaulting to tmp/cache). Using a network mount for the cache directory will likely degrade application performance.
  3. Install and use Bootsnap in Rails

    main

    To use Bootsnap in a Rails application, add the gem to your Gemfile and require the setup script in config/boot.rb immediately after require 'bundler/setup'.

    Important Requirements:

    • Bootsnap requires a writable directory for its cache. By default, it uses tmp/cache. If you are in a read-only environment, you must either provide a writable path via ENV['BOOTSNAP_CACHE_DIR'] or remove the requirement.
    • Bootsnap does not automatically clean up its cache. You should periodically purge tmp/cache/bootsnap* to prevent deployment slowdowns.
    # Gemfile
    gem 'bootsnap', require: false
    
    # config/boot.rb
    require 'bundler/setup'
    require 'bootsnap/setup'
  4. Use `Bootsnap.default_setup` for automatic configuration

    main

    Call Bootsnap.default_setup to automatically configure Bootsnap based on environment variables and application structure. This is the recommended way to initialize Bootsnap in Rails or Rack applications.

    It uses the following environment variables:

    • BOOTSNAP: If not set (or if DISABLE_BOOTSNAP is set), Bootsnap will not start.
    • BOOTSNAP_CACHE_DIR: The directory for the cache. If not provided, it attempts to infer the app root and use tmp/cache.
    • BOOTSNAP_LOAD_PATH_CACHE: Enables/disables load path caching.
    • BOOTSNAP_COMPILE_CACHE: Enables/disables compilation caching.
    • BOOTSNAP_READONLY: Sets the cache to read-only mode.
    • BOOTSNAP_REVALIDATE: Enables cache revalidation.
    • BOOTSNAP_IGNORE_DIRECTORIES: A comma-separated list of directories to ignore.
    • BOOTSNAP_CONFIG: Path to a custom configuration file (defaults to config/bootsnap.rb).
    • BOOTSNAP_LOG: If set, enables logging to $stderr.
    • BOOTSNAP_STATS: If set, enables statistics logging to $stderr at exit.
    • DISABLE_BOOTSNAP, DISABLE_BOOTSNAP_LOAD_PATH_CACHE, DISABLE_BOOTSNAP_COMPILE_CACHE: Used to disable specific features.
  5. Install ISeq compilation caching

    main
    To enable ISeq (Instruction Sequence) compilation caching, call Bootsnap::CompileCache::ISeq.install! with the desired cache directory. This will prepend a mixin to RubyVM::InstructionSequence to intercept loading and use the Bootsnap cache. Note that if the provided cache_dir does not end with a slash, Bootsnap will append -iseq to it; if it does end with a slash, it will append iseq.
  6. Install YAML compilation caching

    main

    To enable YAML compilation caching in Bootsnap, call Bootsnap::CompileCache::YAML.install! with the desired cache directory path. This will initialize the cache and prepend the necessary patch to the ::YAML singleton class.

    Note: The cache directory will be automatically suffixed with -yaml (or yaml if the path ends with a slash) to separate it from other Bootsnap caches.

  7. Fix Bootsnap hangs in QEMU/Docker environments

    main

    When building cross-platform Docker images using QEMU (e.g., via docker buildx), Bootsnap precompilation may cause forked processes to hang. To resolve this, disable parallelization during the precompile step using the -j 0 flag.

    $ bundle exec bootsnap precompile -j 0 --gemfile app/ lib/ config/
  8. Configure Bootsnap manually for non-Rails applications

    main

    If you are not using Rails, or require granular control, you can manually call Bootsnap.setup immediately after require 'bundler/setup'. This allows you to specify the cache directory, directories to ignore, and which specific compilation caches to enable.

    require 'bootsnap'
    env = ENV['RAILS_ENV'] || "development"
    Bootsnap.setup(
      cache_dir:            'tmp/cache',          # Path to your cache
      ignore_directories:   ['node_modules'],     # Directory names to skip.
      development_mode:     env == 'development', # Current working environment
      load_path_cache:      true,                 # Optimize the LOAD_PATH with a cache
      compile_cache_iseq:   true,                 # Compile Ruby code into ISeq cache
      compile_cache_yaml:   true,                 # Compile YAML into a cache
      readonly:             true,                 # Use caches but don't update them on miss
    )
  9. Configure custom compilers in Bootsnap

    main

    You can substitute the default Ruby compiler with custom logic via the Bootsnap configuration file (defaults to config/bootsnap.rb). This is useful for implementing code preprocessing, such as enabling frozen string literals for application code while leaving gems untouched.

    Note: Full support for this feature requires Ruby 4.0.4 or newer. On older versions, it may fail if the Coverage module is enabled.

    # config/bootsnap.rb
    gems_root = File.join(Bundler.bundle_path.cleanpath, "")
    app_root =  File.join(Dir.pwd, "")
    Bootsnap::CompileCache::ISeq.compiler_selector = ->(path) do
      # Enable `frozen_string_literal: true` for app code, but not gems.
      if path.start_with?(app_root) && !path.start_with?(gems_root)
        Bootsnap::CompileCache::ISeq::FROZEN_STRING_LITERAL
      else
        Bootsnap::CompileCache::ISeq::DEFAULT
      end
    end
  10. Monitor Bootsnap cache hits and misses

    main

    You can monitor cache performance by assigning a proc to Bootsnap.instrumentation. The proc receives two arguments: event (one of :hit, :miss, :stale, or :revalidated) and the path.

    Alternatively, you can use Bootsnap.log! to quickly log all events to STDERR. To disable instrumentation, set Bootsnap.instrumentation to nil.

  11. Configure Bootsnap via environment variables

    main

    You can modify the behavior of require 'bootsnap/setup' using the following environment variables:

    VariableDescription
    BOOTSNAP_CACHE_DIRDefines the cache location.
    BOOTSNAP_CONFIGChanges the default config location (defaults to config/bootsnap.rb).
    DISABLE_BOOTSNAPEntirely disables bootsnap.
    DISABLE_BOOTSNAP_LOAD_PATH_CACHEDisables load path caching.
    DISABLE_BOOTSNAP_COMPILE_CACHEDisables ISeq and YAML caches.
    BOOTSNAP_READONLYConfigures bootsnap to not update the cache on miss or stale entries.
    BOOTSNAP_LOGLogs all cache misses to STDERR.
    BOOTSNAP_STATSLogs hit rate statistics on exit (cannot be used if BOOTSNAP_LOG is enabled).
    BOOTSNAP_IGNORE_DIRECTORIESA comma-separated list of directories to skip during scanning (defaults to node_modules).