読者です 読者をやめる 読者になる 読者になる

inFablic

Fablic開発者ブログ

バナーのクリック率をFirebaseで計測し、BigQueryで集計し、Redashで可視化する

f:id:shoby:20170523195419p:plain

こんにちは。エンジニアのshobyです。

FablicではRedashを導入し、主にサーバーサイドのデータやログを分析してきましたが、最近ではRedashにFirebaseとBigQueryを組み合わせ、クライアントサイドのイベントも分析するようになりました。

直近では、クライアントサイドのイベントを、アプリの機能改善だけではなく、キャンペーンバナーなどのコンテンツの効果測定にも利用しています。

Fablicでのバナークリック率計測の事例を元に、Firebaseで計測し、BigQueryで集計し、Redashで可視化する方法をお伝えします。

概要

  • Firebase+BigQueryイベント集計のメリット、デメリット
  • Firebaseでのログ計測
  • BigQueryでのログ集計
  • Redashでの可視化

Firebase+BigQueryイベント集計のメリット、デメリット

コンテンツの効果測定を行う際、一般的なサーバー側でログを仕込む方法と比べ、Firebaseを利用する場合のメリット、デメリットをお伝えします。

計測は簡単、集計が大変というのが特徴になるかと思います。

メリット

サーバー側のログ計測と比べた主なメリットは以下の通りです。

  • クライアントエンジニアだけの作業で完結する
  • 計測コードを記述するのが短時間で済む
  • カスタムイベントパラメータで柔軟な計測ができる
  • 自動でBigQueryにエクスポートされるのでログの管理が不要
  • 全イベントログの生データが参照できる

計測に関するエンジニアの実作業はほぼ必要なく、ログの管理が必要ないのが主なメリットです。

デメリット

デメリットは集計が大変なことです。 Firebaseからエクスポートしたデータは、BigQuery上ではRECORD型という非正規化されたスキーマで保存されており、WITHIN句などの独特な形式でクエリを書く必要があります。

実際のスキーマは以下をご参照ください。

support.google.com

Firebaseでのログ計測

バナーのクリック率を計測する場合、クライアントで、表示とクリックそれぞれのアクションに対して、バナーのIDをパラメータとして付与しつつ計測します。

フリルでは、以下のようなイベントを投げています。

  • show_banner
    • banner_id
  • click_banner
    • banner_id

banner_idを元に2つのイベントを突き合わせ、クリック率を計測することができます。

BigQueryでのログ集計

イベントを集計するのに関係するフィールドは以下の部分になります。

フィールド名 データタイプ 説明
event_dim RECORD このバンドル内のイベントに関する情報を格納する繰り返しレコード。
event_dim.name STRING このイベントの名前。
event_dim.params RECORD このイベントに関連付けられたパラメータを格納する繰り返しレコード。
event_dim.params.key STRING イベント パラメータのキー。
event_dim.params.value.int_value INTEGER イベント パラメータの整数値。

BigQuery Export のスキーマより一部抜粋

event_dim および event_dim.params はrepeatedな RECORD 型であり、WITHIN RECORD 句を用いた集計が必要です。

実際の集計クエリは以下のような形です。

SELECT
  banner_id,
  total_show_count,
  total_click_count,
  total_click_count/total_show_count as click_rate
FROM (
  SELECT
    banner_id,
    SUM(show_count) as total_show_count,
    SUM(click_count) as total_click_count,
  FROM (
    SELECT
      event_dim.params.value.int_value as banner_id,
      SUM(event_dim.name = 'show_banner') WITHIN RECORD AS show_count,
      SUM(event_dim.name = 'click_banner') WITHIN RECORD AS click_count
    FROM  your_table_name
    WHERE (event_dim.name = 'show_banner' OR event_dim.name = 'click_banner')
      AND event_dim.params.key = 'banner_id'
  )
  GROUP BY banner_id
)
ORDER BY banner_id

1レコードごとに表示回数、クリック回数を計測し、それらをまとめて総表示回数、総クリック回数を計測しています。

Redashでの可視化

