LoginSignup
1
1

More than 5 years have passed since last update.

SidekiqでJSの圧縮(Uglifier + ExecJS)が停まるのはこうして回避しよう

Posted at

困っていたこと

Railsで構築したサービスで、modelの更新に応じてJSファイルを生成し直す処理があり、当然ながらそのJSを圧縮しています。その生成+圧縮+配置は sidekiq で処理しているのですが、この処理がフリーズするんですね。。最初はS3への配置で停まってるんだろうなぁと思っていたのですが、調べてみたら圧縮の部分でした。

原因

JSファイルの圧縮は Rails の asset pipeline と同様、Uglifier を使っていました。Ugilifier は ExecJSに処理を委譲していて、ExecJS が使う JSエンジンが The Ruby Racer でした。
で、The Ruby Racerはスレッドセーフだけどデッドロックの問題があるようで(参考)。。。

※実際に問題があった環境のバージョン等は次のとおりです。
(therubyracer以外は関係ないかと思いますが)

  • rails : 3.2.14
  • sidekiq : 2.7.5
  • uglifier : 2.1.1
  • therubyracer : 0.11.4
  • sprockets : 2.2.1
  • execjs : 1.4.0

Node.jsに任せて回避できました

ExecJSのソースを見てみると、JS用のランタイムを切り替えられるようになっているんですね。

gems/execjs-1.4.0/lib/execjs/runtimes.rb
module ExecJS
  module Runtimes
    Disabled = DisabledRuntime.new
    RubyRacer = RubyRacerRuntime.new
    RubyRhino = RubyRhinoRuntime.new
    Johnson = JohnsonRuntime.new
    Mustang = MustangRuntime.new
    Node = ExternalRuntime.new(...)
    JavaScriptCore = ExternalRuntime.new(...)
    SpiderMonkey = Spidermonkey = ExternalRuntime.new(...)
    JScript = ExternalRuntime.new(...)

Nodeだけじゃなくて色々と使えるライブラリがあるのですが、今回は開発と運用の両環境に入っていたNodeを採用しました。で、一発で上手くいったので他のランタイムは試していません。。他のでも上手くいったよーという方は是非教えてください!

ちなみにNodeが入っていない環境だと、こんな例外を出力して教えてくれます。

.../lib/execjs/runtimes.rb:66:in `from_environment':Node.js (V8) runtime is not available on this system (ExecJS::RuntimeUnavailable)

手順

sidekiqの起動時に環境変数で指定する

$ EXECJS_RUNTIME=Node sidekiq

基本は ExecJS が使うJSランタイムを Node にするだけだから簡単な話なのですが、この方法に落ち着くまでちょっとはまりました。ExecJS の中を見てみると

gems/execjs-1.4.0/lib/execjs/runtimes.rb
    def self.autodetect
      puts "#{from_environment}"
      from_environment || best_available ||
        raise(RuntimeUnavailable, "Could not find a JavaScript runtime. " +
          "See https://github.com/sstephenson/execjs for a list of available runtimes.")
    end

    def self.best_available
      runtimes.reject(&:deprecated?).find(&:available?)
    end

    def self.from_environment
      if name = ENV["EXECJS_RUNTIME"]
        if runtime = const_get(name)
          if runtime.available?
            runtime if runtime.available?
          else
            raise RuntimeUnavailable, "#{runtime.name} runtime is not available on this system"
          end
        elsif !name.empty?
          raise RuntimeUnavailable, "#{name} runtime is not defined"
        end
      end
    end

ってな感じになっていたので、initializerで指定しようとしました。

/config/initializers/hoge.rb
ENV["EXECJS_RUNTIME"] = "Node"   #ダメだった

でもこれじゃダメでした。ExecJSがJSランタイムを選択するのは lib のロード時だけなので、initializersの読み込み前なんです。で、結局はプロセスの起動時に環境変数で指定するというこの方法に落ち着きました。

$ EXECJS_RUNTIME=Node sidekiq

ひとまず、めでたし。他のライブラリ/ランタイムの方が早いよorリソース消耗しないよーという情報があったら是非教えてくださいっ

1
1
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
1
1