Docker for Macが遅い問題をdocker-syncで解決する

こんにちは。エンジニアの志村です。 Docker for Mac便利ですね!

docs.docker.com

日本語でも様々な記事が出てきています。

私もVagrant + Dockerをメインに使用しておりましたが、ついにDocker for Macに乗り換えました。 弊社では、dev環境をDockerにしているのですが、Vagrant + Dockerの時は快適に開発が出来ました。 ただ、VMを使わなくなった途端にめちゃくちゃ動作が重くなりました。 docker-compose --service-port ●●という感じで、rails serverとwebpack-dev-serverを立ち上げるのですが、ブラウザからの読込が劇的に遅い…。seedデータ突っ込むのもめちゃくちゃ遅い… フォーラムでも話題になっていますね。

forums.docker.com

私の環境でいうと、Vagrant + Dockerの時よりも、体感10倍近く速度が落ちました…。

なぜ遅いのか

forums.docker.com

このDockerの中の人の回答を見ると、osxfsを用いてファイルシステムイベントを検知・監視していることが主な原因っぽいですね。 勿論、その他環境により差異はありますが、ファイルシステムを監視する方法を替えれば解決できそうです。

docker-sync

ファイル同期で速度早いと良く言われているのがrsyncですね。 docker-dev-osxという選択肢もありますが、これはDocker for Mac未対応… どうしようかなーとフォーラムを眺めているとdocker-syncなるものがありました。 フォーラムの中でも評価が良く、結構な人が使ってそうなのでdocker-syncを使用したいと思います。

docker-sync by EugenMayer

docker-syncはRubyベースで書かれています。

docker-syncの使い方

  1. docker-sync, fswatchをインストールします。
$ gem install docker-sync
$ brew install fswatch

ファイルの同期に関してはrsync, unisonが選択出来ます。 rsyncがホスト→ゲストの一方向の通信なのに対し、unisonはNFSのように双方向の通信が可能なようです。 今回はrsyncを使用します。 unisonに関してはまた記事を書ければと思います。

$ brew tap homebrew/dupes
$ brew install rsync

※ schema.rb, Gemfile.lock等、ゲスト(Docker)側で生成されるファイルに関してはdocker cpコマンドを使用するとホスト側に持ってくることが出来ます。

# Docker内のschema.rbをPC側に持ってくる
$ docker cp コンテナ:/var/www/db/schema.rb ./db/schema.rb
  1. docker-syncの設定 docker-syncはymlファイルに設定を記述します。 Railsを触っている身としては普段から使用しているのでありがたいです。

docker-syncはDocker Composeを使用します。 production環境やstaging環境でもDockerを使用している方は、docker-compose.ymlの他に、開発環境用のymlファイルを用意する必要があります。 私は開発環境のみなので、docker-compose.ymlを直接編集してしまいます。

公式のwikiに載っているので参考下さい。

github.com

docker-sync.yml

syncs:
  # docker-syncで使用するvolume名を記載
  web-sync:
    notify_terminal: true
    # ホスト側(PC)のパスを記載 今回はRailsのルートディレクトリを指定
    src: './'
    # ゲスト側(Docker)にマウントするパスを記載 Docker内の/var/wwwにマウントされる
    dest: '/var/www'

    # IPの設定
    sync_host_ip: '127.0.0.1'

    # rsyncのポート設定
    sync_host_port: 10871

    # rsyncさせないファイル類を記載
    sync_excludes: ['Gemfile.lock', 'Gemfile', 'config.rb', '.sass-cache/', 'sass/', 'sass-cache/', 'composer.json' , 'bower.json', 'package.json', 'Gruntfile*', 'bower_components/', 'node_modules/', '.gitignore', '.git/', '*.coffee', '*.scss', '*.sass']

    sync_excludes_type: 'Path'

    sync_args: '-v'

    # ファイル同期方法の選択 rsync or unison
    sync_strategy: 'rsync'

    # ゲスト側でのユーザー名の指定
    sync_user: 'test'

    sync_userid: '5000'

    # ゲスト側でのgroupの指定
    sync_group: 'testgroup'

    sync_groupid: '6000'

    # fswatchで変更を検知させないファイルやディレクトリ sync_excludesとかぶせとけばよいかと
    watch_excludes: ['.*/.git', '.*/node_modules', '.*/bower_components', '.*/sass-cache', '.*/.sass-cache', '.*/.sass-cache', '.coffee', '.scss', '.sass', '.gitignore']

    watch_args: '-v'

    watch_strategy: 'fswatch'   

docker-compose.yml

docker-compose.ymlにはversion1, version2と2種類の記述方法があります。 ここではversion2を使用します。

version: '2'
services:
  database:
    image: postgres:latest
    volumes_from:
      - datastore
    expose:
      - '5432'

  elasticsearch:
    image: elasticsearch:latest
    volumes_from:
      - datastore
    ports:
      - '9200:9200'

  redis:
    image: redis:latest
    ports:
      - '6379:6379'
    volumes_from:
      - datastore

  memcached:
    image: memcached:latest
    ports:
      - '11211:11211'
    volumes_from:
      - datastore

  datastore:
    build: docker/datastore

  web:
    build: .
    command: bash -c 'rm -f tmp/pids/server.pid && bundle exec foreman start && bundle exec sidekiq -C config/sidekiq.yml -L tmp/sidekiq.log -d'
    volumes:
      # 下記で宣言したvolume名を指定
      - web-sync:/var/www:rw
      - /var/www/client/node_modules
    ports:
      - '3000:3000'
      - '8080:8080'
    links:
      - database
      - elasticsearch
      - redis
      - memcached

# docker-syncで使用するvolume ここで宣言した名前がdocker-sync.ymlで使用される
volumes:
  web-sync:
    external: true

このような感じです。 環境によって差異があると思いますので、適宜調整をお願いします。 一番重要なのは、volumesのsync volumeの設定と、アプリケーション(Rails)側のコンテナでvolumesを指定する部分です。

docker-syncの起動

$ docker-sync start

もしくは

$ docker-sync-stack start

で起動します!

docker-sync-stack startの場合は、docker-sync startと同時にdocker-compose upも行われます。 私はpry-byebugを使用したいので、普段docker-compose run --service-port webで起動しているため、ターミナルを一枚docker-sync startに使用し、さらにdocker-compose run --service-port webを起動させるようにしています。

ベンチマークはとっていないのですが、Vagrant + DockerをNFSで同期させたのと同じ、もしくはそれ以上に早くなりました! GUIで様々な設定が出来、VMとかも必要ないのでDocker for Macは便利ですね。 是非docker-syncを使って快適な開発環境を構築してみては如何でしょうか?

We’re hiring!

Cluexではビジネスサイド、エンジニアサイド共にメンバーを募集しています! お気軽にご連絡下さいませ!

www.wantedly.com

www.wantedly.com

www.wantedly.com

www.wantedly.com

今年2回目の開発合宿に行って来ました!@土浦・筑波

こんにちは、エンジニアの高橋です。

最近都内は急に肌寒くなってきましたが、みなさんお元気でしょうか?

今回は茨城県の土浦・筑波エリアで開催!

先日、2泊3日で茨城県の土浦・筑波の方に開発合宿に行ってきました!