FablicではBigQueryとRedashを接続し、Dashboard機能を用いて可視化しています。

以下のように、デバイスごとのクリック率を、データとグラフを元に、一覧で見られるようなDashboardを生成しています。

f:id:shoby:20170523192228p:plain

Redashにはクエリの定期実行機能が付いているため毎日更新されるDashboardを生成でき、日々の改善に繋げることができます。

まとめ

Fablicでは、バナーのクリック率を計測にFirebaseとBigQueryを用いるようになりました。

サーバー側でログを仕込む方法と比べ、FirebaseとBigQueryを使うことで、計測に関するエンジニアの実作業はほぼ必要なく、ログの管理が不要になります。 ただし、集計は少々複雑です。

Firebaseでイベントを計測し、BigQueryで集計し、Redashで可視化することで、日々の改善に繋げることができます。

Vimの勉強会がFablic本社で開催されました

去る2017-05-22 (月) の夜に、恵比寿のFablic本社にてVimの勉強会 Fablic.vim #1 を主催しました。

f:id:infablic:20170524114832j:plain

発表

今回はFablic社から2名発表を準備しておきました。また、当日の飛び入り発表で、他にlambdalisueさんやrbtnnさんによる発表がありました。

発表は初心者向けと上級者向けそれぞれ用意しておきました。

“Vim初級から中級のあいだ”

https://docs.google.com/presentation/d/1thVwlWFzy5cCXGynInvixRCvlB1BbBKP1Z21Ub_lBqs/edit?usp=sharing by James

JamesさんはFablicのソフトウェアエンジニアで、もっぱらGoのコードを書いています。今回は、VimでどのようにしてGoの仕事をやっているかについて述べていました。キーボードの使い方その他周囲の環境も要チェックです。

当日のデモでは:GoRunがうまく動かないというトラブルがありました。会場にきていた thinca さんの quickrun.vim を使うと解決することが示されました。

“Data.Optional”

https://docs.google.com/presentation/d/1DaFn6uNSCt0St3fLp30AgIPKcNPSd5PIeig-wQJPezc/edit?usp=sharing by ujihisa

f:id:infablic:20170524114953j:plain

Jamesさんの発表ではVimを用いてどのようにプログラミングを行っているかでしたが、こちらはVim pluginを作るときのVim scriptのためのライブラリの話でした。vital.vimについて足早に説明したあと、その1モジュールであるData.Optionalについてを、他の言語における同じ概念の実装を比較しながら解説しました。具体的に登場した言語はVim script以外にHaskell, Scala, Clojure, Elixirでした。

飛び込み発表

  • by ujihisa: 使ってるプラギンを雑に紹介
    • quickrun, vimshellなど
    • painter.vim
    • Vim上で動くマリオ実装であるmario.vimの衝撃。
    • いずれも作者が会場にいるので、便利
  • by lambdalisue: lista
    • neovim上で動く /n のやつを便利化したプラギンの紹介
    • 普段pythonで仕事している彼にとっては、pythonでplugin実装できるneovimはかなり便利とのこと
  • by lambdalisue: gina
    • 現在最も有名なgitプラギンであるginaの紹介
    • コマンドを覚えなくても <tab>? で一覧とかが見れるので便利。作者も頻繁に使用
  • by rbtnn: puyo.vim
    • Vim上で動くぷよぷよの実装の衝撃。
    • みんなの前でデモで無事連鎖をキメることに成功し、拍手する我々
  • by rbtnn: TabSideBar
    • vimでも縦タブを実現するためのパッチ
    • めっちゃ本家に欲しい! windows対応で苦戦してるとのこと

次回予告

次回 Fablic.vim #2は2017年7月上旬を想定しています。今回はFablic社内から発表者を準備しておきましたが、次回はぜひとも皆様からの発表をお待ちしております! 開催日は発表者と相談の上決めることになります。

発表は、初心者向け・中級者向け・上級者向け、できればそれぞれの部門ごとにあると良いなあと思っています。自分で書いたプラギンの紹介でもよいし、普段使ってる便利機能の紹介でもいいし、逆に、普段こういう使い方してるんだけどもっといい方法はないかとみんなに聞いていくスタイルも大歓迎です。

