Upgrade to Pro — share decks privately, control downloads, hide ads and more …

MnesiaとFlowで高速スモールアナリティクス

 MnesiaとFlowで高速スモールアナリティクス

クラスタリングしたMnesiaのイベントをトリガーして、ストリーミング画像解析を行います。

enpedasi

June 22, 2018
Tweet

More Decks by enpedasi

Other Decks in Programming

Transcript

  1. Mnesiaドキュメントより System events and table events are the two event

    categories that Mnesia generates in various situations. システムイベントとテーブルイベントの2種類ある。 A user process can subscribe on the events generated by Mnesia. The following two functions are provided: ユーザープロセスで購読できる。 ⇒ つまり 「サーバーを⽴てればいい」
  2. handle_infoで、メッセージを受け取る defmodule MnesiaTrigger do use GenServer def init(table_name) do :mnesia.subscribe({:table,

    table_name, :simple}) {:ok, []} end def handle_call({:events}, _from, state) do {:reply, state, []} # イベントリスト取り出してクリア end def handle_info({:mnesia_table_event, {:write, {_tbl, key, val}, from}} = msg, state) do {:noreply, [{key, from}] ++ state } end
  3. MnesiaイベントをStream化 #2 Mnesiaからイベントを受け取って、ストリームに画 像を流す実装例 Stream.resource(start_fn, next_fn, halt_fn) 初期化fn / 継続fn

    / 停⽌fn を実装すればOK 本処理では以下を⾏っている -- サーバーのpid(プロセスid)のたらい回し -- 画像テーブルのkeyを受け取る -- 画像をストリームに流す
  4. # Mnesiaイベントをストリーム化 defmodule Analytics.Stream do def subscribe(pid) do start_fn =

    fn -> pid end next_fn = fn pid -> case GenServer.call(pid, {:out}) do {key, _from} -> #keyから画像を取得 img = :ets.lookup_element(:img_list, key, 3) {[img], pid} nil -> {:halt, pid} end end halt_fn = fn _next_url -> :ok end Stream.resource(start_fn, next_fn, halt_fn) end end
  5. Flowを使ったアナリティクス実装 def flow_coord(pid) do Analytics.Stream.subscribe(pid) |> Flow.from_enumerable(max_demand: 1, stages: 4)

    |> Flow.map( &{ CloudVision.analyze(&1 , from: :direct, features: [:label]) |> CloudVision.labels_desc_score, &1} ) |> Flow.map( fn {desc, img} -> socket_send(img, desc) end) end 1. Google Cloud Visionに画像を送信 2. 解析したラベルを受け取って、画像とくくる 3. ソケットでブラウザへ送信
  6. AskServer抜粋 defmodule AskServer do use GenServer def handle_info(:ask, stream) do

    schedule_job(stream) {:noreply, stream} end defp schedule_job(stream) do stream |> Enum.to_list # ここでストリームを実態化 # 戻り値には興味がないので捨てる Process.send_after(self(), :ask, 5 * 1000) end end
  7. defmodule Fuk11Web.VisionChannel do use Phoenix.Channel def join("room:loby", msg, socket) do

    {:ok, socket} end # 今回の実装では、"msg"トピックは未使⽤ def handle_in("msg", params, socket) do broadcast! socket, "msg", %{body: params["body"]} {:noreply, socket} end def handle_out("msg", payload, socket) do push socket, "msg", payload {:noreply, socket} end end
  8. Socket発⽕例 Fuk11Web.Endpoint.broadcast "room:loby", "img", %{"body" => %{"image" => Base.encode64(image), "desc"

    => desc} } 余談 : %{ } はElixirのMap型。 => を : に置き換えれ ば、JavascriptのObjectとほぼ同じ。
  9. JavaScriptからの呼び出し例(Vue.js) const channel = socket.channel('room:loby', {}) channel.join() .receive('ok', res =>

    console.log(` > join ok ${channel.topi .receive('error', res => console.log(' > Error join ', res)) channel.on('img', img => { let baseImage = 'data:image/jpeg;base64,' + img.body.image const newRec = {img: baseImage, no: this.imageNo, desc: img.body.desc} this.visionImages.unshift(newRec) this.imageNo = this.imageNo + 1 })