Nothing in the profile looked slow, and one function accounted for 80 seconds of a 94-second job.
the profile, sorted by total time:
self calls fn
80.2s 41,208 Money::fromCents
4.1s 1 Report::render
2.8s 412 Repository::find
Money::fromCents was 1.9ms, and was validating the
currency code against a database-backed list.
caching the list: 80.2s → 0.4s
Sorting a profile by self time per call finds the slow functions; sorting by total time finds the expensive ones, and they are rarely the same list. A two-millisecond function is invisible in every trace of a single request and dominates a batch job, which is why profiling the job rather than extrapolating from a request is the only way to see it.