今回のFablic.vim #1のKPTを踏まえた上で開催する予定です。

その他

  • 人数的にLサイズのピザを3枚とサイドディッシュ注文したら余裕だろうと思いきや、ピザが結構な速度で完売したため、追加注文しました。結果として、つねにできたて高鮮度なピザを食べることができ、便利でした。
  • 当日は自転車で会場にこられた方もいらっしゃいました。社内のセキュアな自転車ラックに安全に駐輪することができ、便利でした。

文責: ujihisa

フリルのエンジニア有志でCourseraのMachine Learningコースを集団受講しました

こんにちは。エンジニアのshobyです。

最近、機械学習に興味を持ったエンジニアが集まり、話題になっていたCourseraのMachine Learningコースを受講しました。

その経験から、CourseraのMachine Learningコースは初心者の学習に良い教材であることがわかりました。

また、集団受講することでモチベーションを保ちつつ、より深い理解が得られ、知識の属人化を避けることでプロダクトへの導入の敷居を下げられることもわかったため、Machine Learningコースの集団受講をおすすめするためにこの記事を書きました。

概要

  • CourseraのMachine Learningコースとは
  • Machine Learningコースで得られる知識
  • 集団受講のメリット
  • フリルでの機械学習の扱い
  • まとめ

CourseraのMachine Learningコースとは

Courseraというオンライン学習サービス内で提供されている、機械学習のコースです。11週間で機械学習の基礎を学べます。 (英語ですが、ビデオは日本語字幕もあります)

www.coursera.org

こちらのコースはStanford Universityから提供されており、講師は機械学習研究者であるAndrew Ng氏であるため、信頼できる講義内容だと思います。*1

コースでは、ビデオ、選択式クイズ、プログラミング問題、の三種類の課題があり、「分かった気になる」ことを許さず、深い理解が求められます。

Machine Learningコースで得られる知識

Linear Regression(線形回帰)といった基礎から始まり、ニューラルネットワークや実際の機械学習システムの構築方法まで学べます。

全体的に、理論をきちんと理解させるよりも、機械学習という技術を利用し、問題解決に活用できるようにすることに重点が置かれているように感じました。*2

学べる要素技術を羅列すると以下のような感じです。

  • Supervised Learning(教師あり学習)
    • Linear Regression(線形回帰)
    • Logistic Regression(ロジスティック回帰)
    • Nural Network
    • Support Vector Machine
  • Unsupervised Learning(教師なし学習)
    • K-Means Clustering
  • Anomaly detection(異常検知)
  • Recommender System

上記の要素技術だけでなく、機械学習におけるパラメータのチューニング方法や、デバッグ方法、精度評価手法、機械学習システムの精度上のボトルネックを特定するにはどうしたら良いか、といったところまで、実際にこれからぶつかるであろう問題についても言及してくれています。

集団受講のメリット

集団で受講することで、モチベーションを保ちつつ、より深い理解が得られることがわかりました。

私たちのケースでは、毎週お互いSlackで進捗を共有しつつ、プロダクトへの活用方法を想像しながら進めたため、プレッシャーと応用への期待でモチベーションが保てました。

また、お互いに理解の曖昧な部分について補完しあったり、数学に強いエンジニアが難易度の高い数式について解説してくれたため、深く理解することができました。

フリルでの機械学習の扱い

以前はフリルでは機械学習は、「なんとなく面白そうな技術」という程度の認識でしたが、 複数のエンジニアが機械学習に関する知識を得て属人化を避ける目処がたったため、 「フリルで発生している問題に対して取りうる解決手段の一つ」という認識に変わったような印象があります。

現在では、機械学習が技術的負債を生みやすいことも考え、不正検知といった裏側部分から導入を進められないか検討を進めている最中です。

まとめ