台風が近づいている中での開催となり、心配な所もありましたが無事に終わったので、 今回はその様子をまとめようと思います。

 

2月の開発合宿に続いて、今年2回目の開催となります。 Cluexでは合宿を利用して 普段なかなか手のつけられない技術的課題や技術的負債への取り組みや、 理念会議といったメンバー全員でじっくり話し合う場を設けるなど といったことをしています。

ちなみに前回(2016年2月)の日光での合宿の様子はこちら cluex-developers.hateblo.jp

古民家ゲストハウス「jicca」

f:id:cluex-developers:20161014105046j:plain

今回の宿泊先には2泊3日の全日貸切で、筑波山の近くにある 茨城県石岡市のゲストハウス「jicca」さんを使わせてもらいました。

jicca-gh.com

ここのゲストハウスはクラウドファンディングを活用して、 もともと実家の母屋だったものをゲストハウスに改装したそうです。

 

ここの近辺は豊かな自然が溢れていて、とても落ち着いている長閑な場所で、

jiccaは最近は雑誌やラジオなどで度々話題になっているそうです。

 

こちらを運営されている比企(ひき)さんという方は、もともと大手企業で活躍された後に 現在はこちらの「jicca」を運営されつつ個人で中小企業診断士として中小企業コンサルをされている方で、とても暖かい方でした。

   

今回の合宿でのTRY

合宿に参加したのはエンジニア4人・ディレクター3人の合計7名。

合宿中はエンジニア陣は2人で1チーム、ディレクター3人で1チームの全部で3チームに別れて目標を立てました。 合宿の目標はそれぞれのチームであらかじめ決めておき、合宿当日はすぐに作業が開始できる様にしておきました。

ディレクターチーム 売上を上げるためのコンテンツマーケティングの運用方針を決める

今後の事業成長戦略を各々が考えて発表

Web appチーム Rspecのテストカバレッジを95%達成

インフラ&ネイティブアプリチーム CoffeeScriptからES2015(ES6)への完全移行

     

開発合宿1日目

台風が近づいてる影響で初日は曇り空。

午前9時、都内某所に集まり出発です!

都内から土浦までは高速道路を使用して、車でおよそ1時間半ほどの距離にあります。

f:id:cluex-developers:20161014110311j:plain

新卒3人組

前日も遅くまでオフィスに残っていたみたいで非常に眠そうですね。

   

土浦までの道中は高速道路も渋滞なく順調に進み、

守谷SAで途中休憩を挟んでほどなくして土浦に到着です。

   

宿に着く前に、激安ジャングルで物資調達です。

車内では眠そうだった彼も元気になったみたいです。

f:id:cluex-developers:20161014111650j:plain

 

そして11時過ぎにjiccaへ到着。

 

到着してからまずは全体で各チームの合宿中の目標を共有し、 終わり次第すぐに各自作業に取り掛かります。

   

ディレクターチームは主に話し合いが多いので1階にて作業。

エンジニアの方は机と椅子が用意されている2階に上がり作業を開始。

 

ディレクターチーム

f:id:cluex-developers:20161014132340j:plain

 

エンジニアチーム

f:id:cluex-developers:20161014132912j:plain

   

   

 

1日目の夜は夕食と温泉を兼ねて、

車で10分ほど移動して「やさと温泉 ゆりの郷」へ行きました。

www.yurinosato.jp

   

この近辺は日帰り温泉が500~1000円くらいで点在しているので、 その中から一番近場の場所を選びました。

  f:id:cluex-developers:20161014133300j:plain

  f:id:cluex-developers:20161014133340j:plain

「jicca」のオーナーの比企さんも一緒に温泉にお供して頂きました。

  f:id:cluex-developers:20161014133657j:plain

     

茨城県は「常陸牛」や「ローズポーク」が有名なようです。

こちらの常陸牛は柔らかくてとても美味しいお肉でした。

f:id:cluex-developers:20161014134306j:plain

f:id:cluex-developers:20161014184004j:plain

     

夕食後

 

本日はアルコールは一切なしで、 夕食を食べた後はすぐ宿に戻って作業再開です。

 

f:id:cluex-developers:20161014184229j:plain

   

ディレクターチームは順調に進んでいるようで、午前12時頃には就寝。

   

エンジニアの方はまだまだ続きます。

f:id:cluex-developers:20161014135638j:plain

f:id:cluex-developers:20161014135809j:plain

午前4時

まだ続きます。

f:id:cluex-developers:20161014170527j:plain

     

初日が終わり、全員が寝静まったのは午前6時頃。

長い長い1日目が終了です。

     

開発合宿2日目

f:id:cluex-developers:20161014141004j:plain

台風は進路を変えていなくなってしまい、 晴れ晴れとした天気になりました。

   

午前9時、起床。

全員で集まり1日のスケジュール確認です。

f:id:cluex-developers:20161014140301j:plain

 

午前中は引き続き各チームごとで作業。

 

昼食は手打ち蕎麦が有名とのことで、宿の近くの蕎麦屋さん「かまたや」へ

創業60年以上の老舗蕎麦屋だそうです。

f:id:cluex-developers:20161014141622j:plain

www.ibarakiguide.jp

   

蕎麦はもちろん、天ぷらがめちゃめちゃ美味しいです。

f:id:cluex-developers:20161014152519j:plain

     

宿に戻り、今回の合宿のもうひとつの一大イベントに入ります。

 

Cluex理念会議

f:id:cluex-developers:20161014142338j:plain

 

2日目は昼食後から6時間ほどに渡り、

ディレクター・エンジニア含めて全員で「理念会議」といったものを行います。

 

前回の合宿に続き、

今回も個人個人の将来・会社の将来、自分がこれからどうしていきたいか・どうなりたいのかといったことを発表して、

それをどうすれば実現できるのかということをみんなで話し合っていきます。

   

・・・5時間経過後

   

f:id:cluex-developers:20161014142542j:plain

 

ということで、写真にはあまり変化がないですが、

一人一人にフォーカスして個人の思想やその背景にある思い、

そしてそれぞれの将来に関してじっくり話し合ってきました。

   

f:id:cluex-developers:20161014171731j:plain

 

f:id:cluex-developers:20161014171853j:plain

   

理念会議が終わった頃には日がすっかり暮れてしまいました。

これからやや遅めの夕食です。

   

2日目の夕食はBBQ!

f:id:cluex-developers:20161014145526j:plain

「jicca」の方で茨城県産の食材をたくさんご用意して頂いて、みんなでBBQです。

今回のBBQに望むためのチーム編成はこちら

     

 

調理部隊

f:id:cluex-developers:20161014143738j:plain

f:id:cluex-developers:20161014144102j:plain

     

バーベキュー屋の店員

火起こし部隊

f:id:cluex-developers:20161014143842j:plain

       

おじさんたち

火起こし部隊の後ろで待機です。 

f:id:cluex-developers:20161014150015j:plain

       

そして・・・・

     

いざBBQ!

f:id:cluex-developers:20161014144402j:plain

f:id:cluex-developers:20161014144448j:plain

f:id:cluex-developers:20161014145426j:plain

f:id:cluex-developers:20161014144559j:plain

     

普段はこういった機会がなかなか無いので、

みんなでお酒を飲みながら語り合って、貴重な時間を過ごせました。

   

BBQを堪能し、ほどなくして2日目も終了です。

