UUUM攻殻機動隊(エンジニアブログ)

UUUMのエンジニアによる技術ブログです

ElasticsearchをMySQLと同期しつつ手軽に無停止アップデートする

nazoです。

Elasticsearchを運用する際に、マスタデータはMySQLで持ちたいという場合にどうやって同期をするかというのが問題になります。また、Elasticsearchはバージョンの互換性が厳しく、別バージョンをクラスタに混ぜることは基本的にできず、さらに辞書の更新などを行う場合はインデックスを全て更新しなくてはいけないなどの運用上の課題があります。

今回は社内向けに使っているElasticsearchを、これらの問題を解決しつつどのように運用するかを考えてみましたので、紹介したいと思います。

簡単に

MySQLとElasitcsearchの同期は go-mysql-elasticsearch を使います。 無停止のためのデータコピーは elasticsearch-dump を使います。

続きを読む

HTTP Live Streamingを試してみた

エンジニアのタナカです。

ライブ配信というとReal Time Streaming Protocol(RTSP)が有名ですが、 AbemaTVやFresh!といった最近のサービスでは、HTTP Live Streaming(HLS)というHTTPベースのプロトコルでライブ配信 を行っています。

従来のRTSPでは、転送プロトコルにUDPを使用することが多く、配信には専用のサーバーが必要でした。
ところが今回紹介するHLSならば、転送プロトコルにHTTPを使用し、ApacheやNginxといった一般的なサーバーを使って動画配信できるようになります。
また、HTTPベースなのでCDNも利用可能で、同時接続数を増やすのも容易なようです。

HTTP Live Streamingについては、こちらのサイトがわかりやすいので、ぜひ見てみてください。

did2memo.net

今回は、このHLSを使用してライブ配信の実験をしてみました。

概要

以下の要領でライブ配信が行えることを確認したいと思います。

  • リアルタイムの映像とエンコーダーに、動画ビュアーのVLCを利用する
  • エンコードされたものをHTTP Live Streaming ToolsでHLS形式に変換する
  • 変換されたものをApacheで配信する

環境

  • OS: Mac OSX/10.11.6(バージョンアップしなければ・・)
  • エンコーダー: VLC Player/2.2.6
  • HLSコンバーター: HTTP Live Streaming Tools(Apple Developer からダウンロード可能)
  • Webサーバー: Apache/2.4.18

実行コマンド

/Applications/VLC.app/Contents/MacOS/VLC -vv screen:// --intf=rc --sout="#transcode{vcodec=h264,samplerate=44100,vb=800,fps=25,acodec=mp4a,ab=128,height=480,width=640,scale=1}:standard{access=file,mux=ts,dst='-'}" | mediastreamsegmenter -f /Library/WebServer/Documents/stream -s 3 -t 30 -p

VLCにはデスクトップをキャプチャー、エンコードする機能があるのでそちらを利用しています。
エンコード結果をHTTP Live Streaming Toolsのコマンドmediastreamsegmenterに渡しています。
ファイルの出力先にはApacheのDocumentsディレクトリを指定しました。

実行結果

ファイル一覧

Library/WebServer/Documents/stream% ls -l
total 3256
-rw-r--r--  1 tanaka  wheel   213944 10 18 18:28 fileSequence0.ts
-rw-r--r--  1 tanaka  wheel  1309232 10 18 18:29 fileSequence1.ts
-rw-r--r--  1 tanaka  wheel   135168 10 18 18:29 fileSequence2.ts
-rw-r--r--  1 tanaka  wheel      151 10 18 18:28 prog_index.m3u8
/Library/WebServer/Documents/stream% 

指定したディレクトリ下にこのようなファイルが出力されます。

.m3u8ファイル

#EXTM3U
#EXT-X-TARGETDURATION:30
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:EVENT
#EXTINF:29.96593,       
fileSequence1.ts
#EXTINF:14.40000,       
fileSequence2.ts
#EXT-X-ENDLIST

このファイルがプレイリストです。 必要な動画ファイル(.ts)が複数指定されていて、対応ブラウザで開くと順番に動画ファイルが再生されます。 EXT-X-TARGETDURATION:30で30秒毎にプレイリストが更新されることが表されています。

Safariで確認

このファイルをApache経由で配信し、Safariで開くとこのように動画が再生されます。

f:id:tnuuu:20171018184200g:plain

まとめ

専用サーバーが必要だった時代に比べて、ライブ配信もだいぶ手軽に行えるようになりました。 ただし、対応ブラウザに注意が必要です。
PCのIEやChromeの場合はそのままでは再生できず、video.jsのようなライブラリが必要です。
スマホならSafariでもChromeでも再生可能です。