CourseraのMachine Learningコースを集団受講することにより、モチベーションを保ちつつ、深い理解を得ることができます。 CourseraのMachine Learningコースは初心者に向いており、理論だけでなく実践的な内容を学べる質の高い教材です。 また、集団で受講することにより機械学習知識の属人化を防ぎ、実際のプロダクトへ導入する敷居を避けることができました。

*1:Andrew Ng氏はCourseraの設立者の一人でもあります

*2:数式の証明などは省かれている場合が多かったです

Vimの勉強会がFablic本社で開催されます

connpass.com

来る 2017-05-22 (月) 夜6:30pm - 9:30pm, 東京の恵比寿にある株式会社Fablicと呼ばれる会社にて、Vimの勉強会が開催されます。

Vimの初心者の人・中級者の人・上級者の人・実はEmacs使いの人・またはそれら以外の人のためのVimの勉強会です。 Vimに関する技術発表がいくつかあり、休憩時間に参加者が任意でお互いのVimさばきを披露する感じです。

とのことですので、ぜひともお越し下さい。

ujihisa

RxJava 1.x → 2.x 移行ガイド

f:id:hydrakecat:20170426163836j:plain

こんにちは。Androidエンジニアの黒川(@hydrakecat)です。

この記事では、RxJava 1.xから 2.xへのマイグレーションについて説明します。

私が開発に携わっているフリルというフリマサービスのAndroidアプリでは、つい先日のアップデートでRxJava 2.0.8への移行を済ませました。 幸い、いまのところ問題は起きていませんが、マイグレーションにあたっては、当初予想していたよりも多くの作業が発生しました。この記事では、その知見を共有したいと思います。

続きを読む

ActiveAdminでカスタムフィルターを実装する

こんにちは。サーバサイドエンジニアの @masacandy です。

最近MISTEL社のキーボードを購入したので快適にタイピングしながらお送りしております。

さて、今日はActiveAdminを利用する際に、カスタムフィルターを実装したい場合のお話をしたいと思います。

*ActiveAdminとは、管理画面を簡単に導入できるgemです。 github.com

テーブルに紐付いたカラムの内容で検索したい時

「特定のidのレコードを簡単に探したい」時や、「特定の期間に作られたレコードを探したい」時など、テーブルに紐付いているカラムの内容を条件にして検索したい場合は、ActiveAdminに搭載されている「フィルター」という機能を使います。

app/admin/items.rb

ActiveAdmin.register Item do
  filter :id
  filter :created_at
end

f:id:infablic:20170327153105p:plain

filter :検索したいカラム だけでこのように簡単なフィルター機能が実装できます。便利ですね。

カラムの型からよしなに検索方法も作ってくれたり、検索の方法もチェックボックスであったり日付で検索したりと柔軟にオプションが用意されています。

もっと気になる方は公式ドキュメントの方を参考にしてみてください。

複雑な条件で検索したい時

それでは本題に入ります。

上記のように紐付いたカラムで検索する際は簡単に実装することができるとわかりましたが、もう少し複雑な条件で検索したい時はどうすればよいでしょうか?

例えば、複数のカラムの条件から検索できるフィルターを作成したい場合 はどうすればよいでしょうか?

もっと具体的なシチュエーションで言えば、商品が「公開中かどうか」を deleted というカラムで管理しており、「販売済みかどうか」を souldout カラムで判断している場合に 販売中か削除済みか販売済みかの3つの選択肢で 検索をするフィルターを実装したい場合は一体どうしたら良いでしょうか?

カスタムフィルターの作成方法

実はActiveAdminの検索は内部でRansackというgemを利用しています。

そして、Ransackにはransackable_scopesという機能があり、この機能を使ってRansackの検索条件を拡張することができます。

これを利用するとActiveAdmin側でカスタムフィルターを実装することができます。

実装

まず、このransackable_scopesに探したい検索の方法をScopeとして追加します。

app/model/items.rb