今日はみんな疲れが出てきたようで、午前12時にはみんな就寝したみたいです。

     

開発合宿3日目 (最終日)

3日目も天気に恵まれた、気持ちのいい朝を迎えました。

午前9時に起床し、10時にチェックアウトです。

f:id:cluex-developers:20161014104553j:plain

   

3日目は土浦市街の方で貸会議を借りました。夕方まではこちらで各チーム作業を行い、最後に経過発表です。

お借りしたのは、「ワークヒル土浦」の特別会議室。社内の会議などであれば法人でも利用ができます。

10時~17時の利用で4000円ほどと、なかなかのお手頃価格です。

土浦市勤労者総合福祉センター「ワークヒル土浦」

   

人数のわりに、とても立派な会議室でした。

f:id:cluex-developers:20161014151114j:plain

こちらで一通り作業をした後、

各チームからの合宿目標の結果発表と振り返りを行いました。

       

今回の合宿で取り組んでいた、 「ES2015(ES6)の導入」や「Rspecカバレッジ」に関しては、追ってブログにまとめたいと思いますので、しばしお待ちください。

       

観光編

JAXA(筑波宇宙センター)に入ってきました!

f:id:cluex-developers:20161014175051j:plain

     

合宿最終日、会議室で結果発表などを終えた後、

20分ほど車を走らせて茨城県つくば市にある「JAXA 筑波宇宙センター」に行ってきました。

筑波宇宙センター | ファン!ファン!JAXA!

   

日本の宇宙産業を牽引するJAXA

アメリカではNASAスペースシャトルが引退し、SpaceXなどの民間企業へのシフトが進んでいますが、そんな宇宙を相手にする仕事はロマンがあってすごいなあと個人的には思うところです。

最先端の技術たちを駆使して宇宙という過酷な環境に挑むって本当にすごいです。

教育にも最先端の技術をどんどん導入していきたいです。

     

という訳で、JAXA見学です!

   

ISSの日本実験棟「きぼう」

f:id:cluex-developers:20161014153413j:plain

中にも入れちゃいます。

f:id:cluex-developers:20161014175208j:plain

   

宇宙服 (1着あたり10億円するそうです)

f:id:cluex-developers:20161014153511j:plain

興味津々です。

f:id:cluex-developers:20161014174004j:plain

f:id:cluex-developers:20161014153844j:plain

   

その他にも衛星やロケット、エンジンなどが実物大で見れます。

f:id:cluex-developers:20161014154253j:plain

f:id:cluex-developers:20161014174904j:plain

f:id:cluex-developers:20161014175003j:plain

       

施設の方(JAXAを引退された方?)に、

宇宙に関して、衛星や衛星に使用している技術など

色々なお話を付きっきりで伺うことができました。

 

f:id:cluex-developers:20161014154645j:plain

ありがとうございました。

f:id:cluex-developers:20161014181714j:plain

         

合宿のまとめ

前回に引き続き、今回も有意義な合宿となりました。

 

今回の合宿での反省点や改善点は次回以降にまた活かしていきたいと思いますが、

これから開発合宿をやろうと考えている方にも、気をつけるポイントなどをあげておこうと思います。

 

Keep

・メンバーのチームワーク強化に繋がった。

・宿泊代がお手頃。

・旅のお供に温泉は大事。

・ご飯が美味しい。

・宿にネット環境が整っている。

・自然溢れる環境なのでとてもリフレッシュできた。

・近くに娯楽施設がないので、逆に集中できた。

   

Problem

・床に座って作業するときは座椅子があると良かった。

・作業に当てられる時間が意外と短い。合宿中にやることの選択と集中が大事。

   

開発合宿のポイント

合宿にあたって気をつけていたことは、

・合宿の目標には数値的な結果が見えやすい目標を。

→ 加えて、合宿の開始と同時に、すぐに作業に取り掛かれる様に準備しておくと時間を無駄にせずに済みます。

→ 開発合宿の醍醐味として合宿中に立てた目標を達成することがとても大事だと思うので、どうしても終わらなそうであれば合宿前にある程度の下準備をしておくといいかと思います。

 

・お酒は最終日の前夜のみに抑える。

→ ダメになりやすい人は控えましょう。お酒を飲むとせっかくの目標もどうでもよくなっちゃいます。

 

・ネット環境の確認。

wifiの有無はもちろん、速度面なども事前に確認しておくと安心です。

→ もしもの時に、モバイwifiを各自で持参しておくと何かと便利ですが、圏外になることもしばしばあります。

 

・電源の確保。

→ 作業する場所で電源が使用できるかはもちろんですが、延長ケーブルを持参すると便利なこともよくあるので、事前に確認しておくと安心です。

 

・お昼に温泉は入らない。

→ 温泉に入ると気持ちが満たされてせっかくの目標もどうでもよくなっちゃいます。ダメになりやすい人は控えましょう。

 

・近隣の施設や観光地を調べておく。

→ 特に宿泊施設内もしくは近場にコンビニがあるか確認しておくと安心です。24時間営業ではないコンビニもあるので気をつけておくといいかと思います。

 

・予定は詰め込み過ぎない。

→ 食事処の予約や会議室の予約など、時間的な余裕を持って対応できる様にしておくといいかと思います。

→ 合宿中にやることも基本的には1~2つに絞ると雑念なく集中できるのではないかと思います。

 

番外編

懐かしきスマブラ!!

実は遡ること2日目のBBQの後、懐かしきスーパーファミコン任天堂64が出現!

jiccaの方で貸していただきました!

   

プロジェクターも用意して頂いて懐かしい気分に浸りつつ、ガチバトル。

f:id:cluex-developers:20161014155218j:plain

f:id:cluex-developers:20161014175817j:plain

   

jiccaで飼われている猫です。

猫かわいいですね、猫。

f:id:cluex-developers:20161014180231j:plain f:id:cluex-developers:20161014180047j:plain

いかがでしたでしょうか?

日々の業務では対応できない経営課題や技術課題に取り組むために、新しい技術の試験の場に、新しい事業の創出の場に、開発合宿やってみてもいいかもしれません。

   

個人的にはリフレッシュもできてES6の導入も無事に進み、温泉に入れて美味しいご飯を食べれていい合宿でした。 これからまた更に事業スピードあげていきたいところです。

       

We're hiring!!

Cluexではビジネスサイド、エンジニアサイド共にメンバーを募集しています!

気になった方はご連絡ください!!

ビジネスサイド

www.wantedly.com

www.wantedly.com

エンジニア

www.wantedly.com

cluex.co.jp

SPDYも廃止されたのでnginxでhttp2使いたいけど、そもそもhttp2ってなに?っていうお話

こんにちは、高橋です。最近はnginxとrailsとswiftをぐるぐるしています。

nginxは非常にパワフルなwebサーバーで、アクセス数が増大した時した時でも非常に安定してトラフィックを捌いてくれています。

Ruby on Railsを使用する場合には、unicornやpumaなどのアプリケーションサーバーと併用して使うことが多いかと思いますが、どうしてもアプリケーションサーバーの方がボトルネックになってしまいます。nginxの処理能力をどれだけ極限まで高めても、結局はアプリケーションサーバーの方で処理しきれずに503が返ってきてしまうので、どうやってアプリケーションサーバーの方を安定的に処理能力をあげるか最近は悩むところです。。

 

