メドピア開発者ブログ

集合知により医療を再発明しようと邁進しているヘルステックカンパニーのエンジニアブログです。読者に有用な情報発信ができるよう心がけたいので応援のほどよろしくお願いします。

poltergeistからheadless chromeへ移行する時に気をつけること

こんにちは。メドピアのRuby(Rails)化をお手伝いしている@willnetです。最近は子育てに忙しくしています 👶

先日、メドピアで利用しているcapybaraのjavascript driverをpoltergeistからheadless chrome(selenium-webdriver)に移行しました。driverを変更するにあたって既存のテストコードをいくつか修正する必要があったので、そこで得た学びを共有したいと思います。

なぜ移行したのか

ここ数年、Railsでエンドツーエンドのテストを書くときにはpoltergeistを使う、というのがデファクトスタンダードだったはずです。それ以前はみんなcapybara-webkitを使っていましたが、poltergeistはバックエンドにPhantomJSを使っており、Qtに依存しているcapybara-webkitと比べてビルドが簡単だったことから徐々にシェアを増やしていったように思います*1

そんな中、だいたい1年ほど前にChromeのバージョン59からヘッドレスモードが実行できるようになりました。これをうけて、PhantomJSの開発が停止されました。PhantomJSをメンテするのはだいぶ大変だったみたいです。

これは当然、依存しているpoltergeistにも影響します。PhantomJSの依存をやめる構想もあったようですが、リポジトリを眺めている限りではあまり進捗しておらず、さらにpoltergeistの開発自体あまりアクティブではないように見えます。

ここまでの経緯を考えると、headless chromeに切り替えるのは無難な選択と思われるのですが、移行時期には考慮が必要でした。headlessモードが使えるchromeがリリースされた当初は、chromedriver(chromeを別プロセスから動かすのに必要なもの)に大きめのバグがあり、すぐに移行するには不安な状況でした。しかしそれから1年ほど経過した今なら問題なく移行できるのではないか?となったのでした。

そんなわけで、フィーチャスペックのときに使うドライバをpoltergeistからheadless chromeに変更しました。

f:id:willnet:20180619182309p:plain

移行に必要なこと

リリースから1年経ってこなれたと言っても、poltergeistからheadless chromeへの変更は単純に入れ替えただけでは完了しません。テストコードの変更も必要です。

僕たちはcapybaraのインターフェースを経由してheadless chromeやpoltergeistを利用しているので、移行してもテストの書き方は基本的には変わりません。ただ、ドライバによって対応しているメソッドとしていないメソッドがあるため、poltergeistのときは動いていたコードがheadless chromeでは動かないケースがあるのでした。そういうところは動くように書き換えてあげる必要があります。

大抵の箇所は機械的に置換していくようなやり方でOKなのですが、テストの内容によってはもっと頑張らなければならないところがあるのでそれを紹介します。

ファイルダウンロードのテストを変更する

例えばリンクをクリックするとcsvがダウンロードできるようなページがあったとします。poltergeistを利用している場合、次のようにレスポンスヘッダを見て、Content-Typeが text/csv であることをもってテストを成功をみなすという方法があります。

context 'CSVダウンロード用のページに遷移したとき' do
  before { visit csv_download_path }

  it 'かつ"CSVダウンロード"をクリックしたらCSVファイルがダウンロードできること' do
    click_on 'CSVダウンロード'
    expect(page.response_headers['Content-Disposition']).to eq("attachment; filename=\"download.csv\"")
    expect(page.response_headers['Content-Type']).to eq("text/csv")
  end
end

上記のコードは、headless chromeに切り替えるとうまく動きません。どうやらheadless chrome(正確にはselenium webdriver)はレスポンスヘッダを見るAPIを提供していないようです。バグとかではなくそういう設計方針の模様

レスポンスヘッダを見れないとしたら、実際にダウンロードしたファイルの内容を確認するしかありません。こちらのエントリを参考にしつつ、実際にダウンロードしたファイルの中身をチェックする方法に変更しました。

まず、次のようなヘルパーメソッド群をモジュールとして定義し、フィーチャスペックで使えるようにします。

module DownloadHelper
  TIMEOUT = 10
  PATH    = Rails.root.join('tmp/downloads')

  module_function

  def downloads
    Dir[PATH.join('*')]
  end

  def download
    downloads.first
  end

  def download_content
    wait_for_download
    File.read(download)
  end

  def wait_for_download
    Timeout.timeout(TIMEOUT) do
      sleep 0.1 until downloaded?
    end
  end

  def downloaded?
    !downloading? && downloads.any?
  end

  def downloading?
    downloads.grep(/\.crdownload$/).any?
  end

  def clear_downloads
    FileUtils.rm_f(downloads)
  end
end

RSpec.configure do |config|
  config.include DownloadHelper, type: :feature
  config.before(:suite) { Dir.mkdir(DownloadHelper::PATH) unless Dir.exist?(DownloadHelper::PATH) }
  config.after(:example, type: :feature) { clear_downloads }
end

これにより、先程のテストを次のように変更することができます。

context 'CSVダウンロード用のページに遷移したとき' do
  before { visit csv_download_path }

  it 'かつ"CSVダウンロード"をクリックしたらCSVファイルがダウンロードできること' do
    click_on 'CSVダウンロード'
    expect(download_content).to eq "row1,row2\n"
  end
end

さてこれで解決…と思いきや、もうひとつ対処が必要だったりします。どうやら、 headless chromeはデフォルトでファイルダウンロードをしないようです。そこでstackoverflowの回答を参考に、ファイルダウンロードを許可するようにしました。具体的には次のようにconfig/rails_helper.rbに記述しています(bridge変数以下がファイルダウンロードを許可しているコード)。

Capybara.register_driver :headless_chrome do |app|
  driver = Capybara::Selenium::Driver.new(
    app,
    browser: :chrome,
    desired_capabilities: Selenium::WebDriver::Remote::Capabilities.chrome(
      login_prefs: { browser: 'ALL' },
      chrome_options: {
        args: %w(headless disable-gpu window-size=1900,1200 lang=ja no-sandbox disable-dev-shm-usage),
      }
    )
  )
  bridge = driver.browser.send(:bridge)
  path = "session/#{bridge.session_id}/chromium/send_command"
  bridge.http.call(
    :post, path,
    cmd: 'Page.setDownloadBehavior',
    params: {
      behavior: 'allow',
      downloadPath: DownloadHelper::PATH.to_s,
    }
  )
  driver
end

Capybara.javascript_driver = :headless_chrome

これでファイルダウンロードをheadless chromeでテストできるようになりました。

追記

最近(2019年9月)のchrome77からはこの方法だとテストが失敗するようになっています。詳細について新しい記事にしたので参考にしてください。

最近のheadless chromeを利用したファイルダウンロードのテスト方法について - メドピア開発者ブログ

ブラウザ上で見えない要素に対応する

headless chromeは、poltergeistと比べて「ブラウザ上で見える要素であるか否か」にシビアなようです。例えば、position: fixed; left: 1000px としているDOM要素があるとします。このとき、ブラウザのウィンドウサイズが(800, 600)のように小さく要素が画面外になる場合は、その要素は見えないという扱いになります。

この場合は単にウィンドウサイズを大きくしてあげればよいのですが、それでは対応できない場合は次のようにvisible: falseをつけて、見えない要素の中で指定のDOMが存在するかを確認する必要があります。

expect(page).to have_css('.super-right-dom', visible: false)

