Elixirを使ってSidekiqを操作する

  • 29
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

ex-logo.pngside-logo.png

はじめに

これは 【その1】ドリコム Advent Calendar 2015 の19日目の記事です。
18日目はYさんの記事でした。
【その2】ドリコム Advent Calendar 2015の18日目はwasbi01さんの記事でした。

寺社自社で開発/運用している、Elixirを利用した広告配信システムについて紹介したいと思います。

自己紹介

@ohrdev
普段は写経(仏教的な意味で)や仏像彫り、寺社仏閣巡りをしています。
空いた時間はドリコムという会社で広告周りのシステムの開発をしています。
好きなbehaviourはgen_eventです。

Elixirについて

ElixirErlangのVM上で動作する比較的新しいプログラミング言語です。
Erlangで実装されている為、分散、耐障害性、ソフトリアルタイムといった(Erlangの)特徴を兼ね備え、Elixir独自の、マクロ、メタプログラミング、ポリモーフィズムといった拡張機能を持っています。

採用背景

実装言語はいくつか候補(Ruby,Go,Scala,etc)がありましたが、API周りは特に要件として、

  • 無停止稼動が可能
  • 障害耐性がある
  • スケールし易い(スケールの程度が予想し辛い)

があった為、「ああ、あれだな」「あいつだ」「例のアレね」という事で、Erlangを採用する事となりました。

が、Erlangを導入するとなった時はだいたい、

  • Erlangのシンタックスがつらい(LL畑の人からの評判はあまりよくない印象です)
  • そもそもErlangエンジニアの確保ができない
  • 知見/情報が少ない

みたいな意見が出るのではないでしょうか。(実際、同様の意見が出ました)

ですが、

  • Erlangのシンタックスがつらい
    |> Elixirのバージョン1.0がリリースされた
    |> Erlangのシンタックス以外で(ある程度安定した言語で)書ける

  • そもそもErlangのエンジニアの確保ができない
    |> じゃあ他の言語のエキスパートが確保できるのか?
    |> 結局言語の問題では「無い」

  • 知見/情報が少ない
    |> 英語ならそれなりにある
    |> 日本語じゃなくておk

  • 性能面は大丈夫?
    |> ベンチを計測した
    |> ErlangとElixirでほぼ性能差は無い

というのがElixirを採用した理由です。

使い方

APIサーバー部分でElixirを利用しています。
管理画面はRails、非同期処理部分はSidekiqによる実装です。

APIサーバー

小さな(軽量な)リクエストを大量に捌く用途で、大きな(重い)リクエストはあまりありません。(重いリクエストはざっくりと全体の3%程度です)
maruというgrapeのElixir実装(APIのDSL)を利用して実装しています。
バックエンドDBはRedisで、コネクション管理はErlangのpoolboyを使って自前実装しています。

重い処理はどうしている?

軽量な処理はAPIサーバーで処理し、複雑なビジネスロジックが絡む重たい処理はElixirからExqを使ってSidekiqに直接ジョブをenqueueしています。

image2.png

Exq

ExqはSidekiqのElixir実装です。
ExqはResque/Sidekiqと互換性があるので、ExqからResque/Sidekiqにジョブのenqueueができます。

Enqueue側

enqueueはExqのExq.Enqueuer.enqueueを使って行います。

enqueue処理
defmodule MyApp.Job do
  @pid :exq_enqueuer # Exqのプロセス名
  @queue_name "my_queue" # Enqueue対象のキュー名
  @worker "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"

  def enqueue(args) do
    Exq.Enqueuer.enqueue(@pid, @queue_name, @worker, args)
  end
end

@pid は後述のExqのプロセス起動時にsupervisorで指定するプロセス名です。
@queue_name はenqueue対象とするキュー名です。
@worker はActiveJobのAdapterのクラス名です。

SupervisorでExqを起動する際、以下の様になります。

Supervisor
defmodule MyApp.Supervisor do
  use Supervisor

  def start_link do
    Supervisor.start_link(__MODULE__, [], name: :my_app)
  end

  def init([]) do
    children = [[
      host: "myappredis.com", # host
      port: 6379, # port
      namespace: "my_job", # namespace
      queues: ["my_queue", "another_my_queue"], # target queues
      name: :exq_enqueuer]] # process name

    supervise(
      children, 
      strategy: :one_for_one,
      max_retstarts: 30,
      max_seconds: 5)
  end
end

host はバックエンドDBのRedisのホスト名です。
port はバックエンドDBのRedisのポート名です。
namespace はバックエンドDBのRedisのネームスペースです。
queues はExqで処理対象とするキュー名のリストです。
name はExqのプロセス名です。

Dequeue側

ExqでenqueueされたJobは、Sidekiqで以下のクラスでdequeueされ処理が行われます。
queue_asで、MyApp.Jobで指定されたMyApp.Jobのキュー名を指定します。

dequeue処理
class MyAppJob << ActiveJob::Base
  queue_as :my_queue # MyApp.Jobの@queue_nameで指定されたキュー名

  def perform(args)
    # my awesome heavey job
  end
end

また、Redisの設定を以下の様に指定しています。

initializerでのRedis設定
...

# connection設定
redis_conn = proc do
  Redis::Namespace.new(
    "my_job", # Supervisorのnamespaceで指定したnamespace
    redis: Redis.new(url: "myappredis.com:6379/0"), # Supervisorのhost,portで指定したRedisのURL
  )
end

# server設定
Sidekiq.configure_server do |config|
  config.redis = ConnectionPool.new(size: ENV['REDIS_SERVER_POOL_SIZE'], &redis_conn)
  ...
end

# client設定
Sidekiq.configure_client do |config|
  config.redis = ConnectionPool.new(size: ENV["REDIS_CLIENT_POOL_SIZE"], &redis_conn)
  ...
end

...

redis_conn のnamespace、urlには、ExqのSupervisorで指定した namespacehostport に対応したパラメータ値を設定します。

ちなみに、SidekiqからExqへのジョブのenqueueも当然できます。(が必要がなかったのでやっていませんが)

使ってみた所感は?

4ヶ月ほど運用してみましたが、今の所大きな問題は出ていません。
Sidekiqのダッシュボードは以下の様になっています。

sidekiq1.png
4ヶ月で処理した処理が重めのジョブ数はこんな感じです。これらは、ほぼ全てがExqからenqueueされたものです。

sidekiq2.png
半年の遷移を見ると、リリース時に比べるとかなり増えている事が見て取れます。

スケールに関してですが、リリース日から今現在までで、サービスの規模がDAUベースで30倍程になっていますが、その為の構成変更や特別なチューニング等は行っておらず、単純にサーバーのコア数や台数の変更のみで対応しました。

まとめ

広告の配信システムのAPI部分をElixirで実装/運用しました。
Exqを使うことでSidekiqとキューのやりとりを実現しました。

参考資料

地獄のElixir入門
Elixirを本番環境で使ってみたという事例紹介

おまけ

来年、2016/1/11にElixir Meetup #1 in Drecomのイベントを企画しています。
そこで、Elixirを使ってアプリを実際にプロダクション運用した際の地雷とその処理内容について話したいと思います。

次は

【その1】ドリコム Advent Calendar 2015 の20日目はNarazakaさんの記事です。
【その2】ドリコム Advent Calendar 2015の20日目はjizojpさんの記事です。