最近ではAWSのEC2などでautoscalingもよく使われているので、1台あたりのトラフィックの処理能力はそれほどカリカリにまで高める必要はないのかもしれません。

 

ということで前置きはこんな所で、今回は(僕が勝手に)社内にもhttp2を取り入れたいなーと思い、色々調べてサーバー上で試験的に試してみたので、まとめてみました。

 

nginxでHTTP/2を使用する

Googleが提唱していたSPDYを取り込んだ形でhttp/2が標準化される流れとなり、これから本格的に広まって行きそうですが、nginxではバージョン1.9.5からhttp/2をサポートするようになりました。

HTTP/2とは

http/2は、http/1.1でプロトコルレベルでの問題となっていたデータ送受信における効率の悪さを克服する形で、http/1.1ではクライアントとサーバーがシングルシーケンスでデータをやりとりを行なっていたのが、http/2ではmultipleにシーケンスのやりとりを行うことができるようになりました。これによってデータ転送と処理の効率が上がりwebページを表示するまでのスピードが上がるというわけです。

http/1.1の場合 http/1.1ではブラウザのTCP同時接続は最大4~6に制限されています。 HTTP Head of Line Blockingによりブラウザのネットワークはこんな感じに、ひとつのファイルの読み込みが終わるまでは、それ以降のファイルの読み込みはブロックされます。

f:id:cluex-developers:20160928165756p:plain

   

http/2を使用した場合 同時に複数のファイルの読み込みを行うようになります。http/2ではブラウザの最大同時リクエスト数は100以上が可能とされていています。

f:id:cluex-developers:20160928165916p:plain

 

webブラウザのHTTP/2対応状況

2016年9月現在のブラウザの対応状況はこんな感じです。ChromeFirefoxなどのメジャーなブラウザではほぼほぼ対応していますが、IEsafariなどでは一部対応といった感じのようです。

Can I use... Support tables for HTML5, CSS3, etc f:id:cluex-developers:20160928203910p:plain

   

HTTP/1.1からHTTP/2への主な変更点

  • 根本となる部分はhttp/1.1と同じ
  • http/1.1のセマンティックスは変更しない
  • サーバーへのTCP接続数を1つに限定
  • TLSの機構を使用してプロトコルを自動選択
  • バイナリープロトコル
  • 多重化送信

などといったことが挙げられます。

HTTP/2を使用すると現在のTLSは一部機能が使用できない

よりセキュアな通信を確立するために、http/2は現在のTLSの一部機能を制限することで、セキュアなプロトコルとして現在は使用されています。今後http/2に対応した形の新しいTLS、もしくはそれに替わるものが出てくるかと思います。

制限される内容はこちら

  • プロトコル選択にはNPNではなくALPNを使用
  • サーバー認証を共有できる接続は接続共有が可能
  • TLSプロトコルは1.2以上のみが使用可能
  • TLS Compressionの禁止
  • Renegotiationの禁止
  • DH鍵は2048bit以上を使用
  • AEAD(認証付き暗号)のうち、GCMとCCM以外の暗号方式は利用できない

 

 

NPN (Next Protocol Negotiation) からALPN (Applicaiton-Layer Protocol Negotiation) へ

http/2に先行して、NPN (Next Protocol Negotiation) はTLS false start方式の次の規格としてgoogleが提唱し、SPDYを支える次期アプリケーション層プロトコルネゴシエーションとして2010年ごろに登場しました。

背景的にはwebサイトの表示速度にはネットワークの回線速度よりもRTTの影響が大きく関わっており、TCP接続のハンドシェイクを毎回行うところに非効率さがあったと判断したのかと思います。

これに関しては2011年にAkamaiが行なった調査を簡単にまとめた2012年の記事があるので、下記の引用画像を見ていただくとwebサイトの表示速度がRTT依存というのがよりわかりやすいかと思います。

f:id:cluex-developers:20160928235245p:plain 引用元: Latency: The New Web Performance Bottleneck - igvita.com

   

これまではNPNが使用されていましたが、今後の流れとしてはhttp/2での後押しもありALPNが一般的となりそうです。

   

NPNとALPNの簡単な違いは、サーバーとクライアントのどちらが使用するアプリケーションプロトコルの決定を行うかというところで、NPNではクライアント側によるプロトコルの選択が行われていましたが、ALPNではサーバー側がクライアントの使用できるアプリケーションプロトコルの中から使用するプロトコルを選択するという形になります。

 

ALPNを使用するにはOpenSSLのバージョン1.0.2以上が必要となりますが、サーバーのOSによっては最新のOpenSSLを入れてもALPNを使用できないこともあります。

   

CentOSでHTTP/2とALPNを使う

CentOSでhttp/2とALPNが使用できるのはCentOS 7 以上のバージョンとなります。

   

UbuntuでALPNを使う

Ubuntuでhttp/2とALPNを使用する場合は、今年リリースされた Ubuntu 16.04 LTS (Xenial) 以上のバージョンになります。

   

nginxでのhttp2を設定

現在、nginxでhttp/2を使うには、ngx_http_v2_moduleが必要となります。

Module ngx_http_v2_module

再ビルドなどに関しては他にも記事があるので割愛しますが、 サーバーのOSとOpenSSLのバージョンがhttp/2に対応していれば、nginxで使用するのは非常に簡単かと思います。

   

最後の最後で、もはやおまけみたいな感じになってしまいますが、listenディレクティブにhttp2と入れるだけで、基本的には完了です。

server {
  listen 443 ssl http2;

  # ~~~~~
}

付け加えるとしたら、SSLプロトコルの設定や、ssl_ciphersなどの設定などでしょうか。その辺りに関しましては、こちらの記事を流し読み程度にご参考にしていただけると嬉しいです。

cluex-developers.hateblo.jp

     

googleはもちろんのこと、facebooktwitterといった名だたるwebサービスで既にproductionへ導入されているhttp/2で、 徐々に様々なwebサービスで導入され始めていますが、本格的に普及するまではもうしばらく時間がかかりそうな気もします。

また進展があったら、記事にしたいと思います!

Cluexではエンジニアの方を探しています!

cluex.co.jp

AWS Lambdaを使用し、CloudfrontのInvalidationを走らせる

こんにちは。エンジニアの志村です。
先日assets on S3についての実装を行いました。   その際の記事は下記になります。

cluex-developers.hateblo.jp

今回はasset_syncにより、S3にassetファイルがアップロードされた段階でCloudfrontのInvalidationをLambdaを使用して走らせるという処理を実装したいと思います!

Invalidationとは

Cloudfrontのキャッシュを明示的に消す機能です。

dev.classmethod.jp

クラスメソッドさんの上記の記事が非常に分かりやすいかと思います。

キャッシュを使用するのに重要なポイントは適切なキャッシュ期間を設けることです。それを行わないと古いファイルがいつまで経っても配信され続けてしまいます。
特にassets on S3を使用する場合には、明示的にファイルをInvalidationしないといつまで経っても最新のassetsファイルがユーザーに配信されなくなります。
このような状況を防ぐために、S3にファイルが転送された段階で、assetsのキャッシュを消去する必要があります。

Lambdaの実装

ではLambdaの実装を始めていきます。

AWSコンソールからLambdaを選択します