このように「画面外にあるので見えない」というのはわかりやすいのですが、なにをもって「見える」「見えない」と判断しているのか難しいなと感じるケースがあります。例えばあるページではbxSliderを利用して画像をカルーセル表示しているのですが、スクリーンショットで見えていることが確認できるDOM要素もheadless chrome的には見えない扱いをされていました。

やむを得ずこれもvisible: falseとして対応しました。しかしこのあたりはまだ深く調査できてないので、詳しい方いたら教えていただけると嬉しいです。

細かい変更点

次のような細かい点にも対応しました。このへんは単に置換するだけなので軽く箇条書きで済ませます。

  • triggerメソッドがない
    • trigger('click')を使わず、clickメソッドを使うようにした
  • ウィンドウのリサイズのお作法が違う
    • before: Capybara.page.driver.browser.resize(320, 580)
    • after: Capybara.current_session.current_window.resize_to(320, 580)
  • ブラウザのダイアログが表示される部分は、polgergeistでは自動でacceptされるが、headless chrome(というかselenium webdriver)ではどのように対応するかをaccept_alert {} みたいに書く必要がある

まとめ

polgergeistからheadless chromeへの移行において、気をつける点と変更が必要な点について紹介しました。headless chromeに移行することで、今後メンテナンスが続いていくであろうという安心感が得られます。しかし、セットアップには今回紹介したようにコツをつかむ必要があります。

この記事がこれからheadless chromeへ移行する人や、移行に苦労している人にとって参考になれば幸いです。


是非読者になってください(︎ ՞ਊ ՞)︎


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html

Rails未経験/経験年数が2年以内のポテンシャルエンジニア絶賛募集中です!*2

Rails開発してみたい人、新しい技術に意欲的な人、リードエンジニアに教育されてみたい(?)人、医療に関わるサービス開発を行ってみたい人、その他メドピアに興味を持った方などは、是非、コンタクトを取って頂ければと思います!

*1:PhantomJSもQtに依存しているけど、PhantomJSはバイナリが配布されているのでビルドせずにインストールできるというのが大きかった

*2:リードエンジニアも募集中です!

Rails × ECS でオートスケーリング&検証環境の自動構築

マリオカートでカーブを曲がるときに体を傾斜させてしまうCTO室 kenzo0107 です。

今回は 2018/04/02 にリニューアルしたイシコメの Rails × ECS についてです。

イシコメとは?

「イシコメ」は、医師10万人の声でつくるヘルスケアメディアです。
医師と一般の方々をつなげることで、医療情報格差を埋めることを目指しています。

MedPeerの10万人の医師会員に協力いただいたアンケート結果をもとに編集部で記事を執筆し、医師監修の上で配信。多くの医師の声を反映することで、より正しい情報を提供しています

https://ishicome.medpeer.jp/

リニューアル経緯

リニューアル前は以下のような構成でした。

  • フロントに Laravel 5
  • バックに Drupal
  • Docker on EC2
  • コンテナイメージの S3 でのプライベート管理

Docker がまだ出てきて間もない頃、当然、AWS ECS がリリースされておらず
果敢に技術的にチャレンジした痕跡が多々ありました。

ですが、 当時の構成では以下の問題がありました。

  • イメージの管理やデプロイがし辛い
  • スケーリングが考慮されてない

また、
Google 検索結果の医療や健康に関する検索結果の改善 が追い風となりトラフィックが伸び、スケーリングへの配慮が重要になりました。

webmaster-ja.googleblog.com

上記の問題を AWS ECS・ECR の恩恵を受けることで解決しようと考えました。

また、
開発促進をすべく、弊社で開発の知見のより多い Rails へのリプレイスを行う運びとなりました。

まず結論

※ 左側がユーザサイド、右側が管理画面になります。

  • イメージ管理は ECR
  • デプロイは ecs-cli
  • CloudFront > ALB > Nginx > Rails というルーティング*1
  • Tasks, EC2 をオートスケーリング
  • RDS Aurora MySQL へリプレイスし Read Replica オートスケーリング

メディアサイトというサイト特性もあり、アクセスが集中しスパイクすることを鑑みて CloudFront でのキャッシュを有効にし、オートスケーリングできるところはする様にしました。

リニューアル直後の 2018年4月頃、麻疹が流行した際に麻疹関連の記事へのアクセスが急増した際は、本当にこの構成にしておいてよかったと思いました。

https://ishicome.medpeer.jp/entry/767ishicome.medpeer.jp

続いて ECS でサービス構成するに当たって考慮したことをまとめました。

ECS への準備で考慮したこと

  • コンテナ設計
  • デプロイ
  • ロギング
  • オートスケール
  • バッチ処理
  • 検証環境

コンテナ設計

Nginx

Nginx をルーティングに挟んだのは以下の理由からです。

  • Rate limit の設定を細かくコントロールしたかった。*2
    • AWS WAF では最低でも 5分間に 2000 リクエスト以上でアクセス制限可能
  • IP 直指定回避

Rails

Rails コンテナについて以下の点を設計考慮しました。

secrets.yml

yaml_vault で暗号化・復号するようにしました。*3

  • KMS のエイリアスキーを作成し
  • そのキーでの暗・復号権限を IAM Group で管理する

こうすることで
権限の付与時には IAM Group に含める、
権限の剥奪時には IAM Group から除外する、
と管理が楽になりました。

Task Definition には基本秘密情報を載せない様にしました。*4

  • 実行コマンド
// secrets.yml の暗号化 (env: production)
yaml_vault encrypt \
  config/secrets.yml \
  -o config/encrypted_secrets.yml.production \
  --cryptor=aws-kms \
  --aws-region=ap-northeast-1 \
  --aws-kms-key-id=<kms alias> \
  --aws-profile <profile>

// secrets.yml の復号 (env: production)
yaml_vault decrypt \
  config/encrypted_secrets.yml.production \
  -o config/secrets.yml \
  --cryptor=aws-kms \
  --aws-region=ap-northeast-1 \
  --aws-kms-key-id=<kms alias> \
  --aws-profile <profile>
Dockerfile (Rails)

RAILS_ENV を渡してビルドし各環境毎に処理分けさせてます。

FROM ruby:2.5-alpine