Sidekiqが動く仕組みについて

UUUMに入社して1年が経ったエンジニアの北畠です。 Railsも使い始めてもう半年経ちました。

そこで、Railsのジョブキューのgemで有名なSidekiq。

UUUMのプロダクトでもかなり使っているんですが、

どのように動いているかよくわかっていないので調べてみました。

基本的な使い方は

Getting Started · mperham/sidekiq Wiki · GitHub

から

まずはエンキュー

ActibeJob.perform_later

キューに入れたい処理はActiveJob::Baseを拡張したクラスのperformメソッドに実装し、

perform_laterを呼び出すと、エンキューの処理が走る。

Class SampleJob < ActiveJob::Base
  def perform
    p "Hello!"
  end
end

...

SampleJob.perform_later

queue_adapter.enqueue

perform_laterは ActiveJob.queue_adapter に設定されているオブジェクトの enqueue にjobインスタンスを投げます。 queue_adapterの設定はデフォルトでは、 :async(AsyncAdapter) で、configで以下のように指定すると、SidekiqAdapterが設定されます。

config.active_job.queue_adapter = :sidekiq

他にどんなアダプターがあるかは、activjob/queue_adapters.rb のコメントに結構細かく書かれています。

rails/queue_adapters.rb at master · rails/rails · GitHub

SidekiqAdapter.enqueue

def enqueue(job) #:nodoc:
  #Sidekiq::Client does not support symbols as keys
  job.provider_job_id = Sidekiq::Client.push \
    'class'   => JobWrapper,
    'wrapped' => job.class.to_s,
    'queue'   => job.queue_name,
    'args'    => [ job.serialize ]
end

こんな感じ。 ジョブのクラス名や引数を Sidekiq::Client に投げている。

class として指定されている。JobWrapper の実装はかなり薄くて、

class JobWrapper #:nodoc:
  include Sidekiq::Worker

  def perform(job_data)
    Base.execute job_data.merge('provider_job_id' => jid)
  end
end

これだけです。 Base.executeは受け取ったjob_dataをdeserializeして、performを実行します。

Sidekiq.client

Sidekiq.clientのpush処理では、受け取ったジョブ情報をRedis上に保存できる状態に成形し、ジョブIDの情報を追加して、 Redisの queues:#{queue_name} キーに追加します。

サンプルで追加してみた中身はこんな感じのjsonです。

{
    "class": "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper",
    "wrapped": "SampleJob",
    "queue": "default",
    "args": [
        {
            "job_class": "SampleJob",
            "job_id": "ae214f55-3d84-4037-8086-be111661ec6a",
            "queue_name": "default",
            "priority": null,
            "arguments": [],
            "locale": "ja"
        }
    ],
    "retry": true,
    "jid": "d1ecd3aa4bf5340b5c2a52f6",
    "created_at": 1506733202.404303,
    "enqueued_at": 1506733202.405045
}

ジョブの取得と実行

Redisにジョブを追加するだけではジョブは実行されません。 ジョブ情報を取得して、実行する必要があります。

取得

bundle exec sidekiq を実行することで、ジョブを取得、実行するプロセスを起動できます。

プロセスが起動すると、Sidekiq::Launcher.run が実行されます。

sidekiq/launcher.rb at master · mperham/sidekiq · GitHub

まず、Sidekiq::Managerがworkerを作成し、起動します。(デフォルトの設定では25個作成されます。)

workerはそれぞれのスレッドを起動し、ジョブ情報をRedisから取得しに行きます。

wokerの中身は Sidekiq::Processor で、Processor.get_one から下記の Sidekiq::BasicFetch.retrieve_work が呼び出され、Redisからジョブ情報を取得しています。

sidekiq/fetch.rb at master · mperham/sidekiq · GitHub

実行

Redisから取得したジョブ情報を元に、ジョブのクラスと引数情報を deserialize して、worker上のスレッドで実行されます

sidekiq/processor.rb at master · mperham/sidekiq · GitHub

performが呼び出されてます。

Railsのloadは元のプロセス起動時に行われています。

sidekiq/cli.rb at master · mperham/sidekiq · GitHub

scheduled

下記のように waitwait_untill を設定した場合はどうなるか?

SampleJob.set(wait: 5.minutes).perform_later

エンキュー

指定された値から、実行する時刻を計算し、 queue_adapterの enqueue_at にジョブ情報と一緒に渡されます。