「Create Lambda Function」を選択します

f:id:cluex-developers:20160916104010p:plain

今回はBlueprintは使用しないので「Skip」を選択します

f:id:cluex-developers:20160916104022p:plain

S3を選択します。

f:id:cluex-developers:20160916104040p:plain

項目 説明
Bucket assets用のBucketを選択
Event Type S3にassetsファイルがCreateされた段階でInvalidationを走らせたいので「Object Created(All)」を選択
Enable Trigger チェックを入れると、設定したイベントが走るようになります(production等で設定する場合は注意!)

※Prefix, Suffixは必要であれば入力して下さい。

LambdaFunctionを作成します

f:id:cluex-developers:20160916104141p:plain

項目 説明
Name 適当な名前を入力
Description 分かりやすい説明を入力
Runtime 今回はPythonにします

コードは下記のコードが非常に分かりやすかったのでそれを使用します。

www.cloudberrylab.com

※DistributionIDは自身のCloudfrontディストリビューションのIDを入力して下さい

from __future__ import print_function

import boto3
import time

def lambda_handler(event, context):
    for items in event["Records"]:
        path = "/" + items["s3"]["object"]["key"]
    print(path)
    client = boto3.client('cloudfront')
    invalidation = client.create_invalidation(DistributionId='ディストリビューションID',
        InvalidationBatch={
            'Paths': {
                'Quantity': 1,
                'Items': [path]
        },
        'CallerReference': str(time.time())
    })

因みにですが、boto3はPython用のAWS SDKです。 Lambda FunctionをPythonで書く際には必ずと言って良いほど良く使用します。 ドキュメントも充実しているので非常に使い勝手が良いと思います。

Boto 3 Documentation — Boto 3 Docs 1.4.0 documentation

ロールやその他の設定をします

「Lambda function handler and role」のRole→「Create a custom role」を選択します。 f:id:cluex-developers:20160916104432p:plain

そうすると下記のような画面が現れるので、下記のCloudfrontのInvalidationを許可するロールを作成します。 f:id:cluex-developers:20160916104616p:plain

下記のようなポリシードキュメントになります。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:*"
    },
    {
        "Effect": "Allow",
        "Action": [
            "cloudfront:CreateInvalidation"
        ],
        "Resource": [
            "*"
        ]
    }
  ]
}

最後に「Create function」を押下すれば完成です!

使い方

asset_syncでデプロイすると勝手にInvalidationが走ってくれます。 下記の画面で確認が出来ます。

AWSコンソールからCloudfrontを選択します

該当するディストリビューションのIDを選択します

f:id:cluex-developers:20160916104912p:plain

「Invalidations」タブを選択すると、Invalidationsの状態が閲覧出来ます。ここでデプロイ直後にStatusがProgressとなっていればトリガーがきちんと走っています。

f:id:cluex-developers:20160916105140p:plain

以上になります。 キャッシュの扱いって本当難しいなーと感じますが、この様に自動化してしまえば特に意識することも少なくなって、よりCloudfrontが使いやすくなりますね。

Rackの基礎

こんにちは!! エンジニアとしてインターンしている村田です。

今回はズバリRackについて書いていきたいと思います。

Rackに関しては日本語の資料もあまり多くないため、特にRailsの初心者の方はよく分からないまま見過ごしていることも多いかもしれません。

私も初心者なので探すのに苦労しました。。

そこで自分もまだまだではありますが、これを読んで自分と同じような初心者の方々がRackのぼんやりとしたイメージをつかんでいただけるように書いていきたいと思います。

それでは早速ですがやっていきましょう!

Rackが誕生した背景

まず何故Rackが必要なのか? ここがわからないと、話が頭に入ってこないですよね。

これはPSGIというPythonのインターフェースの仕様に影響を受けました。

以前のPythonフレームワーク界は、様々フレームワークが開発されたものの、そのフレームワークが特定のWebサーバーに依存した状態でした。 ただし、これではサーバーの乗り換え時の負担が大きかったりして、開発者も頭を悩ませることが多かったようです。 そこで、ある特定のルールを定めて(インターフェース)、そのルールのもとフレームワークとWebサーバーの開発を進めることにしました。

これがPSGIの正体です。

RubyフレームワークRailsSinatraを筆頭に多様性に富みますし、WebサーバーもWebrickをはじめとして様々な独自のデプロイ方法を持っています。

すると、当然PSGIの代わりとなるシステムが必要になりまして、そうやって検討されていった結果現在のRackが誕生していくことになります。

Rackってなに??

さていよいよ本題にいきましょう。

Rackとは、rackupというコマンドを打つことで指定したファイルを参照しながらWebサーバーを立ち上げられるようにしているライブラリ(gem)です。 もう少し機能自体の説明をするならば、HTTPの送受信処理を担当するモジュールです。

指定したファイル(デフォルトではconfig.ru。ちなみにruはruckupの略。)には独自のRuby DSLで様々なミドルウェアやアプリケーションを参照するように書かれています。 これをもとにWebサーバーを立ち上げていくことになります。

ビジュアル的に説明をするとこんな感じになっています。

f:id:cluex-developers:20160916102713p:plain

このようにミドルウェアを介して、RailsなどのRackアプリケーションに対してWebサーバーから来たHTTPをパースしたりしています。

試しにbundle exec rake middlewareと打ってみましょう。 例えばこのような感じになって出てくると思います。

use Raven::Rack
use ActionDispatch::Static
use Rack::Lock
use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007fd239f96d58>
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use BetterErrors::Middleware
use ActionDispatch::RemoteIp
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::Migration::CheckPending
use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
use ActionDispatch::Cookies
use ActionDispatch::Session::CacheStore
use ActionDispatch::Flash
use ActionDispatch::ParamsParser
use Remotipart::Middleware
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
use Warden::Manager
use ExceptionNotification::Rack
use Bullet::Rack
use OmniAuth::Strategies::Facebook
run Rails::Application.routes

ここに書いてあるuseの対象がRackミドルウェアです。 そして最後に書いてあるrunの対象はRackアプリケーション(Rails)になります。 ちなみにuserunはRack DSLなのですが、今回は詳しく触れませんので興味がある方は調べてみてください。

ruckupの正体

こちらをご覧いただければお分かり頂けると思いますが、実はrackupコマンドがやっているのはRack::Server.startをしているだけなんですよね。

と言われてもRack::Serverとかよく知らないって方がこの記事を読んでくださっていると思いますので、説明します。

Railsで開発をしているとrails s、省略しないで書くと、rails serverというコマンドを打つことがあると思います。 このrails serverというコマンドも実は省略形でして、正確にはRails::Server.startというコマンドを打っています。

何か先ほどとても似ているコマンドを見ましたよね? そうRack::Server.startです。

もちろんこの二つは関係がないわけではなく、Rails::ServerはRack::Serverのサブクラスになっているのです。

継承する中で、ポートを3000番にするなど様々な違いを作っているようですが基本的な動きの部分は引き継いでいることがわかっていただけたと思います。

Rackミドルウェアの話

Rackミドルウェアはもちろん自分で追加することもできますが、そのためには簡単なルールを幾つか覚えなくてはなりません。