class Item < ApplicationRecord
  # Ransackの自前Scopeには '0' や '1' が使えないので注意(参考: [http://qiita.com/t_oginogin/items/b45636d64c271ebc409c:title] )
  STATUS_OPEN = 2
  STATUS_DELETED = 3
  STATUS_SOLDOUT = 4

  scope :open, -> { where(deleted: false) }
  scope :deleted, -> { where(deleted: true) }
  scope :soldout, -> { where(soldout: true) }

  scope :complex_status, lambda { |status| 
    case status.to_i
    when STATUS_OPEN
      open
    when STATUS_DELETED
      deleted
    when STATUS_SOLDOUT
      soldout
    end
  }

  def self.ransackable_scopes(_auth_object = nil)
    %i(complex_status)
  end
end

そして、ActiveAdmin側で上記で追加したScopeをfilterとして利用します。

今回は特定の条件で探すケースなので、Select Boxを指定しました。

app/admin/items.rb

ActiveAdmin.register Item do
  filter :complex_status, label: '商品状態', as: :select, collection: proc { [(%w(販売中 2), %w(削除済み 3), %w(販売済み 4)] }
end

すると

f:id:infablic:20170327193732p:plain

より正確な商品状態の条件で検索できるフィルターを実装することができました。

まとめ

管理画面をお手軽に作れるActiveAdminのちょっとしたTipsでした。数行で複雑な条件検索が管理画面に実装できるのは嬉しいですね。

皆さんも是非参考にしてみてください。

参考

Elixir Conf Japan 2017 参加レポート

Elixir

Elixir Conf Japan 2017

去る2017-04-01に、Elixir Conf Japan 2017に行ってきました。

Ruby界隈でも有名で、Elixir作者のJosé Valimさんが来日されるというだけでもすごいのに、他の発表者が豪華陣営すぎるので、開催が告知されたとき即座に参加を決定しました。

僕は昔Vancouverで2014年のErlang Factory Liteに参加したところその体験が悟りレベルで良さが高かったので、基本的にErlang関係のイベントはErlang書く人と 書かない人 の両方におすすめできると思ってます。今回のイベントはさらにそれどころかElixirなので、これはまさに行くべきで、行ってない人はもったいなかったです。

文責: id:ujihisa ujm

各セッション

発表内容のまとめは他サイトにありそうなので、ここには見聞きして思った僕の感想や、余談を書いていきます。

Opening Keynote by José Valim

南米なのでなんとなく名前Joséの読み方はホセと勝手に思ってたんですが、南米の中でもブラジルだったのでその逆のジョゼの方だったことにこの日はじめて気が付きました。

  • 昔、Ruby on Railsが3.0でthread-safeになるなど、webアプリバックエンドで単なるUNIXプロセス複数使ったparallel化の限界からthreadをも使ったconcurrent化への要求が高まっていた (Amdahl’s lawなどを引き合いに出しつつ)
    • とりあえずメモリ使用量に関しては複数プロセス使っててもそれがforkならwrite barrierで便利にはなった
  • でもthread-safeはあくまで最低限すぎる保証。thread使っても壊れないというだけで、これによって速度向上するとは言ってない (そしてたいてい速度向上してない)
    • 余談ながら、大江戸Ruby会議06の僕の発表は、Vimのthread safetyが話題。そう、Vimはこの最低限の保証すらない
  • JVMとかならこのあたりはずっと進んでるので、JVM上で動かすclojureやscalaは大幅に有利。また、goみたいに独自に別方向から攻めてる例も
  • 一方ErlangというかそのBEAM VMはconcurrentどころじゃなく離散システム含めて他のすべてを圧倒して、大幅に先導してる

→ まちがいなくErlangを採用するのがBest

  • でもErlangだけだとつらい
  • → Elixir!
  • しかもすでに静的型付けをどんどん導入する研究中 (NEW!)
    • この流れがRubyぽい
    • でもRubyよりだいぶ現実的にすみやかに導入できそう
    • すでにErlangレベルでの後付けの静的型チェッカDialyzerもあるわけで、今回それをより厳しくするような感じ

(Erlang) processを使うべきポイント

  • 状態を持たせるとき
  • Concurrentな計算させたいとき
  • failureをisolateしたいとき
  • distributed systemのとき

言い換えるとそれ以外のときはprocess使わないのがよい。 (※UNIX processじゃなくてErlangのアクターとしてのprocessのこと)

雑感

  • Elixirのプロトタイプがダメダメだった、とのJoseさんによる自省の分析が興味深かった
  • “解きたい問題に対して適切でない解法を試みていたため、いくらがんばって諸問題を修正しても、うまくいかなかった” を示す図がめちゃくちゃ良かった
    • 写真とっておけばよかった・・・。これスライド公開されたらここに貼ります (2017-04-04現在、José Valimさんのスライドはインターネットに未公開)

「Phoenix で作るスケーラブルなリアルタイムゲームサーバー」 by hdtkkj

  • 先程はElixir本体の話で、今回は実際にElixirを使うシステムの事例
  • websocketでchannelの双方向通信
    • topic-basedでやってる
  • Erlangが公式にサポートしてるHot code loadが使える
    • ※ただし完全に理解してた場合
    • ゲームはstatefulだし適用例としてはとても適切
    • 著者ujihisa自身、minecraftサーバ運用時にclojureのコードをゲームサーバ稼働したままデプロイするの何度か実装したためだいぶイメージできる
    • 関数の参照が死ぬと全体が死ぬ問題、あるあるすぎる

「Elixir から始める関数型言語」 by tuvistavie

発表資料: http://tuvistavie.com/slides/elixir-fp-intro/

(僕自身Elixirの言語は知ってたのと、普段からScalaやClojure書いてたので、僕個人にとってあまり新規性がなかったから流し聞きしてたのだけど、とてもよく整理して分かりやすく説明していたので、このあたりの経験ない人を連れてきたかったです・・・。)

「Rediscovery of OTP」 by cooldaemon

  • Elixirを全面的に導入するためにCTOになるという事例
  • ElixirとかErlangとかでよくわからないところがあれば時雨堂というコンサル会社で必ず優勝できるという学び
    • ただし後述する理由でRabbitMQ固有の質問は不可能

「Elixir はリアルタイムWeb に強いというのは本当か?」 by mururu

発表資料: https://speakerdeck.com/mururu/elixirkariarutaimuwebniqiang-i-toiufalsehaben-dang-ka

  • Phoenixの実用事例
  • visualixirがかなり便利そうなので、これをVim scriptで実装してVim上でうにょにうょ動くと便利そうだなと @thincaさんを煽るなどしました

visualixir

ランチ休憩 + サイン会

ちょうどいい機会なので、プログラミングElixirを買って、さっそくサインいただいてきました。

「Rubyist |> (^o^) |> Alchemist 〜Elixirの採用からサービス稼動までの記録〜」 by ohrdev

発表資料: https://www.slideshare.net/ohr486/elixirconfjapan2017sessionohr486

  • Railsアプリを部分的にElixirに置き換えた事例の話
  • elixirで内部apiサーバを作成、microservices
  • exq (sidekiqのElixir実装) があるので段階的移行が比較的容易
  • デプロイが難しい (いわゆるいまのimmutable infrastructureの流れにそのままだと乗れない。BEAM自体がそんな感じなので二重の抽象化になる)
  • あえていきなり並行に動くコードを書くのを避ける
  • 古典的なRed/Green/Refactorサイクルを拡張する感じ。この視点、すごく良い

「ニコニコを支えるErlang/Elixir 〜 大規模運用して初めて見えたアレやコレ」 by kojingharang

発表資料: http://niconare.nicovideo.jp/watch/kn2397

  • Erlangのあの密度で56万行はやばいw
    • Javaでいう200~300万行かな
  • うち自動生成されたコードが9万行とか。結局ほとんど人力
  • ニコニコ動画・ニコニコ生放送だけかと思いきや、ドワンゴが持つメディア系のすべてを抽象化したミドルウェアらしい
    • この抽象化が適切だったかどうかの疑問の声がtwitter上で多数
    • これの評価は実際むずかしい

LT

Closing Keynote by Voluntas

発表資料: https://gist.github.com/voluntas/81ab2fe15372c9c67f3e0b12b3f534fa

  • Erlangの強みは 落ちない こと
    • sequentialな速度を犠牲 (GoやNodejsの方が速い)
    • “Erlang/OTP を選択した場合、 90% は Golang で特に問題ないと思う、ただのこり 10 % で使い道がある”
    • 前述のcooldaemonさんの発表にあるように、あまりにも落ちないので、空気のように存在を忘れることができるのはメリット
      • もちろん、だからといってドキュメントがないのは将来秘伝のタレ化しそうで怖い
      • せっかくElixirでさくっと書けるのだから意図的に式年遷宮するのがよいのかもしれない
      • “適当に書いてもなんとなく動く” というメリットが活きてくる
  • “コンテナやmicroservicesに優しくしていく” の進捗に期待

おわりに、雑感

FablicではただちにElixirを使うシーンはいまのところなさそうです。ただ、このあたりの知見は、必要となったときにはすでに知っておかなければ正しい判断ができなくなるわけで、今回カンファレンスに参加した僕以外の人にもどんどん知見を広めていきたいと感じました。

Statefulでかつ落ちてほしくないようなミドルウェアが欲しいときに、ツール選定を行うにあたって、Elixirは間違いなく候補に入ることでしょう。Erlangの基本的な罠について把握した上で、各種テストで比較し、導入を検討していこうと思います。

大江戸Ruby会議06参加レポート

去る2017-03-20、Fablic社員数名で、大江戸Ruby会議06に参加してきました!

twitter.com

今回は、Rubyの地域コミュニティ中でも最高レベルに重鎮が集うこの大江戸Ruby会議06参加レポートをお送りします。

  • 大江戸Ruby会議とは
  • 午前の部
    • Docker時代の分散RSpec環境の作り方 / 橋立友宏 @joker1007
    • My Open Source Journey / Juanito Fatas @JuanitoFatas
    • Text Editing in Ruby / 前田修吾 @shugomaeda
    • 招待講演 / Ruby考古学II 1993-1997 / 石塚圭樹 @keiju_ishitsuka
  • 昼食
  • 午後の部
    • 多相型、推論、Ruby / 松本宗太郎 @soutaro
    • esaとRubyistと私(仮)/ 赤塚 妙子 @ken_c_lo
    • Rationalを(もうちょっと)最適化してみた / 斎藤ただし @tad
    • フルタイム コミッター大戦 / 中田伸悦 @n0kada Urabe Shyouhei @shyouhei 笹田耕一 @ko1 Kenta Murata @mrkn
    • 高濃度雑談 / Tatsuhiro Ujihisa @ujm
    • Ruby 2.4 Internals / 笹田耕一 @ko1
    • 如何にして若き天才コミッタは生まれるのか / Sho Kusano @rosylilly
    • Keynote / Sorah Fukumori @sora_h
  • おまけ: 懇親会の様子
  • FablicでのRuby

文責: id:ujihisa and 岸

続きを読む

Railsにおけるレコード作成時のレースコンディションについて

こんにちは。サーバサイドエンジニアの @yamy です。

フリルでは商品情報など様々なレコードが作成されています。 今回は、レコード作成時にレースコンディションが発生した件についてお話しします。

レースコンディションとは

「レースコンディション(競合状態、Race Condition)」とは、並列で走る複数の処理(プロセスやスレッド)が、共有のリソースへほぼ同時にアクセスしたとき、処理のタイミングによって想定外の結果をもたらすことをレースコンディションと呼びます。

一般的なレースコンディションの例を以下に記載します。

  • スレッドAが17を取得
  • スレッドBもほぼ同時に17を取得
  • スレッドAが18にインクリメントして保存
  • スレッドBも18にインクリメントして保存(オーバーライト)
  • 期待した19ではなく18として保存されてしまう

f:id:infablic:20170315130020p:plain

フリルでのケース

あるトランザクション内において、レコード作成処理と関連リソースの更新処理があるとします。

def create
  ActiveRecord::Base.transaction do
    return unless creatable?

    # レコード作成処理
    Product.create!(
      foo: 'bar',
    )

    # 更新処理
    ...
  end
end

creatable? は、Productレコードの存在をチェックし、以降の処理をすべきかの判定をするものとします。 ここで、複数プロセスA,Bが同時に処理された場合、creatable?のブロック条件に該当するはずのところ、レースコンディションによりすり抜けてしまい、以下のようなエラーになる可能性があります。ユニーク制約をかけているので Duplicate entry となっています。

A> BEGIN
B> BEGIN
A> INSERT INTO `products` (`foo`) VALUES ('bar')
B> INSERT INTO `products` (`foo`) VALUES ('bar') <- Duplicate entry 'bar' for key 'index_products_on_foo'

Productの重複登録は避けれていますが、プロセスBにはプロセスAの処理を待ってから処理してほしいところです。

楽観的ロック

レースコンディションへの対応として、Railsでは「楽観的ロック」「悲観的ロック」と呼ばれる排他制御が提供されています。

「楽観的ロック」とは、「競合は多分起きないだろう」という前提で、データの更新時に競合をチェックする方法です。ActiveRecordでは、lock_versionというバージョン管理向けのカラムを追加するだけで楽観的ロックを利用できます。

レコードの更新時に1づつインクリメントし、更新時にデータ取得時のロックバージョンと異なっている場合、競合が発生したと判断し ActiveRecord::StaleObjectError を発生させます。

カラム追加の必要があるため、フリルでは利用を見送りましたが、以下に簡単な利用方法を記載します。Productsモデルを例にしています。

マイグレーションファイルの追加

rails g migration add_lock_version_to_products lock_version:integer
class AddLockVersionToProducts < ActiveRecord::Migration
  def change
    add_column :products, :lock_version, :integer, default: 0, null: false
  end
end

マイグレーションの実行

rake db:migrate

これで、Productsモデルで自動的に楽観的ロックが行われるようになります。

悲観的ロック

「悲観的ロック」とは、「競合が発生する可能性が十分ある」という状況に向いた排他的なロック手法です。SELECT … FOR UPDATE によりレコード取得時にロックを行い、ロックされたレコードを更新できないようにする仕組みです。

def create
  ActiveRecord::Base.transaction do
    item.lock!
    return unless creatable?

    # レコード作成処理
    Product.create!(
      foo: 'bar',
    )

    # 更新処理
    item.update!(
      status: 2,
    )
    ...
  end
end

ここで、共有リソースのitemという更新対象のオブジェクトがあるとします。トランザクションの冒頭で共有リソースに対してitem.lock!を実行しています。モデルオブジェクトのインスタンスメソッドのlock!は、そのオブジェクトのテーブルレコードに対しての悲観的ロックを取得します。

あるプロセスAとBがほぼ同時に処理を開始し、プロセスAが一瞬早く呼び出された場合、プロセスBは共有リソースであるitem.lock!のところで待たされるため、プロセスAのトランザクションが完了するまで先へは進めません。 これにより、レースコンディションを防ぐことが可能となります。

なお、トランザクション内から別メソッド呼び出しでlock!を行なっている場合、想定したロックが掛からないケースがあるので、テストケースなど十分な検証が必要です。

まとめ

レコード作成時にレースコンディションが発生した件についてお話ししました。同時アクセスなどによる非常に検出しづらい問題ではありますが、Railsのメソッドとしてロック機構が提供されているので、そこまでの手間を掛けずに対応することが出来ました。

参考

Fablicでよく利用されるデザインの検証手段

f:id:kurechon:20170308182821j:plain

こんにちは!デザイナーのくれちょんです。

2016年の末に株式会社basicさんで行われたNextstage Design Niteというイベントにて、チームを動かすデザイナーというタイトルで登壇させていただきました!Fablicでのデザイナーの働き方についてをメインにご紹介させていただきましたが、時間の都合上詳しくお話ししきれなかったデザインの検証手段についてこの記事でより詳しくご紹介させていただこうと思います。

続きを読む