#SidekiqAdapter.enqueue_at
def enqueue_at(job, timestamp) #:nodoc:
  job.provider_job_id = Sidekiq::Client.push \
    'class'   => JobWrapper,
    'wrapped' => job.class.to_s,
    'queue'   => job.queue_name,
    'args'    => [ job.serialize ],
    'at'      => timestamp
end

Sidekiq::Client側の処理

Sidekiq::Client側の処理では、 at が指定されていると sorted set に timestampでソートする形でジョブ情報が登録されます。 (上記の通常(at指定なし)のジョブ情報リストとは違うキーに保持されます)

取得、実行

Sidekiqのプロセスは上記で説明した プロセスを実行する woker を管理する manager の他に、 at 指定で登録されているジョブを ジョブキューに登録する poller が動いています。

pollerは5秒ごとに、 sorted set から 現在時刻より前 の timestamp のジョブ情報がないかを見に行き、 対象のジョブがあった場合は Sidekiq::Client::push を呼び出し、 sorted set から ジョブキューへジョブ情報を移します。

この処理により、 at で指定したジョブも worker達が処理する対象になってくれます。

retry

プロセスでエラーが起きた時、

retry が有効な場合で、retry数がまだMAX retry数内(デフォルトは25)の場合は、ジョブ情報が retry queue に追加されます。

retry queue に保持される情報は、通常のジョブ情報に、エラー情報とリトライ回数などの情報が加えらえれたものになります。

上記で話した scheduledのキューを処理する poller は、retryのキュー情報も見るようになっているので、retryに入れられた情報は、

自然とジョブキューへ追加されます。

リトライ実行のディレイはリトライ数が多くなるごとに指数関数的に多くなるようになっています。

ディレイ時間の計算処理 sidekiq/job_retry.rb at master · mperham/sidekiq · GitHub

リトライ設定がfalseの場合と、リトライ数が上限を超えたジョブ情報は dead というキーの sorted set に 失敗時刻が sort 対象の項目として、登録されます。

dead データセットは、オプションの dead_max_jobs, dead_timeout_in_seconds の設定を元に、

失敗したジョブ情報を保持しています。

デフォルトの設定は下記となっています

deed_max_jobs: 1万
dead_timeout_in_seconds: 6ヶ月

となっています。

sidekiq-cron

Sidekiqをcron形式の設定でスケジューリングするgemが sidekiq-cron です。

github.com

設定ファイルの読み込み

YAMLの設定ファイルを読み込む場合は、 config/initializers などに