1つのHashオブジェクトを引数に取る「call」メソッドが実装されていること callメソッドは「ステータスコード」、「ヘッダーを表現したHash」、「eachに反応するオブジェクト」の3つの要素を持った配列を返すこと

上にある2つがそのルールになります。 また慣習的にcallメソッドの引数にはenvを用いることも覚えておくと得をするかもしれません。

詳しく話を始めるとDSLの説明やその他いろいろな話を書かないといけないので割愛しますが、Railsのようなアプリケーションも最後はcallで呼ばれるので

lamda do |env|
[200, {"Content-Type" => "text/html"}, ["Hello, world!"]]
end

簡単に言ってしまえばこのような形で動いていることになります。 これは先ほどの条件を満たしていますよね。

最後に

私の説明では物足りなかった部分も多かったと思います。 でもこれをきっかけにしてRackのイメージを掴めた方が一人でもいらっしゃればとても嬉しいです。

私もわからないことだらけなのでもっと勉強していこうと思いますが、最後にこの記事を作る上で参考にさせていただいた記事を載せておこうと思います。

RailsGuides

Rackとは何か

Rack解説

パーフェクトRubyonRails

それでは失礼いたします。

vertical-alignの使い方と別の実装方法について

こんにちは、エンジニアの井戸田です。

先日 vertical-align ではまり、検索してみると予測変換で vertical-aling 効かない と出てくるので、結構皆さんもはまっているんだなと感じました。 なので今回は僕もはまったcssのプロパティである、 vertical-align の値・使い方や、垂直方向の調整の別の実装方法を紹介したいと思います。

コードについて、htmlはslim、cssはscssを使用しているので注意してください。

vertical-alignの説明

vertical-align は行内のテキスト、画像の垂直方向の揃え位置を指定するプロパティです。 vertical-align は適用できるのが、 inline要素テーブルセル です。なので divタグpタグ などの ブロック要素 に対して vertical-align を指定してもうまくいきません。
divタグpタグvertical-align を指定したい時は diplay: inline や, display: table-cell , diplay: inline-block を指定し、要素自体を変更する必要があります。

説明
baseline 適当した要素のベースラインを親要素のベースラインに合わせる(初期値)
top 適用した要素の上端を、行ボックスの上端に合わせる
middle 適用した要素の中心を、親要素の中心に揃える
bottom 適用した要素の下端を、行ボックスの下端に合わせる
text-top 適用した要素の上端を、親要素の文字の上端に合わせる (テーブルセルへの指定はできない)
text-bottom 適用した要素の上端を、親要素の文字の下端に合わせる (テーブルセルへの指定はできない)
super 上付き文字 (テーブルセルへの指定はできない) 
sub 下付き文字 (テーブルセルへの指定はできない)
pxなどの単位 px, emなどの単位を使用しての指定(ベースラインが揃った状態を0、正の値は上方向、負の値はした方向になる)
  • 初期値が baseline なのですが、画像などは baseline を持たないため、下端が親要素のベースラインに揃えられます。
  • middleとは、フォントの場合 0.5ex 。小文字の「x」の半分高さです。

toptext-topbottomtext-bottombaselinetext-bottom の違いについて、勘違いしやすいので下記にまとめておきます。

top と text-topの違い

.verticalAlign.verticalAlign-top
  span.example
    | Example
    span.parent
      | parent
      span.child
        | top

.verticalAlign.verticalAlign-textTop
  span.example
    | Example
    span.parent
      | parent
      span.child
        | text-top
.verticalAlign {
  margin: 0 0 8px 0;
  background-color: #f93;

  &-top {
    .child {
      vertical-align: top;
    }
  }

  &-textTop {
    .child {
      vertical-align: text-top;
    }
  }
}

.example {
  background-color: #fd7c79;
  font-size: 5rem;
}

.parent {
  background-color: #33cfff;
  font-size: 2.5rem;
}

.child {
  background-color: #24ff18;
  font-size: 1rem;
}

f:id:cluex-developers:20160909205157p:plain

上が top で 下が text-top になります。
text-top は親要素の文字の上端(一番高い場所)を基準とします。なので上端を基準として配置することが可能です。
top は適用した要素の上端を、行ボックスの上端に合わせるため上記のような表示になります。

bottom と text-bottomの違い

.verticalAlign.verticalAlign-bottom
  span.example
    | Example
    span.parent
      | parent
      span.child
        | bottom

.verticalAlign.verticalAlign-textBottom
  span.example
    | Example
    span.parent
      | parent
      span.child
        | text-bottom
.verticalAlign {
  margin: 0 0 8px 0;
  background-color: #f93;

  &-bottom {
    .child {
      vertical-align: bottom;
    }
  }

  &-textTop {
    .child {
      vertical-align: text-bottom;
    }
  }
}

// 省略 以下はtopとtext-topと指定の仕方は同じです。

f:id:cluex-developers:20160909205407p:plain

上が bottom で下が text-bottom になります。
text-bottom は親要素の文字の下端(ディセンター文字の下端)を基準として配置することが可能です。
また bottom は適用した要素の下端を、行ボックスの下端に合わせるため上記のような表示になります。

baseline と text-bottom

次は baselinebottom-text に対してどのような違いがあるのか、説明します。

.verticalAlign.verticalAlign-baseline
  span.example
    | Example
    span.parent
      | parent
      span.child
        | baseline

.verticalAlign.verticalAlign-textBottom
  span.example
    | Example
    span.parent
      | parent
      span.child
        | text-bottom
.verticalAlign {
  margin: 0 0 8px 0;
  background-color: #f93;

  &-baseline {
    .child {
      vertical-align: baseline;
    }
  }

  &-textBottom {
    .child {
      vertical-align: text-bottom;
    }
  }
}

// 省略 以下はtopとtext-topと指定の仕方は同じです。

f:id:cluex-developers:20160909205017p:plain

上記を見てみるとどのような違いがあるのか迷うと思うのですが、親要素( .parent )を対して line-height を入れてみると違いが良く分かります。

.verticalAlign.verticalAlign-baseline
  span.example
    | Example
    span.parent
      | parent
      span.child
        | baseline

.verticalAlign.verticalAlign-textBottom
  span.example
    | Example
    span.parent
      | parent
      span.child
        | text-bottom
.verticalAlign {
  margin: 0 0 8px 0;
  background-color: #f93;
  
  &-baseline {
    .child {
      vertical-align: baseline;
    }
  }
  
  &-textBottom {
    .child {
      vertical-align: text-bottom;
    }
  }
}

.example {
  background-color: #fd7c79;
  font-size: 5rem;
}

.parent {
  background-color: #33cfff;
  font-size: 2.5rem;
  line-height: 2; // ← 追加
}

.child {
  background-color: #24ff18;
  font-size: 1rem;
}

f:id:cluex-developers:20160909205503p:plain

.parent に対して line-height: 2; を指定すると、 baseline は親のフォントのベースライン(基準線)に合わせるので、ズレは生じません。ただ text-bottom は親の要素の文字の下端に合わせるので、ズレが生じてしまいます。
上記の例の様に親の要素で line-height (や height などの縦幅調整をするプロパティ)を指定している時は注意が必要です。

(実際に実装する時には 初期値が baseline なので、 vertical-aling: baseline; の指定は必要ありません。)

ブロック要素への対応

テーブルセルとdisplay: table-cellの使い方