RUN apk update \
  && apk upgrade \
  && apk add --update build-base mysql-dev nodejs tzdata git \
  && rm -rf /var/cache/apk/*

# TZ JST
RUN cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

ENV app /work/app
WORKDIR $app

COPY . $app
RUN bundle install -j4 --retry 6 --without test development --no-cache \
  && npm install --production --no-progress \
  && mkdir -p tmp/sockets \
  && mkdir -p tmp/pids

ARG RAILS_ENV

# docker build 時に db 接続しようとする為、接続しない様、nulldb 指定
RUN chmod +x docker/on_build.sh \
  && sync \
  && docker/on_build.sh

CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]

docker/on_build.sh

  • assets:precompile, assets:sync 実行時に S3 にアップロード, CloudFrontで配信 by asset_sync, fog-aws
#!/bin/sh

set -e

# docker build 時に db 接続しようとする為、接続しない様、nulldb 指定
DB_ADAPTER=nulldb bundle exec rake assets:precompile assets:sync RAILS_ENV=${RAILS_ENV}

デプロイ

ecs-cli を採用しました!

採用理由は以下の通りです。

  • ECS 向けの AWS オフィシャルのツール
  • 既存の設定した AWS credential 利用可
  • Task 定義が docker-compose.yml 形式
  • Fargate 対応可

Fargate Tokyo region が待ち遠しいです♪*5

デプロイフロー

  1. master, develop, qa/* に merge をトリガーに CircleCI ビルド
  2. テストがパスしたら CodePipeline 開始
  3. CodePipeline で cap (production|staging) deploy 実行

CircleCI には CodePipeline の開始・更新権限のみの AWS Access Key ID, Secret Key を設定しています。

CircleCI 上からデプロイしないの?

はじめに検討したのですが以下理由により上記構成を選択しました。

  • CircleCI とビルドサーバを分けることで CircleCI はテストに専念できる。
    → デプロイが終わるまでリソースを手放さず他のビルドが遅延するのを避ける。

ゆくゆくは CodePipeline でテストも実行し全てを完結させてみてコスト比較する検証をしたいと思っています。
既にあったら教えてください

ロギング

  1. コンテナから awslogs で CloudWatch に出力
  2. Lambda で日次で CloudWatch のロググループを S3 に保存

Rails コンテナでは環境変数に RAILS_LOG_TO_STDOUT: 1 を設定することで Rails のログを標準出力し、CloudWatch に流す様にしています。

また、お好みですが
lograge で CloudWatch 上のログの視認性が高まりました。

CloudWatch のイベントフィルターで

{$.db >= 1000}

とすることで DBで 1000 msec 以上時間を要したイベントを抽出することができます。

オートスケール

スケールアウト時に考慮したことと

  • まず EC2 インスタンスを増やして、その後、Task を増やす
    → Task が一方に偏ってしまう等の事象が発生してしまう為です。

CloudWatch の監視設定で evaluation_periods を EC2 < Task の様に設定しました。

  • インスタンス単体の CPU 使用率等のメトリクスでなく、Service でのメトリクスで考えること
    → 一時的な偏りでなく、全体的に負荷が高い場合にスケールする、という様にする為です。

  • RDS Aurora MySQL のオートスケールは Read/Write を switch_point で参照先を切り替える様にしました。

バッチ処理

クラスタを指定し ECS Scheduled Tasks で one-off container で定期実行します。

以下 Sitemap 定期更新タスク例です。

検証環境の自動構築

qa/* というブランチ名で push するとそのブランチに紐づくポートで検証環境を自動で構築する様にしました。

これによりエンジニア・ディレクターの検証回数が飛躍的に増え、画面を通じてコミュニケーションすることで認識の齟齬が減りプロジェクトがより円滑に進む様になりました。

現状、インスタンスタイプ m5.large の EC2 インスタンスをスポットで利用することで価格を抑えつつ 3~5 程度の検証環境が動作しています。

構築手順

  1. ALB の空いている Listener Port 取得
  2. 空き Port を元に target group 作成
  3. target group に branch 名をタグ付け
  4. インスタンス登録
  5. ALB Listener 作成
  6. ECS Service 作成
  7. service 名を branch 名がわかるようにラベリング
  8. QA 環境へ deploy

現状不要となった QA は cap で削除用コマンドを用意してますが ブランチ削除時に自動削除される等、検討中です。

ちなみに、どのブランチがどのポートを使っているかは Slack bot が教えてくれます。

まとめ

Rails × ECS へのリプレイスにより自動化できた部分が多く本当に運用が楽になったと感じます。

特に検証環境の自動構築は他プロジェクトでも望まれ導入を進めています。

また、Fargate の検証を実施しておりますので、その折には本ブログにまとめていきたいと思います。

以上です。
参考になれば幸いです。


是非読者になってください(︎ ՞ਊ ՞)︎


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html

インフラ構築・運用や開発プロセスの改善に携わっていただける SRE 募集中です!

https://medpeer.co.jp/recruit/entry/entry09.html

Rails未経験/経験年数が2年以内のポテンシャルエンジニア絶賛募集中です!*6

Rails開発してみたい人、新しい技術に意欲的な人、リードエンジニアに教育されてみたい(?)人、医療に関わるサービス開発を行ってみたい人、その他メドピアに興味を持った方などは、是非、コンタクトを取って頂ければと思います!

*1:Nginx を挟んだ理由はコンテナ設計で!

*2:時折、 /phpmyadmin のようなパスでアクセスしてくるような攻撃が1分間に 200 リクエスト程きますが、 AWS WAF の based rule では防げません。

*3:イシコメ リニューアル直後に Rails 5.2 credentials が出た為、secrets.yml での秘密情報管理をしています。

*4:EC2 上で ssm で値を取得し環境変数に設定する方法もありますが Fargate の時どうするのか検証仕切れず、一旦 secrets.yml に寄せる様にしました。

*5:本記事執筆時に Fargate Tokyo Region の 2018年7月 予定がアナウンスされる神タイミングでしたので、執筆を急ぎました

*6:リードエンジニアも募集中です!

RubyKaigi2018に参加してきました

こんにちは!メドピア歴1年、Railsエンジニアの小林です。

今回のブログでは、5月31日〜6月2日に仙台で開催されたRubyKaigi2018に全日参加してきましたので、その感想をお届けしたいと思います。

f:id:marikokobayashi:20180605163917j:plain

このブログを書いた人の背景

私はメドピアに入社することになって初めてRubyを書き始めました。

昨年9月に開催されたRubyKaigi2017時はまだRubyを書いて数ヶ月という状態だったのですが、実はそのときも参加しており、今回のRubyKaigi2018は2回目の参加になります。

なぜ前回、まだ右も左も分からない状態で参加したかというと、自分が仕事で一番使っているプログラミング言語の大規模な国際会議が日本国内であるのであれば、外から盛り上がりを見ているよりは実際に体験してみたいと思ったからです。

また、Rubyを書いている仲間を見つけて交流したいという気持ちもあり、参加しました。

今年は、去年のそんな私の姿が評価されたこともあってか(?)、RubyKaigiへ行きたいと表明したメンバー全員分の参加費・交通費・宿泊費が会社で補助していただけることになりました!

tech.medpeer.co.jp

本当は見たセッションすべての感想を書けたら良いのですが、長すぎてまとまりがなくなってしまいそうなので、本記事では特に印象が強かった部分だけを書かせていただきます。

個々のセッションについてもう少し知りたい方は、RubyKaigi中に参加したメンバーがそれぞれ見たセッションを速報としてブログにまとめましたので、こちらをご参照いただければと思います。

tech.medpeer.co.jp

(2日目以降のブログは上の記事にリンクがありますので、辿って下さい!)

速報性を大事にしていたため、突っ込みどころもあるかと思いますが、これはおかしいだろうという箇所がありましたら、ご指摘いただければと思います!

Matz基調講演について

1日目で特に印象に残ったことは何と言ってもMatzのキーノートです。今回は諺についてのお話。

Matzが大事にしている諺として下記の3つが挙げられていました。

  • 名は体を表す
  • 時は金なり
  • 塞翁が馬

名は体を表す

Rubyのカンファレンスに参加するようになってから、Matzのお話を聴講したことは何回かありますが、そのたびにMatzは毎回「名前重要」という話をしているということに気づきました。

名は体を表す、といいますが、確かにRubyがRubyという名前ではなかったらその言語に対して持つ印象も違っただろうと思います。持つ印象が違えば、コミッターやユーザーがその言語をどのように扱うかも変わってくると思います。そうであれば今のRubyとは全く別の言語になっていたかもしれません。

正しい命名付けを行うことは、正しく概念を理解していることでもあります。良いプログラマーは、良い命名者でもあります。

「概念がどういう局面で使われるか十分に理解していれば比較的簡単に名前をつけられる」「よいプロジェクトの名前はコミュニティの旗印になったり、求心力になったりする」、とMatzは説いていました。

ちなみに、もし今のネット社会の中で新たに言語の名をつけるとしたら、Rubyはググラビリティが悪いのでつけないだろうということでした。

確かにMatzが2014年に作ったもう一つの言語であるStreemは、実在する単語を少しいじったものであるので、ググればすぐMatzのリポジトリが出てきてググラビリティが良いですね。ちなみに某言語…おっとだれか来たようです。

時は金なり

時間は、誰にでも平等に1日24時間与えられています。時間をどう使うかは、どういう人生を歩むか、を決定づけます。

プログラミングで自動化できることは自動化していったほうが生産性の高い人生を歩むことができると思います。

Rubyには便利なメソッドがたくさんあるので、時間を有効活用するのを助けてくれる傾向があります。

また、プログラミングにおいては、問題が発生したときに早く解決できたほうが時間の短縮になります。問題が自分ひとりでは解決できないこともあると思いますが、そういった意味では「ほとんどの人にとって『質問に答えてくれる人が身近にいる言語』が一番いい言語」でしょう。

Rubyはコミュニティが活発なので、質問に答えてくれる人を比較的見つけやすい環境であると言えると思います。

処理速度については、Rubyは遅いと言う人も居ます(Rubyでググると「Slow」が候補として上がっています)が、現在もコミッターの方々がパフォーマンス改善を続けています。

書きやすさと処理速度についての良いバランスを取っている言語だと思います。

塞翁が馬

塞翁が馬というのは、中国の故事『准南子』に出てくる小話のことで、「人間万事塞翁が馬」とも言われているかと思います。

内容は、人生には良いことがあってもその後悪いことが起きることもある、悪いことがあってもその後に良いことが起きることもある、先のことはわからない、というお話です。

読む人によっては、良いことがあっても浮かれすぎるな、悪いことがあっても落ち込みすぎるな、という解釈もあるかと思います。

Rubyは多くの人に使われる言語となりましたが、先のことはわかりません。

他の言語に取って代わられ、Rubyユーザーが縮小してしまう未来もないとは言い切れません。

そんな中でも今後も時代に合わせた進化を続けることで、時代に合わせたRubyのあり方を模索していきたい、といった趣旨のお話でした。

私も実は個人的に以前からこの諺を気に入っており、人生そのものだと感じているため、座右の銘の一つとしておりました。

Matzの考え方には深く頷ける部分が多くあり、そのように強い信念、理念を持った言語設計者が作った言語だから多くの人に広まったのだなあと改めて思いました。

--

また、セッションの締めにはMatzは「Rubyに『言語仕様として』型が入ることはない」とおっしゃっていました。型推論についてが今議論されていますが、それはあくまで推論であるということです。

Rubyを使う人は型を使いたくない人も一定数いると思うので、もし言語仕様としてそこを変えてしまうとしたらかなりの既存のユーザーが置いてけぼりになるのではないかと思います。

時代の流行を気にしながらも、Rubyとしてそぐわないところは無理に採用しないというMatzの意志を感じました。

Ruby Committers vs the Worldについて

2日目の締めはRuby Committers vs the World。

壇上にRubyコミッターが勢揃いする光景は圧巻です。

コミッターたちが、寄せられた質問などについて答えてくれました。

このようにRubyコミッターが勢揃いすると、Matzの意見が必ずしも通っているわけではないということに気付かされます。

特にMatzが最近Ruby2.6に追加したというエイリアス「then」の命名の良し悪しについては熱い議論が交わされていました。

普段Rubyコミッターたちがどのように議論してRubyを改善・機能追加しているかの様子が垣間見れたような気持ちになります。

Matzが愛されて(いじられて?)いる様子には、ただのいちRubyユーザーである自分も何だかつられて笑ってしまいました。

f:id:marikokobayashi:20180604155312j:plain

セッションの見かたについて

今回2回目の参加でしたが、去年の1回目の参加後は「なにがなんだかわからない…参加費を無駄にしてしまったのでは…」と絶望して帰ってきたのですが、2回目に参加してみると「あれ、もしかしたら前回の反省がちょっと生きているかも、少しは成長したかも」と思いました。

具体的には、「すべてを1回でわかろうとしない、絶望しない」というところが大事なポイントかもしれません。

イベントに参加する人は、Rubyの内部実装やC言語を深くわかっている人だけでなく、私のようなRailsを使っているだけというWeb系エンジニアも多くいると思いますが、そんな中でも「知識不足ゆえにまったく理解できなそうなセッション」と「Web寄りやビジネス寄りで、現時点での知識でも少しは理解できるところがありそうなセッション」があると思うのです。

割り切って「現時点で少しは理解できるセッション」に絞って聞いてみるのも戦略としてありかなと思いました。

(もちろん自分の知識不足に喝を入れるために全然わからないセッションを聞きたい、という人はそれもアリかと思います)

また、セッションは全部の時間で聞こうとすると1日に6~7個聞くことになると思いますが、どれも全部内容が濃く、それぞれを深く理解するためには、無理に全部の時間に聞かなくても良いと思いました。

ありがたいことに、最近はセッションの動画がのちに公開される(今年も公開していただけるようです)ので、会場では体調を整え、自分の体力と相談しながら適度に休憩を入れるのも、長い3日間を乗り切るポイントかなと思います。

ステッカーについて

メドピアは今回ゴールドスポンサーとして参加したので、ステッカー置き場にステッカーを置かせていただきました。メドピアのキャラクター、メドベアちゃんがRubyと戯れている様子がステッカーになっています。

今後Ruby関連のイベントでスポンサーする機会があればそのときにも持っていくと思うので、今回もしもらい損ねたという人がいましたら、その機会に是非!

f:id:marikokobayashi:20180604154754j:plain

英語について

セッションの大半は英語なため、英語力に課題を感じました。RubyKaigi直前は英語力を高めるため下記のPodcastを聞いてみた*1のですが、それでもやはりセッションを1回聴講しただけで理解するのは難しいなと感じました。

Ruby Rogues Archive | Devchat.tv

5by5 | Ruby on Rails Podcast

のちにセッションの動画が上げられたら、繰り返し見て、理解を深めたいと思います。

観光について

RubyKaigiは、東京開催以外だと地元の美味しい料理を楽しんだり観光を兼ねられるのが1つの魅力でもありますね。

2日目の夜は、会社のメンバーみんなで牛タンを食べてきました。

予約していたお店は行ったことがある人はおらず不安でしたが、結果的にとても美味しく皆満足していました!

f:id:marikokobayashi:20180604154814j:plain f:id:marikokobayashi:20180604154845j:plain

ちなみに最終日翌日は日曜でしたので、個人的に仙台周辺を観光してきました。

時間のある方は松島まで行かれた方もいらっしゃるようですが、私は以前から行ってみたかった仙台城跡を見てきました。

城跡なので実際にお城の中に入れるわけではないのですが、伊達政宗公の騎馬像の実物を目の前にしたときは改めて「仙台に来たんだな〜!」と感じました。

f:id:marikokobayashi:20180604154920j:plain

仙台城跡まではバスで行くこともできましたが、国際センター駅から歩いていくと傾斜がある山を少し登ることになり、運動不足気味なエンジニアには適度な運動になるかもしれません!(女性や子供でも登れていたので、それほど苦しいレベルではないと思います)

全体を通して

今回も、RubyKaigiにはコミッターからRubyを最近書き始めた人まで、たくさんのRubyを使う人が参加していました。

そんな中で、自分がお話させていただいた中では「RubyKaigiに一度参加してみたけれど、自分の目指す方向性はRubyを極めたいわけではなくビジネスに使いたいだけだった」と改めて認識する方がいらっしゃったり、「参加してみてもっとRubyの内部実装を知りたくなった、C言語を勉強したくなった」という方がいらっしゃったり、参加した方の分だけそれぞれの感想があり、それぞれ素敵だなと思います。

セッションを聞くことも楽しいですが、このようにたくさんの方がそれぞれのRubyとの関わり方をしているのだということに思いを馳せられるのもRubyKaigiの良いところだなあと思いました。

f:id:marikokobayashi:20180605163304j:plain

セッション以外にも、休憩時間にもブースを見て回ったり、ペアプロの様子を見たり、サイン会があったり、朝食やお弁当を各スポンサーさんに出していただいたり、セッション終了後は1-3日目それぞれPartyに参加できたりと盛り沢山な内容で、Kaigiでもあり、お祭りでもあるような3日間でした。

個人的には、去年は知り合いが少なく少し寂しかったのですが、今年は会社のメンバーがたくさん参加したので、あまり寂しい思いをすることがなく過ごすことができました!

f:id:marikokobayashi:20180605163344j:plain f:id:marikokobayashi:20180605163835j:plain

また、同じコミュニティに顔を出し続けると少しずつ知り合いが増えてきて、いろいろな方とお話できるようになり、前回参加したときより少しではありますが自分がRubyistな方々と近づけたのかなと思いました。

来年は福岡、4月18〜20日の開催とのことです。来年も多くのメドピアメンバーが参加し、また多くのRubyistと交流できる会になれば良いなあと思っています。

*1:理解したとは言っていない

swift4.1へのバージョンアップとつまづいたこと

こんにちは。メドピアのエンジニアの保立です。
メドピアでは、医師専用コミュニティサイト「MedPeer」のiOSアプリを3月26日にリリースしました。
詳しくはこちら

メドピアのiOSアプリは、swift4.0で開発を始めましたが、先日swift4.1にバージョンアップしたので、今回のブログ記事では、swiftのアップグレードのためにやったことについて紹介します。

swift4.0から4.1の変更点

3月29日にswift4.1がリリースされました。変更内容とメドピアでの対応について記載します。

変更1) SE-0157 Support recursive constraints on associated types (関連する型の再帰的制約をサポート)

変更内容

swift4.0以前では、「プロトコル内で、プロトコル自身を再帰的に参照する」ことを制限していましたが、swift4.1以降では、その制限が解除されました。

// swift4.0以前では、コンパイルエラーとなる
protocol Sequence {
    // `SubSequence` が、再帰的に `Sequence` を参照している
    associatedtype SubSequence: Sequence
        where Iterator.Element == SubSequence.Iterator.Element, SubSequence.SubSequence == SubSequence

    func dropFirst(_ n: Int) -> Self.SubSequence { ... }
}

ちなみに、swift4.0以前では以下のように実装する必要がありました。わかりづらい。。。

protocol Sequence {
    associatedtype SubSequence

    func dropFirst(_ n: Int) -> Self.SubSequence { ... }
}

struct SequenceOfInts : Sequence {
    func dropFirst(_ n: Int) -> SimpleSubSequence<Int> { ... }
}

struct SimpleSubSequence<Element> : Sequence {
    typealias SubSequence = SimpleSubSequence<Element>
    typealias Iterator.Element = Element
}
対応

プロトコル内でassociatedtype を使って再帰的に参照している箇所を修正。

変更2) SE-0185 Synthesizing Equatable and Hashable conformance (EquatableとHashable適合性の合成)

swift4.1化のメインで、最もメリットを享受できる変更だと思います。

変更内容

Equatable / Hashable プロトコル内に実装する必要があった決まり文句を、暗黙的に実装してくれるようになりました。

struct Question: Equatable {
    let id: Int64
    let title: String

    // swift4.1以降では、 '==' の実装が不要になる。便利!
    public static func == (lhs: Question, rhs: Question) -> Bool {
        return lhs.id == rhs.id && lhs.title == rhs.title
    }
}

今までは、public static func == (lhs: Element, rhs: Element) -> Bool { ... } を明示する必要があったため、プロパティの追加、削除、または変更があった場合、この演算子を更新しなければならないことと、手作業で記述する必要があるため、漏れや誤植で間違ってしまう可能性がありました。
Hashable の場合、以下のようになります。

struct ForumTag: Hashable {
    let id: Int64
    let name: String

    // swift4.1以降では、 '==' も 'var hashValue: Int{} ` も実装が不要になる。超便利!!
    var hashValue: Int {
        return id.hashValue
    }

    static func == (lhs: ForumTag, rhs: ForumTag) -> Bool {
        return lhs.id == rhs.id
    }
}
対応

Equatable / Hashable プロトコルに実装された ==hashValue を使うように修正。

変更3) SE-0187 Introduce Sequence.compactMap(_:) (Sequence.compactMap(_:)の導入)

変更内容

Sequence.flatMap を廃止し、 同じ機能として、 compactMap(_:) を導入する。

対応

コンパイルエラーになったら対応。(MedPeerアプリでは Sequence.flatMap を使っている箇所はありませんでした)

変更4) SE-0188 Make Standard Library Index Types Hashable (標準ライブラリのインデックス型をHashableに)

変更内容

swift4から KeyPath という機能が実装されました。 KeyPath については Qiita が参考になると思います。

対応

MedPeerアプリでは、KeyPath使ってないので対応不要。

(おまけ) SE-0143 Conditional Conformance (条件付き適合)

この変更は、swift4.2以降で実装されます。

変更内容

URL: https://github.com/apple/swift-evolution/blob/master/proposals/0143-conditional-conformances.md

型引数が特定の要件を満たす場合にのみ、ジェネリック型が特定のプロトコルに準拠するという概念を表現できるようにします。

例えば、Array に対して、その要素がEquatableである場合に、Equatableプロトコルを実装することができることを表したい時、以下のように実装します。

extension Array: Equatable where Element: Equatable {
  static func ==(lhs: Array<Element>, rhs: Array<Element>) -> Bool { ... }
}

swift4.1以前で、上記のような実装をすると、「 'Array' 自体がEquatableでない」という理由で、コンパイルエラーになります。

error: extension of type 'Array' with constraints cannot have an inheritance clause
extension Array: Equatable where Element: Equatable { }
^                ~~~~~~~~~

これが解決されます。 Standard Library Adoption に記載されている通り、 OptionalArrayEncodable / Hashable 適合など、既存のライブラリにはすでに内部で使われています。

swift4.1に移行する時につまづいたこと

swift4.1への移行はほぼスムーズにできましたが、1点だけ修正が面倒なことがありました。Firebaseなどのサードパーティーツールで、以下のようなWarningが出ることです。

/path-to-app/vendor/FirebaseCoreDiagnostics.framework/FirebaseCoreDiagnostics: Failed to parse executable: Unknown header: 1918975009

メドピアでは、ライブラリ管理にCarthageを使っていたのですが、FirebaseなどはCocoaPodsにのみ対応していたので、FirebaseSDKを直接ダウンロードしていました。それにより、Firebaseのバージョン管理が適切にできていないのが原因でした。
結局、Carthageで管理できないライブラリは、CocoaPodsで管理することで解決しました。 この修正により、以下のライブラリをCocoaPodsで管理するように修正しています。

- Firebase/Core
- Fabric
- Crashlytics
- Repro
- R.swift

まとめ

今回、メドピアでは初めてswiftのバージョンアップを実施しましたが、今後もバージョンアップ等で得た知見をブログに執筆していきます。
iOSアプリはまだリリースしたばかりで、開発したいこと/すべきことが山ほどあるので、メドピアに少しでも興味を持っていただけたら遊びに来てほしいです。


是非読者になってください(ง `ω´)ง


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html

RubyKaigi2018 速報 !! ( 6/2 最終日)

他の「RubyKaigi2018 速報!!」まとめ ( 6/2 13:50 ) RubyKaigi2018 速報 (5/31 1日目)!! - メドピア開発者ブログ
RubyKaigi2018 速報 !! ( 6/1 - 1つ目 ) - メドピア開発者ブログ
RubyKaigi2018 速報 !! ( 6/1 - 2つ目) - メドピア開発者ブログ

Design pattern for embedding mruby into middleware

speakerdeck.com

Cで書かれたソフトウェアの拡張や設定をRubyで書くことが出来るのでは?と考え様々なミドルウェアについて、mrubyで拡張等を行ってきた。

なぜmruby?

  • rubyで書きたい、mrubyの限定された機能が都合が良かった。
  • 今までC言語で書かないといけないため難易度が高かったが、敷居が低く効率が良い。

どうやって組み込むか?

注意点

  • メモリ管理
  • パフォーマンス
  • 使いやすさ

Design Pattern

multi process, multi thread

  • ミドルウェアのスレッド単位でmrb_stateを組み込むことが大事
  • でもそんな単純じゃない、mrb_stateを作る処理が重いのでパフォーマンスに影響が出てしまう。。。
  • mrb_stateとbyte_codeの共有方式が重要

non-blocking

  • ミドルウェア側でnonblockな実装がされていてもmrubyがnonblockになっていないため、そこでパフォーマンスに影響が出てしまう。
  • できるだけmrubyの処理をnonblockで実装させる、blockする処理はミドルウェアのイベントループを優先して、その処理のコールバックによって再開するようにしてあげる。

Build your own tools

http://rubykaigi.org/2018/presentations/shugomaeda.html#jun02

スライド:

slide.rabbit-shocker.org

内容

自由なソフトウェアの話

  • FLOSS (Free Software and Open Source Software)
  • Web ApplicationにはFLOSSが多く含まれている
  • SaaSS (Service as Software Substitute) はあまり好きではない
  • ローカルでできるものはローカルでやりたい

自作したツールの数々

様々な自作アプリに関する紹介でした。最近キーボード自作する人多いですね!

ツールを自作するために必要なもの

  • モチベーション、時間、スキル
  • 自分に自信を持つ
  • 好きなテーマ(自分が使いたいもの)を見つける
  • 飽きる前に動く状態に持っていくために小さく作ろう
  • EmacsはEditorMACroS、つまり拡張可能なエディタ、そういう意味だとvimも同じ。仲良くしよう!

感想

テストは変更しやすくするためのものなので、書きすぎて逆に変更に弱くならないように書きすぎないというのはそのとおりだと思いました。

Deep into Ruby Code Coverage ( 6/2 13:50 )

  • テストが無いコードやデッドコードを検出するのにカバレッジは重要
  • DeepCoverageを使うと分岐のカバレッジをかなり細かくとれる(ruby本家のカバレッジよりも細かい!)
  • 実装は結構大変だった

感想

  • とりあえず試してみたい
  • そもそもruby2.5のカバレッジをまだ使っていなかった…

The Method JIT Compiler for Ruby 2.6 ( 6/2 13:00 )

http://rubykaigi.org/2018/presentations/k0kubun.html#jun02

スライド:

speakerdeck.com

現状のJIT状況

  • すごいバグってる! 
  • 並列にCIが走るとこけてしまう…

RubyのJITコンパイラ

バイトコード->Cのコード->コンパイル->バイトコード

ポータビリティ

Mingwで最適化をおこなったらなんかうごくようになった

パフォーマンス

  • ベンチマーク
  • 去年 : 5倍
  • 今(preview2): 5.7倍
  • 遅くなる場合がある。
    ファミコンは早くなるのにRailsだと遅くなるケースがある…

おそくなる仮設

  • longjump c言語のraiseみたいなもの call cache メソッドキャッシュが聞かない時(キャンセルされた時。再定義など。railsだとけっこうある)

  • JITコンパイルのオーバーヘッド thread間の転送(ロックなどのコスト) そんなに気にしなくて良さそう

  • JIT-ed
    全部nilを返すだけのmethodでなぜか遅くなる… 1メソッドあたり2Mバイト消費する(soファイル。なぜ2Mも) 1個のsoファイルにメソッドをいっぱいつっこめば速くなるがどうやるか

現状

Rails遅くなるから控えて。いろいろ改善しつつはある

navive code

  • GCCがもろもろめんどさを引き受けてくれる
  • われわれはインライン化に精を出せる

インライン化

  • Cコンパイラに最適化をしてもらうためにインライン化が必要
  • 条件
    メソッド定義
    呼び出しコードをJITコンパイラが更新できる
    cacheのinvalidation

インライン化のターゲット

  • Rubyから呼び出されたものはまだ簡単
  • Cから呼び出されたものは難しい
    やればできるけど保守性が下がる

もしRubyがcより早かったら?

  • プロトタイプはある 「C language is dead」

f:id:motsat:20180602133351j:plain

感想

JITによる高速化をめざしたもの。言語の中の人はバイトコードまでおっかけていろいろなアプローチで高速化を試みていてありがたいと思った。 また、Cよりも速くなるケースを作れるという話は単純にすごい。

Reirb: Reborn Irb ( 13:00〜)

概要

irbを作り直している話。

最近のirb

  • 開発が長く停止
  • 似たようなアプリが出てきた
  • Reish(URubyistのためのnixシェル)を開発している過程で、irbを作り直すためのアイデアを思いついた!

Reishの説明(機能)

  • job control
  • reidline
  • smart completion

↑Reirbは上の機能を使える。というかソースコードをかなり再利用してる。

Reirbの字句解析

Ripperを使っている。 irbではRipperを使っていないため、字句解析が不十分。

字句解析とは

irb(main):002:1> p 1
irb(main):003:1> end
1
=> 1

上記のようにstatementを解析し、宣言の終わり(式の終わり)に実行するようにしてくれるやつ?

Ripperとは

  • ruby の Parser

Job-Control

  • ^Z
  • fg, bg, jobs
irb(main):002:1>  p 1
irb(main):003:1>  sleep1
irb(main):003:1> end

上記の時にZで止める機能

reidline

  • readlineの複数行編集可能版
irb(main):002:1>  p 1
irb(main):003:1>  sleep1
irb(main):003:1> end

上記を1行ずつだけでなく、複数行に渡って同時編集できる

  • history
  • messages

Completion

現在実装中

  • bash/zsh風のcompletionを実現したい (edited)

Grow and Shrink - Dynamically Extending the Ruby VM Stack ( 6/2 10:50- )

http://rubykaigi.org/2018/presentations/sugiyama-k.html#jun02

  • 最近Goとか並列処理が得意な言語が増えてきたので、RubyVMのスタック拡張の改修を行っている

  • スタック拡張についてStretchCallStackChainringという2つの手法の紹介

  • 成果として、メモリ使用量の40%程度の削減に成功
  • 今後の課題としては、さらなる性能向上と幅広いテスト環境での検証をしていく

Ruby code from the stratosphere - SIAF, Sonic Pi, Petal ( 6/2 10:50- )

成層圏から来たRuby code http://rubykaigi.org/2018/presentations/kn1kn1.html

スライド:

github.com

  • Sonic Pi 大人から子供まで、Rubyで音楽を簡単につくれるソフトウエア
  • 内部でpetalという言語(Rubyライク)
  • Rubyコードでマリオの名曲を再現できている…
  • petal
    cps 2 # 1秒に2サイクル
d1 'bd*8' # bdという音を8回 * 1鳴らす
d1 'bd*64 [bd]' # 入れ子もできる
d1 'v' , n: 'irand 2 8' # 乱数もある
d1 'v', rate: 0.5 # 音の高さや逆回転などの制御
  • Euclidean rhythm
d1 'bd(5,8)' # 全体を8として、5を分散させる(太鼓っぽい!)
  • 音声ログから、その音声を再現することも可能
    それに音を追加したり

感想

Rubyコードの簡潔な部分が、曲作りや設定にすごく生かされていると感じた。 GUIな作曲ツールと、このツールで作った曲で作りたいものや出来上がるものがけっこう変わってきそう。

tech.medpeer.co.jp


(☝︎ ՞ਊ ՞)☝︎是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html

RubyKaigi2018 速報 !! ( 6/1 - 2つ目)

RubyKaigi2018の6/1 (2日目)の速報 (6/1 - 2つ目)です!

会場内で突然始まったペアプロの様子! f:id:motsat:20180601163227j:plain

他の「RubyKaigi2018 速報!!」まとめ
RubyKaigi2018 速報 (5/31 1日目)!! - メドピア開発者ブログ
RubyKaigi2018 速報 !! ( 6/1 - 1つ目 ) - メドピア開発者ブログ
RubyKaigi2018 速報 !! ( 6/2 最終日) - メドピア開発者ブログ

Journey of a complex gem upgrade

http://rubykaigi.org/2018/presentations/Edouard-chin.html#jun01

概要

  • Shopifyの人。Shopifyは1つの大きなRailsアプリケーションで、今までフルリライトはされたことがない
  • Gemのアップグレード時のベストプラクティスとはなにかを探っていく

やり方について

  • 通常のフローはCHANGELOGを見る、Bundle updateする、CI叩いて問題なかったらDeploy
  • だが、「Bleeding」をやめよう(Lessons learnt : Stop the bleeding first)

具体的には下記のような順で行っていく

  • 環境をデュアルブート可能にする(新バージョンGemを使う環境と旧バージョンGemでENV分け、Gemfile内でENV分岐でGemバージョン書き換えするなど)
  • Gemfileを分ける
  • アプリケーションを走らせて問題を修正する
  • CIを通す

問題の修正のやっていき方

  • バージョン切り分けで問題となるソースをコンポーネント化 (Rails.rootにcomponentsフォルダを作る)
  • どのコンポーネントがどこに依存しているかを切り分ける
  • ActiveSupport.Deprecation.warn('This code is deprecated')でDeprecationを出す

まとめ

  • bleedingをしないようにしよう
  • ツールを活用しましょう
  • Deprecationの修正に追従していきましょう
  • スライドは下記のツイッターアカウントにあとでアップしていただけるとのことでした! https://twitter.com/daroudedudek

Type Profiler: An analysis to guess type signatures

http://rubykaigi.org/2018/presentations/mametter.html#jun01

ruby 2.6の新機能

  • Endless range:(1..)
  • Beginless range(..1)も出るかも??

RDL: https://github.com/plum-umd/rdl

  • 実装が公開されている注目プロジェクト

使い方

  • rdlをrequire
  • 型修飾を書く
    • typecheck: :callとすると、メソッド呼び出された際に型チェックが実行される。他にも:nowを渡して、定義時にチェックすることも出来る。

特徴

  • 半静的な型チェックツール
  • メタプログラミングをサポートしている。
  • Implementationが、とても良い
  • 型情報をたくさん書かないといけない。。

他との比較

f:id:madogiwa0124:20180601172551j:plain

Ruby3の型システムの話

型システムのアイデアと課題

  • Static Analysis 1:定義されているメソッドから型を推定する。
    • 静的にチェック出来るけど、引数や返り値の解析は難しい。。。
  • Static Analysis 2:n + 3だとNumelic等、式に使われている値から型を推定する。
    • 静的にチェック出来るけど、StringArray<<等の解析が難しい。。。
  • Dynamic Analysis3:TracePointを使って動的に型を推定する。
    • 実装は分かりやすい、テスト実行が必要かつPerformanceに難あり。。。

Scaling Teams using Tests for Productivity and Education ( 6/1 13:50 )

Julian Nadeau @jules2689

チーム開発で起こる失敗と問題解決方法について

失敗

  • 失敗を避けるためにチーム内で「これをすべき、これをすべき」のマナーを作ってもにしてもすぐに古くなる、覚えられない、どうやって自動化しよう?

問題の解決方法

  • 問題を明文化するIdentify the problem
  • 修正が自動化できるかを判断するdetermine if a fix can be automated
  • テストを実施するimplement a test to detect and educate

問題の明文化

例:メンテナンスタスクでのファイル名 メンテナンスタスクで読み込むべきファイルの名前が間違っていたとしたら、ただ「間違っています」というエラーメッセージ出すのでは不親切 同ディレクトリ内を読み込んで、開発者にファイル名がどう間違っているかを知らせる

前のエラーメッセージが:「あなたの読み込んだファイルが間違っています」だったとしたら… 「test.rbというファイルを期待しましたが、test_test.rbファイルが読み込まれましたよ。test.rbというファイルを読み込んで下さい」と教える

間違っている可能性のあることすべて伝える(suggest any possible)

修正が自動化できるかを判断する

ユニットテストを書くこと

  • ユニットテストの実行時のメッセージをできるだけ書く
  • MiniTestだとしたら、asssert時のオプションmessageに上記のような細かいメッセージを書く
  • 問題が常に覚えておくべきことかどうかにかかわらず、検知と教育のためにテストを書こう

rubocopを導入すること

  • rubocopは便利、初心者でもとっつきやすい
  • shopifyでもruby-stydy-guideのリポジトリを作っている

just in time な教育をすること

  • JIT educationのJITとは、Just In Timeのことで、JITコンパイラのJITとコンテキストは近い
  • 開発者が問題を認識したのを検知して、 そのタイミングで問題をと知識を共有、修正に向けて動く

Other solution

bundlerのエラー時のメッセージがわかりにくかったので、実際に上記のように修正のGemを作った https://github.com/jules2689/extended_bundler-errors たとえばbundle installに失敗したときに 「could not create Makefile due to some reason」と言われることがあるが extended_bundler-errorsを使うと 「何が問題か?どのバージョンならOKか?何をすべきか?」 を提示してくれる

感想

  • 現場感のあるとてもいい発表でした。テストを書くことには動作の保証やヒューマンテストのコストを減らすだけではなく、教育の意味もあるのだなと思いました
  • 問題になりやすいテストに、なぜそれが落ちるのかをメッセージで表示していくのは取り入れてみたいです

Ruby Programming with Type Checking ( 6/1 14:40〜)

概要

発表者が作った型チェックライブラリ「Steep」(前回のRuby会議で発表した)は、実験的すぎたためプロダクションでは全く使用されなかった。 https://github.com/soutaro/steep

より実践的なものにするべくここ9ヶ月に渡る改良と、この型チェックライブラリがどれだけRubyプログラミングを助けるかを発表する。

Steepの機能

type annottaiion type definition local type inference structural subtyping

昨日v0.3.0リリース

いろんな機能追加。 typing polymorphic methodなど

Sorbetとの比較

写真撮ったのであとであげる

Demo

基本的な使い方の説明から、ここ9ヶ月で実装した機能などを紹介。

ここ9ヶ月で実装した機能

インスタンス変数をattr_reader, attr_accessorで扱う場合があるが、signature対応していなかったため対応 case分の型チェック 型チェックのstrict modeを追加 ...and so on

型づけのステップ

write signatures write ruby program with annotation rub type checker

型付けの困難

ライブラリの型がない 型付けの表現が難しい メタプロへの対処

感想

RubyKaigi全体を通して型付けのセッションが多い。みんなRubyに静的型付欲しいの? このセッションでは、1日目のセッションで紹介されていた「Sorbet」との比較もあり(Sorbetの開発者もこのセッションを聞いていた)、お互い意識しているのだろうか?

tech.medpeer.co.jp


(☝︎ ՞ਊ ՞)☝︎是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html

RubyKaigi2018 速報 !! ( 6/1 - 1つ目 )

RubyKaigi2018の6/1 (2日目)の速報 (6/1 - 1つ目)です!

他の「RubyKaigi2018 速報!!」まとめ
RubyKaigi2018 速報 (5/31 1日目)!! - メドピア開発者ブログ
RubyKaigi2018 速報 !! ( 6/1 - 2つ目) - メドピア開発者ブログ
RubyKaigi2018 速報 !! ( 6/2 最終日) - メドピア開発者ブログ

RubyKaigi2018に参加中のメドピア エンジニアが、セッションの内容や感想を更新していきます。

extend your own programming language ( 6/1 13:50〜)

http://rubykaigi.org/2018/presentations/m_seki.html#jun01

for DSL

  • rubyを使ってやるよりも機能が制限されている分安全

Extend MinRuby

  • 拡張して遊ぶ!(末尾最適、rinda eval、
  • requireで自分で作成したライブラリを読み込むことが出来る。
  • 自分で演算子を追加したりも出来る。クラスの演算子をまねっこすれば簡単に出来る。
    • でもrubyに存在しない演算子を追加するのはRipperに無いので難しい。。。

感想

MinRubyを使って僕の考えた最強のRubyを作るのは楽しそう、かつ勉強になりそうでした。

Guild Prototype

new concurrency model for ruby 3 ( 6/1 13:00〜) Koichi Sasada @ko1

Guild Prototype - RubyKaigi 2018

感想

  • guildがどんなものかイメージができてよかった
  • GILから逃れられる?方法ができるのは単純にありがたい

memo

  • 2016のときはまだアイディアだったguildの進捗をお伝えします
  • マルチスレッドプログラミングは人類には早すぎた
  • motivation
    • rubyではプログラミングを簡単にしたい
    • CPUコアを有効利用したい
  • Guildのコンセプト

    • mutableなものは共有しない(raceが起こらない)
    • threadをguildに置き換える
  • 共有不可オブジェクト

    • 1つのguildにのみ所属
    • mutable objects(String ,array)
    • ruby2とcompatible
  • 共有可能オブジェクト

    • immutable objects(symbol, numeric, true, false, nil, etc)
    • Class/Moduleオブジェクト
      • 共有できないとさすがに辛い
      • C::Const = [1, 2, 3]みたいなのは特別扱い
    • special mutable objects
      • Shared hash, array
      • Guild objects
      • 変更するには同期が必須
    • Isolated Proc
      • 外のmutableな変数にアクセスできない
  • 40 virtual CPUのマシンでデモ

    • fib(23)を100000回
      • 早くなるに決まっています
      • fib(10)くらいだとあまり変わらない
    • File.read(file).b.upcase.split(/\W/).uniq.size
      • Guildが増えると遅くなる
        • GC/object allocationでロックが発生する
  • GIL(Giant Interpreter Lock)がguildごとに適用される?

    • GGL(Giant Guild Lock)
  • Guild間通信

    • Actor model, send/receive semantics
      • Guild.parent << 'hoge'
      • hoge = Guild.receive
    • Guild間で共有不可オブジェクトを受け渡すときはコピーするか移動する
  • 実装

    • VM -> 1+ Guilds (NEW!) -> 1+ Threads -> 1+ Fibers
    • GCは全Guildを止める(Guildの中のスレッドも)
  • Guildという名前はmembershipを意識している

    • ただしまだコードネーム扱い

Improve Ruby coding style rules and Lint ( 6/1 13:00〜)

http://rubykaigi.org/2018/presentations/koic.html#jun01

スライド:

speakerdeck.com

LintとStyle違い

  • Style
    • いろいろなスタイルガイドがあるがrubocopのdefaultはRubyStyleGuide
    • それは正解不正解ではなく、文化の違いのためRubocopを変更するのはいいこと。
  • Lint
    • Lintはバグとなり得るもの。
    • ERB.newが2.6でキーワード引数で呼び出すように変更となったものを取り込んだりしている。
    • ruby -woptionでも検知出来るものもあるが、rubocopの方が検証範囲を細かく設定できるため、かゆいところに手が届く。

実際の業務からrubocopにつなげる

  • 実際に現場で発生したバッドケースをCopとして定義している。
  • それが他の人に役に立つ。

他のOSSからrubocopにつなげる

  • railsのrubocop.ymlは基本的にすべてfalseにして、有効にしたいものだけtrueとする運用としている。
  • railsのminitestのcopをrubocopのrails部署に置き換えることを行った。
    • railsの新機能のcopが集まってきたりするようになった!

It's Rubies All The Way Down ( 6/1 10:50〜)

http://rubykaigi.org/2018/presentations/wyhaines.html#jun01

  • アプリケーション(rails等)、環境構築(itamae等)、デプロイ(capistrano等)、ロガー(Fluentd等)等のRubyで実現されている様々な機能
  • Rubyで作られたKey/Value Storeがあるの知らなかった・・・! https://roma-kvs.org/

Faster Apps, No Memory Thrash: Get Your Memory Config Right ( 6/1 10:50〜)

github: noahgibbs Noah Gibbs @codefolio

Faster Apps, No Memory Thrash: Get Your Memory Config Right - RubyKaigi 2018

スライド:

docs.google.com

感想

  • 他でも良いという話は聞くのでjemallocは一度試してみたい

memo

ruby memory system

  • tiny objects
    • no memory allocations
  • small objects
    • 40-byte slot
    • 408 slots / page
  • big objects
    • slotに入らない

GC

  • generational
    • major GC
      • 全部チェック
      • GC.start
    • minor GC
      • 新しいのだけチェック
      • GC.start(full_mark: false)
  • mark/sweep

GC cycle: grow, collect, expand

問題: garbageが増えると遅くなる

省ける無駄は省く destrictive ops (gsub!, concat)はやさしい

質疑 Q) immutableなデータ使いたいんだけど? A) といっても、immutabilityを利用した最適化はrubyでは難しいから…

GC.stat useful parts - heap__available__slots, heap__live__slots, heal__free_slots - major__gc__count, minor__gc__count

環境変数 - RUBYGCHEAPINITSLOTS - RUBYGCHEAPFREESLOTSGOALRATIL - RUBYGCMALLOCLIMIT - RUBYGCOLDMALLOCLIMIT

適切な環境変数により、startup timeを改善できる

EnvMem: A Memory Tool

gem install env_mem

crubyはwarmupに時間がかかるが、EnvMemを使ってみたらwarmupの影響がなかった

Allocators

なんか難しいんだけど、簡単に早くなるものはないの? => YES!

mallocの代替 → jemalloc

linuxだったら特にjemalloc良い

./configure --with-jemalloc RUBY_CONFIGURE_OPTS="--with-jemalloc" rbenv install ...

10-12% speedup

まとめ what helps?

  • LESS WASTE
  • Good Env variable
  • jemalloc
  • latest ruby

その他

Slotは動かしづらい → fragmentation

GC::Profiler

GC::Profiler.enable

...

puts GC::Profiler.report

(☝︎ ՞ਊ ՞)☝︎是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html