Sidekiq::Cron::Job.load_from_hash YAML.load_file(#{filepath})

のように指定することで、Redis上に、cronの設定情報が保存されます。

poller

sidekiq-cronでプロセスを持つことはなく、sidekiqのlauncher を上書きすることで実現しています。

# Sidekiq::Lancher

# remember old run
alias_method :old_run, :run

# execute normal run of launcher and run cron poller
def run
  old_run
  cron_poller.start
end

sidekiqの manager, poller とは別に、 cron_poller の処理を start しているのが解ります。

cron_poller の 処理は、上記でRedisに取り込んだ cron の設定を定期的に見に行って、ジョブキューへ追加する処理をやっています。

Redisの使い方のまとめ

今まで説明した中でRedisでいくつかのキーを使っていましたが、どんな使い方をしているか整理してみます。

queue周り

queues(set型)

queue の種類のリスト。

デフォルトでは、 default のみです。

queue:{queue name}(list型)

job queue の本体。

lpush, brpop することで、ジョブ情報の追加、取得を行っています。

タイムスタンプでのソート済みセット

遅延実行(key: schedule)、リトライ(key: retry)、cron(key: cron)はタイムスタンプを score としたソート済みセットとして実装されています。

zrangebyscore で- inf から now を定期的に取りに行くことで、実行時間になったジョブを queue に追加しています。

その他

ジョブの成功数、失敗数の情報を文字列型で持っていたり、

sidekiq-cronでは、yamlなどで書いた設定を Redis側にloadしていたり、いろいろな使い方をしています。

おわりに

Sidekiqなどのジョブキューの実装は自分の中ではブラックボックスで、結構複雑な感じがしていましたが、

実際に見てみると、結構シンプルで解りやすかったかなと思います。

また、Redisの色々なデータ型を駆使しているプログラムなので、Redisを使ったアプリケーションの見本のストックとして認識しておけば、自分がRedisを色々使うときにも参考になるのではないでしょうか!

スマホアプリづくりのすすめ

どーも、ドロイド君フィギュアを30体以上持っている、ドロイド君愛好家のてっこです。

スマホアプリづくりは楽しい

f:id:k_nakahashi:20170921194403p:plain

僕はスマホアプリを作るのが大好きで今までに80本以上のアプリをリリースしてきました。

リリースしていないのも含めたら200くらいのプロジェクトはつくったかな?

スマホアプリの楽しいところ

ユーザーと直接接点が持てる

f:id:k_nakahashi:20170921194421p:plain

スマホアプリは実際にユーザーが触ります。そして世の中にリリースするとフィードバックが届きます。

おもしろい!使いやすい! 逆に つまらない!使いづらい! といったものです。

これはモチベーション向上にもなるし、より良いプロダクトにするための手助けとなることが多いでしょう。

世界中の人に使ってもらえる

f:id:k_nakahashi:20170921194449p:plain

AndroidだとGoogle Play、iOSだとApp Storeがあります。 前者は25ドルで出し放題、後者は年額11800円(2017/8/30現在)で出し放題です。

Androidだったら飲み会一回断れば生涯リリースし放題!iOSは年に3回飲み会断ればリリースし放題!

これだけで世界中の人に自分の作品を触ってもらえる機会が付与されます。

ね、簡単でしょ?

アウトプットが自分の経歴書になる

f:id:k_nakahashi:20170921194504p:plain

たぶんエンジニアの皆さんは経歴書とか書くの嫌いですよね(笑) 個人でリリースの経験があれば、それを見せることでとてもわかりやすい経歴になります。

会社で作っていたものももちろんですが、個人でリリースしておくと

企画->開発->リリース->マーケティング

など全てを経験できるのでオススメですよ。

よく聞かれる質問集

Q. AndroidとiOSどっちやれば良いですか?

A. リリース予定なければiOS、リリース予定あればAndroidが良いと思う

iOSの場合、Swiftはモダンでとても書きやすい言語なので、初心者でも入りやすいと思います。(ここでObjective-Cの話は禁止な ただし、年額11800円というのは とりあえずやってみる のハードルがちょっと高いです。(おカネ的な意味で

Androidの場合、Javaというちょっと奥ゆかしい言語を使っているので慣れていない人は難しいかもしれません。(ここがネックだと思う人はKotlinでググれば幸せになれると思うよ 逆に 25ドルさえ払ってしまえばリリースし放題 という気軽にリリースできる環境が整うので見返りはでかいです。

ちなみに世界のAndroid, iOSシェアが見れる以下サイトとか見てみると面白いですよ。

参考: KANTAR WORLDPANEL

Q. リジェクトされないですか?

A. されます。確実に

逆にリジェクトされたことないというスマホアプリ開発者に出会ったことがないと思います。 ラーメン屋に置いてあるコショウみたいなもんだと思ってください。

僕の場合、計50回以上リジェクトされてます。 世間的にはリジェクトといえばAppleのイメージが強いと思いますが、Googleもあります。 なので明らかにアウトなものはGoogleに出してもリジェクト(即アプリ削除など)される可能性があるので気をつけてくださいね。

また、単純なアプリだとリジェクトされるんじゃないか?という話もよく聞かれますが、僕は ボタンを押したら音がなるだけのアプリ をリリースしたときは珍しく一発審査合格でしたので、そんな気にせずとりあえず提出してみるのが良いと思います。

Q. アイディアはどうやって思いつくんですか?

A. たとえば、不満をアプリにすれば良いと思います

僕の場合、スマホアプリ大好きっ子なので、なにか不満があると それアプリで解決できないの? ってすぐに考えてしまいます。 たとえば、 ボタンを押したら音がなるだけのアプリ は居酒屋でボタンがなくて店員が呼べなくて、さらにコミュ障な自分は声を張り上げるのがツライので、じゃあアプリにしちゃお。と思ったわけです。

せっかくなのでもう一つくらい例にあげると、僕は北海道生活が長かったので電車がわからないんですよ、カオスなんですよ。 Kindleとか読んでて、 あれ?今何駅だ? って思ったときにツライなと思って、 現在地を投げると最寄り駅のみを返してくれるアプリ を作りました。

こんな感じでアイディアなんてどこにでも転がっているので、どんどん見つけていけば良いと思います。

また、アイディアを大切にずっと持ち続けている人もいると思いますが、多分世界中に同じことを考えている人はいっぱいいるので、どんどんTwitterやFacebookなんかで共有して、それに対する世間の反応を観察してみるのも大事かなと思います。

Q. スマホアプリ開発って楽しいですか?

A. 楽しい!!!

現場からは以上です。

いまさら人に聞けないmake入門

おはこんばんちは!! 尾藤 a.k.a. BTO です。

最近の若い人で Makefile を書く人が増えているそうじゃないですか。 そしたらもう、ウホ、これはオサーンの出番ってなるわけですよ。 僕みたいな老害はこんな場面でしか幅を利かせられないってことで、ええ、やりましたよ make 入門を社内勉強会で。

  • makeとは
  • 様々な実装
  • サンプル
  • ルール
  • やってみる
  • ファイルを一部だけ更新してみる
  • 変数
  • 自動変数
  • Suffix Rule(古い)
  • Pattern Rule
  • Phony Target
  • まとめ
続きを読む

さくっとレスポンシブの基本をおさえる

レスポンシブデザインのコーディングが難しくて、基本的なところはおさえておこうと思ったエンジニアの北畠です。

まず、レスポンシブデザインとは?

単一のWebページ(HTML)を使い、スクリーンサイズを基準にCSSを切り替えてレイアウトを調整する技術を使ったデザインです。

いつから始まったか?

2010年に イーサンマルコッテさんが記事を書いて広まったらしいです。

alistapart.com

概要としては、

  • 多様なデバイスが登場している。
  • それぞれのデバイスに合わせてコンテンツを作っていくのはかなり大変。

  • デバイスの仕様に合わせてレイアウトを調整する技術がある。

  • CSS2でもあったけど、CSS3から実用レベルで使えるようになっている。

CSS2のmedia typeは、デバイスの対応状況が悪かったりで、実用できるレベルではなかったようです。

レスポンシブ周りの技術として、

  • CSS3から使えるようになった media query
  • 画像の取扱(fruid, responsive)
  • グリッドレイアウト

あたりが重要な所とされています。 それぞれ見ていきましょう。

media query

CSS3から実装された、デバイスの表示幅などに応じて、読み込むスタイルシートを切り替えるものです。

linkタグにも書けるし、cssにも直接書けます。

linkタグに書くケース

<link rel="stylesheet" media="(max-width: 800px)" href="example.css" />

CSSに直接書くケース

@media (min-width: 700px) { ... }

@media (min-width: 700px) and (orientation: landscape) { ... }

詳しくは、

メディアクエリの利用 - CSS | MDN

にかなり細かく説明されています。

画像の扱いについて

fruid image

画像をコンテンツ幅に応じて収縮させたい場合に使う技術.

imageにwidth: 100% を指定すれば実現できます。

そのままですね。

responsive image

スクリーンサイズに応じた、適切なサイズの画像を読み込む技術.

media queryとbackgroundイメージの組み合わせで実現できます。

例えば、PC用、スマホ用の画像をそれぞれ用意して、768px以下のデバイス幅の場合スマホ用の画像を読み込ませたい場合は下記のような指定をすれば実現できます。

div {
    background: url(background.png) top center no-repeat gray;
}


@media screen and (max-width: 768px) {
    div {
        background: url(background_mobile.png) top center no-repeat gray;
    }
}

また、background-sizeプロパティを使うことで、背景画像の表示方法を調整できます。

background-size - CSS | MDN

各デバイス幅ごとに細かく指定すると、少ない画像でもいい感じに調整できそうですね。

グリッドレイアウト

間隔や幅に規則性を持たせることができるもので、 おなじみのBootstrapでは、全体を12列に分割して、各デバイス幅ごとに要素に何列分割り当てるか指定することで、レスポンシブなグリッドレイアウトが実現できます。

デバイス幅の一定の間隔でいくつかブレークポイントが用意されていて、それぞれのブレークポイントごとに応じた指定を、

col-{ブレークポイント名}-{列数} という書き方で指定できます。

詳しくは、

getbootstrap.com

Bootstrapのバージョン4では、flexboxを採用していて、グリッドレイアウトの仕様もかなり変わっているようです。 指定方法がかなりシンプルになっています。

Grid system · Bootstrap

おわりに

レスポンシブデザイン周りの技術、全然キャッチアップできていなくてかなり苦手意識があったのですが、調べてみると技術的に重要な要素はそれほど多くなくて、割りとシンプルな気がしました。

ただ1つのhtmlで複数デバイスに最適化したデザインを実現すること自体は、以前のようなPC向けの幅決め打ちのデザインと比べてかなり複雑で、しっかり作り込もうとするとかなり大変になるなぁという印象は変わりません。がんばっていきましょう。