テーブルセルとは tableタグ で使用する tdタグ , thタグ のことです。こちらの要素に対しても vertical-align を使用すことができます。但し text-top , text-bottom , super , sub は指定することができません。 display: table-cell での実装方法は下記の様になります。下記の例では .block を垂直方向に中央揃えにしています。

.tableCell
  .block
    | block
.tableCell {
  display: table-cell;
  height: 100px;
  background-color: #f93;
  vertical-align: middle;
}

f:id:cluex-developers:20160909205532p:plain

block要素 に対して vertical-align を使用する場合には、垂直方向の調整をしたいタグの親要素に対して display: table-cell , vertical-align: ◯◯ を指定することで、使用することができます。 ただ表に関係ない要素に display: table-cell を指定するのはバットな気がしますね。

display: inline-block

display: table-cell を使用しないで display: inline-block を使用する方法もあります。inline-block要素 とは inline要素block要素 を兼ね備えた要素です。下記の例では2つの inline-block要素 を垂直方向に中央揃えにしています。

.block
  .inlineBlock1
    | inlineBlock1
  .inlineBlock2
    | inlineBlock2
.inlineBlock1 {
  display: inline-block;
  height: 8rem;
  background-color: #fd7c79;
  font-size: 2rem;
  vertical-align: middle;
}

.inlineBlock2 {
  display: inline-block;
  background-color: #33cfff;
  font-size: 2rem;
  vertical-align: middle;
}

f:id:cluex-developers:20160909205049p:plain

block要素 に対して display: inline-blockvertical-align: ◯◯ を指定することで、使用することができます。 inline-block要素inline要素 で異なるのが、 inline-block要素 の場合は横幅・縦幅が指定が可能なので、その分 inline要素 より使い易いかも知れません。

block inline inline-block の違いについては、また別の機会に詳しく話したいと思います!

vertical-alingを使用しないで垂直方向を調整する方法として

  • line-height
    line-height を使用して、親要素のheightに調整する。なんとなくバットな調整方法だと思います・・・。
  • position
    親のblock要素に対して position: relative 、子要素に対して postion: absolute を指定する。
    ただ垂直方向の中央揃えにしたいと思っていても、 postion:absolute は指定した要素の本来の位置からの相対位置なので top: 50% という指定をしていても、その要素の上端が真ん中にきてしまうので、 top: 50% ではなく調整する必要があります。

上記2つを挙げましたが、それぞれ調整が難しいと思います。そこで僕個人がおすすめしたいのが FlexBox を使用する方法です。

FlexBoxを使用し垂直方向を調整する

FlexBox とは「Flexible Box Layout Module(フレキシブルボックスレイアウトモジュール)」のことで、要素をFlexコンテナというボックス内にフレキシブルで簡単に整列する方法です。

FlexBoxに対応しているブラウザは下記のようになります。

f:id:cluex-developers:20160909210303p:plain

参考) Can I use... Support tables for HTML5, CSS3, etc

  • IE11から正式対応。
  • safariには -webkit- のベンダープレフィックス必要のようです。

垂直方向の調整をするために align-items 又は align-self というプロパティを使用します。

align-items と align-self

  • align-items
    flexコンテナに対して指定することで、コンテナ内のすべてflexアイテム(子要素)に対して、同じ位置に調整します。
  • align-self
    flexアイテムに対して指定することで、個別に位置を調整します。
説明
baseline flexアイテム内の文字のベースラインを揃える。 最初のflexアイテムのbaselineに揃うようです。
flex-start flexアイテムの上端を揃える( flex-directioncolumncolumn-reverse の場合は右側を揃える)
flex-end flexアイテムの下端を揃える( flex-directioncolumncolumn-reverse の場合は右側を揃える)
center 適用した要素の中心を、親要素の中心に揃える( flex-directioncolumncolumn-reverse の場合は横幅の中央を揃える)
stretch レックスアイテムの高さを揃える( flex-directioncolumncolumn-reverse の場合は横幅を揃える)

下記の例では、 align-items: center を使用して実装してます。

.flexBox
  .flexItem1
    | flexItem1
  .flexItem2
    | flexItem2
  span.flexItem3
    | flexItem3
.flexBox {
  display: flex;
  align-items: center;
}

.flexItem1 {
  background-color: #fd7c79;
  height: 200px;
}

.flexItem2 {
  background-color: #33cfff;
  height: 80px;
}

.flexItem3 {
  background-color: #24ff18;
  height: 160px;
}

f:id:cluex-developers:20160909205040p:plain

.flexBox に対して display: flex , align-items: center を指定することで、flexコンテナ内の全てのflexアイテムが垂直方向に中央揃えになります。 このように簡単に垂直方向を調整することができるので、ブラウザの対応に気をつけなければいけませんが、 FlexBox はとても便利です。

もし vertical-align での調整が難しい場合には、 FlexBoxの導入をぜひ検討してみてください。弊社が運用しているmamanokoでも flex を多用してます。

余談

株式会社Cluexではエンジニアの方を探しています!

cluex.co.jp

GeocoderとGeokitを使用して、現在地周辺検索を行う機能を実装してみた

こんにちは、エンジニアの神山です。最近大豆製品ばっかり食べています。

今回はWebサービスでよく見かける、現在地周辺検索を行う機能の実装について書きました。

例えば現在地から半径2km内にあるレストランを検索したり、現在地より最寄りの駅を探したりできる機能です。

今回使用したGemは、 geocodergeokit-rails です。

まずこの2つのGemにフォーカスして話を進めていきます。

GeocoderとGeokit

これらのGemは住所から緯度経度を割り出したり、緯度経度を用いた検索ロジックを提供しております。

具体的には以下の様な事ができます。

  • 住所から緯度経度を割り出す
  • 緯度経度から住所を割り出す
  • IPアドレスから緯度経度を割り出す
  • 指定したオブジェクトまたは緯度経度を中心に、指定した距離内や範囲(ex: 2km~5km)にある施設を検索する
  • 2点間の距離を算出する
  • あるオブジェクトに対して指定したオブジェクトが位置する方角を示す
  • 指定したオブジェクトまたは緯度経度を中心に、長方形状に指定した範囲内ある検索する

住所から緯度経度を割り出すとは、「東京都中央区◯◯」という文字列を判別し、北緯35度、東経139度というように緯度経度に直すことです。

GeocoderとGeokitの比較

Geocoderの利点

Geocoderを使うメリットは、住所から緯度経度を割り出す機能が優秀という点です。

GeocoderはGeokitより多くのAPIを網羅しております。

GeokitはGoogle・Yahoo・Bing・FCC・MapQuest・Mapbox・OpenCageの7つに対して、 Geocoderは上のFCCを除いたものに加えて、Google Maps API for Work・Google Maps API for Work・Nominatim・Yandex・Geocoder.ca・Geocoder.us・Ovi/Nokia・Here/NokiaESRI・Mapzen・Pelias・Data Science Toolkit・Geocodio・SmartyStreets・OKF Geocoder・Geoportail.lu・PostcodeAnywhere Uk・LatLon.ioの25個網羅しています。

使用できるAPIの数が多いほうが、最適なものを選べるので使い勝手がいいですね。

Geokitの利点

一方、Geokitを使うメリットは、オブジェクトを絞るメソッドを使用する際メソッドチェーンが使えるという点です。

