0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

RenderにデプロイしたRailsアプリで「Ran out of memory (512MB)」が出たので原因と対策をまとめた

0
Posted at

はじめに

Render(無料/Starterプラン、RAM
512MB)にデプロイしているRailsアプリで、管理画面から画像をアップロードした瞬間にこんなエラーが出ました。

Ran out of memory (used over 512MB) while running your code.

アップロードした画像はたったの 2.41MB。「え、そんなんでメモリ足りなくなる?」と思いますよね。私も思いました。

この記事では、

  • 何が起きたのか
  • なぜ起きたのか(初心者向けにメモリの話から)
  • どう直したのか(コード変更なし・無料)

を順番に説明します。同じプランでRailsを動かしている人の役に立てば嬉しいです。


環境

  • Ruby 3.3.6 / Rails 7.2
  • PostgreSQL 16
  • Puma(ワーカー1・スレッド3)
  • 画像保存はCloudinary(activestorage-cloudinary-service gem経由)
  • Render(512MB RAMのプラン、Dockerは使わずネイティブRuby環境)

何が起きたのか

管理画面で小テストのサムネイル画像(2.41MB)をアップロード → 画面が503エラー。Renderのログを見ると:

Ran out of memory (used over 512MB) while running your code.

つまり「アプリが使ったメモリが512MBを超えたので強制終了した」ということです。


なぜ起きたのか

前提:Renderの「512MB」はアプリ全体の合計

このエラーは「1回の処理で512MB使った」という意味ではありません。アプリのプロセス全体が使っているメモリ(RSS)の合計が512
MBを超えた
ときに出ます。

Railsは起動しただけで結構メモリを食う

 Railsは起動時にクラスやモジュール、gem、ライブラリ、フレームワークなどを読み込むためメモリにそれがロードされてしまいどうやらメモリの占有率が高くなってしまうようです。ですので、何もしていなくても、そもそもかなりのメモリを使用してしまっていたみたいです。
引用(https://railsguides.jp/v7.2/autoloading_and_reloading_constants.html)

そこに画像アップロードが来ると…

画像をアップロードすると、サーバー側でこんなことが起きます。

  1. 送られてきたファイルを一時的にメモリに載せる
  2. ActiveStorageがチェックサムを計算する
  3. cloudinary gem がファイルを読み込んでCloudinaryのAPIに送信する

この一時的な上乗せが、ギリギリだったメモリを512MBの天井にぶつけてしまった、というわけです。

✅ ポイント:「2.41MBの画像が重い」のではなく「もともとカツカツで、アップロードがトドメを刺した」

補足:「ImageMagickでリサイズしてるから重い」パターンではなかった

よくあるメモリ不足の原因に「ActiveStorageのvariant(サーバー側でのリサイズ処理)がimage_processing / ImageMagickでメモ
リを大量に使う」というものがあります。でも今回のアプリは画像変換をCloudinaria側に任せていてimage_processing
gemも入れていないので、これは該当しませんでした。
引用(https://qiita.com/koxrtx/items/e77d8e8a45f703f6c36b)


どう直したのか

結論:Renderの環境変数を2つ追加するだけでした。コード変更もデプロイ設定の変更も不要、無料です。
 ※(もう一つのやり方で「jemalloc」という手がありますが、今回はDockerで本番環境は動いていないので、環境変数から手を打ってみます)

Renderのダッシュボード → 対象サービス → Environment に以下を追加:

Key Value
MALLOC_ARENA_MAX 2
RAILS_MAX_THREADS 2

環境変数を変えると自動で再デプロイ&再起動がかかります。

MALLOC_ARENA_MAX=2 とは

Rubyがメモリを確保するとき、内部的にはOSのメモリ管理機能(glibcのmalloc)を使っています。これは速度のためにスレッドごとに「アリーナ」というメモリ領域をいくつも作るのですが、これがメモリの断片化=無駄な肥大化の原因になります。

MALLOC_ARENA_MAX=2
でアリーナの数を2個に制限すると、この肥大化が抑えられてメモリ使用量(RSS)が数十%下がることがあります。これに関しては定番のチューニングで、デメリット(ロック競合のわずかな増加)は実質ないと言っていいレベルです。
引用(https://zenn.dev/inop/articles/49ab079e81b9dc)

RAILS_MAX_THREADS=2 とは

Puma(Railsのサーバー)が1プロセスあたり同時に処理するリクエスト数です。デフォルトは3。スレッドが増えるとその分メモリも使うので、トラフィックが少ないアプリなら2に下げてOK。

「スレッド減らしたら遅くならない?」
→ Rubyは仕組み上(GVL)同時に動けるのは1スレッドだけなので、トラフィックが少ないうちは体感ほぼ変わりません。むしろ1リクエストあたりの応答は微妙に速くなるくらいです。
引用(https://railsguides.jp/v7.2/tuning_performance_for_deployment.html)

結果

設定後、RenderのMetricsでメモリ推移を見ながらもう一度同じ画像をアップロード → メモリがほとんど跳ね上がらなくなりました
image.png

。再起動直後のベースライン自体も下がっていて、ちゃんと効いているのが確認できました。

まとめ

  • Render 512MBプラン + Rails本番起動 = もともとメモリがカツカツ
  • 2.4MBの画像アップロードでも、その一時的な上乗せで天井(512MB)を超えてOOM
  • MALLOC_ARENA_MAX=2RAILS_MAX_THREADS=2 を環境変数に追加するだけで解消(コード変更なし・無料)
  • それでも足りなければ Cloudinary直アップロード / jemalloc / プランアップ

「メモリ足りないエラー」=「すぐ課金」ではなく、まずは環境変数で粘れる、というのが学びでした。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?