The Symptom
Symptom
Root Cause Analysis
Root Cause
We used the `axlsx` gem to generate Excel files, and then attached the generated file to an `Export` ActiveRecord model using ActiveStorage (`export.file.attach(io: File.open(temp_file))`). However, because the files were generated locally on the disk, ActiveStorage decided it needed to read the *entire* file into a string in RAM to calculate the MD5 checksum before uploading it to S3. For a 2GB Excel file, this forced Ruby to allocate 2GB of contiguous memory, instantly crashing the 2GB worker node.
# The fatal flaw: ActiveStorage reading whole files into memory for checksums
class ExportWorker
def perform(export_id)
export = Export.find(export_id)
temp_file = generate_massive_excel(export)
# This line reads the entire 2GB file into RAM instantly
export.file.attach(io: File.open(temp_file), filename: "export.xlsx")
end
end
The abstraction provided by ActiveStorage completely hid the fact that it was not streaming the upload, but rather buffering it into memory.
Resolution
Resolution
# The fix: Direct multipart S3 streaming
s3_resource = Aws::S3::Resource.new
obj = s3_resource.bucket("exports").object("temp/export.xlsx")
obj.upload_file(temp_file_path) # AWS SDK streams chunks efficiently
blob = ActiveStorage::Blob.create_before_direct_upload!(
filename: "export.xlsx",
byte_size: File.size(temp_file_path),
checksum: "bypass" # Skip memory-heavy checksum
)
export.update(file: blob.signed_id)
Discussions 0