Feature #44396
openReduce memory usage by not loading rbpdf until a PDF is generated
Description
Redmine loads the rbpdf gem at every boot, even though it is only needed for PDF export. This patch defers loading it until a PDF is actually generated. In my measurements, this reduced the median RSS by about 12 MB, from 217.8 MB to 205.9 MB. Processes that do not export PDFs can therefore avoid loading rbpdf and its dependencies for their whole lifetime.
The first patch fixes the existing RuboCop offenses in lib/redmine/export/pdf.rb so that moving ITCPDF in the second patch does not require updating .rubocop_todo.yml.
The second patch makes the following changes:
1. Adds require: false to gem 'rbpdf' so that Bundler does not load rbpdf at boot.
2. Moves the ITCPDF class to a new file, lib/redmine/export/pdf/itcpdf.rb, where rbpdf is explicitly required. RDMPdfEncoding stays in lib/redmine/export/pdf.rb because it does not depend on rbpdf.
3. Excludes the new file from eager loading with loader.do_not_eager_load. The file remains autoloadable, so it and rbpdf are loaded when Redmine::Export::PDF::ITCPDF is first referenced.
I measured the effect by booting a production environment and reading the RSS of the process:
for i in 1 2 3 4 5; do bin/rails r -e production 'GC.start; puts "RSS=#{`ps -o rss= -p #{$$}`.to_i}KB rbpdf=#{$LOADED_FEATURES.grep(%r{/rbpdf-}).any?}"'; done
macOS 15 (arm64), Ruby 3.4.8, SQLite3, no plugins, assets already precompiled, 5 runs each:
| RSS median | RSS range | rbpdf loaded | |
|---|---|---|---|
| trunk | 217.8 MB | 212.5–218.7 MB | yes |
| patched | 205.9 MB | 205.5–208.5 MB | no |
With the patch, rbpdf, rbpdf-font, and htmlentities are no longer loaded at boot.
With a preforking server such as Puma in cluster mode, loading rbpdf in the master process allows its memory to be shared with workers through copy-on-write. An installation that exports PDFs often can keep the old behavior by adding require 'rbpdf' to config/additional_environment.rb.
Files
No data to display