Geocoderだと可能な場合と不可能な場合があるようです。

例えば、Geokitのメソッドであるwithinを使うとき、

prefecure_id = 1
shop_type = restaurant
latitude = 33.33
longitude = 140.44

Shop.within(2, [latitude, longitude]).tap do |scoped|
  scoped.where!(prefecure_id: prefecure_id) if prefecure_id
  scoped.where!(shop_type: shop_type) if shop_type
end

のようなことが出来ます。 例えば絞込を行う等、メソッドチェーンを使用したい場面を想定するとGeokitのほうが使い勝手が良いですね。

上記の理由により今回は、

  • Geocoder: 緯度経度の割り出しを行う
  • Geokit: 現在地周辺検索を行う

の様な使い分けをしました。

ちなみにgeocodergeokitの人気を比較しているサイトがあったので載せておきます。

ruby.libhunt.com

Geocoderの使い方

住所から緯度経度を割り出すことを geocoding といいます。 geocoding を行いたいカラムがあるモデルに以下のように記述します。

geocoded_by :address
after_validation :geocode, if: :address_changed?

geocoded_bygeocoding を行いたいカラムを指定します。 addressというカラムに住所(ex: 東京都港区〇〇)が入っており、その住所を基に検索を行いたいような場面です。

after_validationgeocoding するタイミングを指定します。 上のように書くと、addressカラムに変更があった際に、自動でgeocodingされるようになります。

Geokitの使い方

緯度経度を使って出来ることを、GitHubより抜粋しました。

# 指定した緯度経度から半径5km内のオブジェクトを検索
Shop.within(5, origin: [33.33, 140.33]) 

# 指定した緯度経度から半径5km外のオブジェクトを検索
Shop.beyond(5, origin: [33.33, 140.33])  

# 指定した緯度経度から2km~5km内のオブジェクトを検索
Shop.in_range(2..5, origin: [33.33, 140.33]) 

# 緯度が30.33~35.55内でかつ、経度が135.55度~145.55度内にあるオブジェクトを検索
Shop.in_bound([[30.33, 135.55], [35.55, 145.55]]) 

# 指定した緯度経度から一番近いオブジェクトを検索
Shop.closest(origin: [33.33, 140.33])

様々な検索方法がありますね。 因みにGeocoderでも同じようなこと検索は可能です。

現在地の検索機能

では、現在地より半径2km以内にある店を検索する機能を実装していきます。 「現在地から探す」ボタンを押した時に、上記の条件で検索が行われるような実装です。

「現在地から探す」ボタン押下後の挙動としては、 Javascriptを用いて現在地の緯度経度情報を取得→Geokitのメソッドを用いて緯度経度付近にある施設を検索して、Viewに表示させる という流れになります。

Model

Shopモデルのaddressカラムをgeocodingします。

  • models/shop.rb
geocoded_by :address
after_validation :geocode, if: :address_changed?

address_changed?というメソッドはActiveRecord::Dirtyの機能です。{column_name}_changed?で該当するカラムが変更された時にtrue`を返します。 詳しくは下記を御覧ください。

qiita.com

Controller

Geokitのwithinメソッドを用いることにより、現在地から2km以内の店が@placesに渡されます。

  • shops_controller
def search
  latitude = params[:latitude]
  longitude = params[:longitude]

  @places = Shop.all.within(2, origin: [latitude, longitude])
end

View

  • shops/index
    「現在地から探す」ボタン部分になります。
.currentLocation
  = button_tag ‘現在地から探す’, type: ‘button’

Javascript

現在地の取得には、HTML5Geolocation APIを使用します。 以下の様なコードになります。

class @CurrentLocations
  @getCurrentLocation: ->
    $('.currentLocation').on 'click', ->
      if navigator.geolocation
        navigator.geolocation.getCurrentPosition(successGetPosition, failGetPosition, options)
      else
        message = 'ご使用中のブラウザは現在地検索に対応されておりません。'
        Alert.set('warning', message)

  successGetPosition = (position) ->
    window.location.href = "/shops/searches?latitude=#{position.coords.latitude}&longitude=#{position.coords.longitude}"

  options =
    enableHighAccuracy: true

  failGetPosition = (error) ->
    switch error.code
      when 1
        message = '位置情報の提供を許可してください。'
      when 2
        message = '位置情報の取得に失敗しました。'
    Alert.set('warning', message)

では上から見ていきます。

  @getCurrentLocation: ->
    $('.currentLocation').on 'click', ->
      if navigator.geolocation
        navigator.geolocation.getCurrentPosition(successGetPosition, failGetPosition, options)
      else
        message = 'ご使用中のブラウザは現在地検索に対応されておりません。'
        Alert.set('warning', message)

@getCurrentLocation が現在地を取得する関数です。

navigator はブラウザの情報を取得するオブジェクトです。 そして.geolocationでブラウザが Geolocation API に対応しているか調べています。 対応していないブラウザもあるので注意して下さい。(IE8.0など)

ブラウザが Geolocation API に対応している時、 geoCurrentPosition で現在地の取得を試みます。 geoCurrentPosition の第一引数は現在地の取得に成功した時に実行され、第二引数は失敗した時に実行されます。 第三引数は現在地の取得の際のオプション設定をしております。

ちなみにブラウザが Geolocation API に対応していてもユーザーが位置情報の提供を許可していない状況も考えられます。 そのような場合、 geoCurrentPosition が呼ばれると自動で許可するか否かのポップアップが出るようになっております。これまた便利ですね。

ブラウザが Geolocation API に未対応である場合は、 else の部分が実行されます。 ここでは「対応していません」というアラートを出すようにしています。

  successGetPosition = (position) ->
    window.location.href = "/places/searches?latitude=#{position.coords.latitude}&longitude=#{position.coords.longitude}"

successGetPosition は現在地の取得に成功した時の関数です。 positionオブジェクトに現在地の情報が入っており、position.coordsで様々な情報を取り出せます。

例えば、 position.coords.latitude で緯度を取得できたり、 position.coords.logitude で経度を取得できます。

そして現在地の取得に成功した時に、現在地周辺の店の一覧ページに移動します。 またControllerに緯度経度の値を渡しております。

  options =
    enableHighAccuracy: true

options はその名のとおり、現在地取得の際のオプション設定です。 enableHighAccuracyGPSなどを使った高精度な現在地の取得を可能にします。 他にもタイムアウト設定やキャッシュの設定なども可能です。

  failGetPosition = (error) ->
    switch error.code
      when 1
        message = '位置情報の提供を許可してください。'
      when 2
        message = '位置情報の取得に失敗しました。'
    Alert.set('warning', message)

failGetPosition は現在地の取得に失敗した時に実行される関数です。 error というオブジェクトに失敗の原因が入っております。 今回は、エラーの原因ごとに別々のアラートを出したいので、 error.code ごとに条件分岐をしております。

error.code = 1 はユーザーが位置情報の許可をしていない時を指しております。
error.code = 2 は現在地の取得に失敗した時を指しております。

Geolocation API の詳しい情報は以下のリンク先にも書かれているので参考にしてみてください。

HTML5 Geolocation

以上が現在地周辺の施設を検索する機能の解説でした。 非常に便利なGemですね。

改良点等アドバイスいただけたら大変幸いです。

参考