patorashのブログ

方向性はまだない

第23回中国地方DB勉強会に参加してきた

昨日はこれに参加してきました。

dbstudychugoku.github.io

LT枠で申し込んでいたのでLTもしましたよ!

今回は、AWSの藤原さん(@twingo_b)のAmazon Auroraの話と、オミカレの曽根さん(@soudai1025)のデータベースモニタリングの話で、どちらも内容が濃くて面白かったです。

togetterまとめ

まとめがありますので、当日の様子はこちらでご確認ください。

togetter.com

Amazon Aurora ベストプラクティスと最新アップデート

私自身はAWSほとんど使ったことがないのですが、RDSとAuroraは何が違うのかイマイチわかっていなかったので楽しみにしていました。

Auroraすごい

Auroraはハイエンドデータベースのような性能と可用性で、オープンソースデータベースのMySQLPostgreSQLのどちらかを選択できる。PostgreSQLの場合は、9.6互換。データベースを通常のRDSからAuroraに変更するだけで普通に動く。プログラム側の変更なしで高速化できる!5倍速いとかネット上で見かけた。

お試し用にt2のインスタンスが選択できる。ただし、MySQLのみ。PostgreSQLはまだ選べない。残念…。

速くて障害に強い理由は、DB専用に作られたストレージと、3つのアベラビリティゾーンに分散化されたストレージノードにストライピングされていること、各AZにコピーが作られていること、マスターとレプリカが同じストレージ上にあること、など。

AWSのサービスなので他のAWSとのサービスとの連携が強み。例えば…

  • プロシージャからLambdaの呼び出し
  • S3にバックアップ
  • IAMでアクセス制御
  • CloudWatchで監査ログやシステムメトリクスをとる

などなど。

一番嬉しいのは、ユーザーはアプリケーション最適化だけ考えればいいという点だと思う。DBのチューニングは、インスタンスサイズによって最初から適切な設定が行われているとか、最高。

RDSはスケールアップでもそんなに性能が上がらないことがあるけれど、Auroraにするとかなり上がる。そして移行は容易。Auroraにしない理由が見つからない…(お金以外で)。とはいえ、Auroraにすることでコスト削減っていう話もあった。管理コストも下がるし、商用DBからの移行で下がるという話だったかなと思う。商用DBからの移行用のサービス・ツールもある。データ移行サービス(DMS)と、スキーマコンバージョンツール(SCT)。

あとは新機能についての話題があったのだけれど、Aurora Serverlessが今後出るとか…。オンデマンドで起動するDBで、瞬発的にアクセスが多くなるようなものに使えるという話だった。ブログとか、BIツールとかによさそうとTLでは上がってたかなと思う。

Amazon Lockerすごい

話の導入前に、藤原さんがAmazonの本社に行ってたときの話があったのだけれど、Amazon Lockerという、買ったものがコインロッカーに届くというサービスが向こうでは始まってるらしい。日本でいうコンビニ配達みたいなものなのだけれど、人の手を介さずに荷物を受け取れるのですごくよさそう。流通も最適化されるだろうし、日本にも早く導入してほしい〜!

気づき

Auroraに変えるだけでパフォーマンスが向上する、というのは正直すごい。なんだかんだで多少は面倒臭いんだろうなとか思っていたけれど、移行が容易というのがよかった。PostgreSQLでもt2インスタンス使えるようになってほしい…。

今から始めるデータベース監視 ~あなたのデータベースは大丈夫?~

DBのモニタリング、スロークエリくらいは時々見ているけれど、あんまりちゃんとできていないしこれから趣味アプリでMackerelとか使っていくかーと考えていたので、ちょうどよかったです。

なぜモニタリングするのか?

モニタリングする理由は、

  • 素早く障害に気づくため
  • 原因究明のため
  • 未然に障害を防ぐため

そもそも障害が起きてしまってはいけないので、サービスが落ちる前に気づけるようにしないといけない。

監視の勘所

監視は、サービスが正しく動いているかをモニタリングするわけだけれど、定量データを取り続けることで、意図しない挙動に気づくことができる。

アクセス監視でいえば、

  • 特定のページにアクセスがない
  • 特定の時間帯でアクセスがない

サーバ監視でいえば、

  • batchのジョブ数の変化
  • キューの数の変化
  • DNS, NTPは問題ないか

サービス自体の監視でいえば、

  • ユーザーのプレイ状況(アクティブユーザー数など)
  • 検索ワード
  • 申し込みボタンのクリック数

これらで、サービスの課題に気づきやすくなる。

DBのモニタリング

DBのモニタリングにはMackerelってやつがいいよという話と、DBの得意・不得意を知っておくことの重要性と、おすすめの本の紹介など。あとは内部構造がわかれば何を監視すればいいかが見えてくるという話もあった。

DBの得意・不得意

MySQLはキー1発でデータを取ってくるのは得意だけれど、JOINとか、複数INDEXを扱うのが苦手なので、それを知っていれば、一部をNoSQLに逃すとかの対策が打てる。DBだけでなんとかしようとしてもどうにもならないことがあるのを知っておくことが大事。

おすすめの本

この本は知らなかったので、読んでみたい。というか、社内で読書会するかな〜。募ってみるか。

実践ハイパフォーマンスMySQL 第3版

実践ハイパフォーマンスMySQL 第3版

エキスパートのためのMySQL[運用+管理]トラブルシューティングガイド

エキスパートのためのMySQL[運用+管理]トラブルシューティングガイド

MySQLについてはこれらの本がおすすめらしい。もうMySQLは数年は触っていないな…。ちなみに家には結構MySQLの本ある(妻の持ち物とか)。

内部構造から学ぶPostgreSQL 設計・運用計画の鉄則 Software Design plus

内部構造から学ぶPostgreSQL 設計・運用計画の鉄則 Software Design plus

PostgreSQLに関してはこの本は本当にいい。この本はバージョン9.3の頃のやつなのだけれど、そろそろ新しいのが出るらしいという噂を聞いたので個人ではまだ持ってないけれど新しいのが出たら買う!

Mackerelプラグインのコードリーディングがいい

Mackerelプラグインのコードを読むと、どういうSQLでどういう値を取得しているかがわかって面白いらしい。オープンソースだからこそって話でよかった。

LT

LTは3枠あったのだけれど、キャンセルや交通機関の影響?で、私だけだった。

OSS-DB Silverという資格を取ったのだけれど、体系的にデータベースの基本知識を学べるのでおすすめですというLTをした。Goldいつ取るの?と煽られたので、頑張ろうと思う。

お悩み相談タイム

JPUGの中国支部長のお悩み相談タイム、のはずが、本人が用事で急遽参加できなくなったため、相談内容だけ残してみんなで喧々諤々の話し合いが行われた。割と、身もフタもない話が展開されつつも、面白い話題になってよかったと思います。雑にまとめたツイートを自分がしていたので、とりあえず貼っておくけれど、togetterまとめを見た方がいいでしょう。

自分なりの気づき

「SELECT INSERT or UPDATEはINSERT(UPDATE)中にSELECTの結果が変わってしまってはいけないからSELECTしている行もロックされる」という話題があって、なるほど〜!!と思いました。大量のデータで気軽にやったらシステム止まっちゃうよということだったので、忘れないようにしたい。

あとネットでは業務内容に絡むので話題にしづらいけれど、DB勉強会ではある程度踏み込んだ相談ができるので、そのあたり実際にDBに詳しい人たちに会って聞けるってのはほんま恵まれた機会だなと思います。

今回も充実した内容で楽しかったです!また参加したいと思います。

CircleCIのworkflowを使ってコードのチェックアウト、ライブラリのインストールを共通化した

2019-01-25 追記:CircleCI 2.1を使うとデフォルトで共通化ができるようになっています。 patorash.hatenablog.com

以下より、投稿時の原文です。

私が担当している製品では、RailsのテストをCircleCIで4並列で動かしているのだけれど、これがいちいち各コンテナ毎にコードのチェックアウト、ライブラリのインストールをしているのは無駄じゃないかなー?と常々思っていました。これが、workflowを使ったら解決できそうだとわかったので、やってみることにしました。

目標を定義する

作業前は、以下のようになっていました。

  1. コードのチェックアウト・・・各コンテナ毎に実行
  2. bundle install・・・各コンテナ毎に実行
  3. yarn install・・・各コンテナ毎に実行
  4. rspec・・・各コンテナ毎に実行

作業後は、このようにしたい。

  1. コードのチェックアウト・・・1コンテナで実行
  2. ライブラリのインストール・・・並列実行
    1. bundle install・・・1コンテナで実行
    2. yarn install・・・1コンテナで実行
  3. rspec・・・各コンテナで実行

これならば、ライブラリのインストールを並列で行えるので、速度改善が見込めるんじゃないか?と思いました。

config.ymlを修正する

まずは、定義も何もないところから、workflowだけを書いていきます。

workflows:
  version: 2
  build:
    jobs:
      - checkout_code
      - bundle_dependencies:
          requires:
            - checkout_code
      - yarn_dependencies:
          requires:
            - checkout_code
      - test:
          requires:
            - bundle_dependencies
            - yarn_dependencies

まぁ、こんな感じでしょう。requiresを定義して、ライブラリのインストールが両方終わってからテストが実行されるようにしています。

その後は、以下のconfig.ymlを参考にして作ってみました。

github.com

順に書いてみます。

コードのチェックアウト

コードをチェックアウトして、キャッシュに保存しておきます。

jobs:
  checkout_code:
    docker:
      - image: circleci/ruby:2.5.1-node-browsers
    working_directory: ~/project
    steps:
      - checkout
      - save_cache:
          key: v1-repo-{{ .Environment.CIRCLE_SHA1 }}
          paths:
            - ~/project
  # 省略

ライブラリのインストール

bundle_dependencies

先ほどキャッシュしたコードと、過去のgemのキャッシュを戻した後、bundle installを実行し、またキャッシュに保存しておきます。

jobs:
  # 省略
  bundle_dependencies:
    docker:
      - image: circleci/ruby:2.5.1-node-browsers
    working_directory: ~/project
    steps:
      - restore_cache:
          keys:
            - v1-repo-{{ .Environment.CIRCLE_SHA1 }}
      - restore_cache:
          keys:
            - v1-bundle-{{ checksum "Gemfile.lock" }}
            - v1-bundle
      - run: bundle check --path=vendor/bundle || bundle install --path vendor/bundle --jobs 4 --retry 3
      - save_cache:
          key: v1-bundle-{{ checksum "Gemfile.lock" }}
          paths:
            - ./vendor/bundle
  # 省略
yarn_dependencies

こちらもbundle_dependenciesと同様に。yarnを行ってJSライブラリを入れて、またキャッシュに保存しています。

jobs:
  # 省略
  yarn_dependencies:
    docker:
      - image: circleci/ruby:2.5.1-node-browsers
    working_directory: ~/project
    steps:
      - restore_cache:
          keys:
            - v1-repo-{{ .Environment.CIRCLE_SHA1 }}
      - restore_cache:
          keys:
            - v1-yarn-{{ checksum "yarn.lock" }}
            - v1-yarn
      - run:
          name: Install dependencies
          command: yarn
      - save_cache:
          key: v1-yarn-{{ checksum "yarn.lock" }}
          paths:
            - ./node_modules
  # 省略

test

テストは、並列実行したいので、parallelismを指定しています。また、テストにDBが必要なので、DBのdocker imageの指定も行っています。

やっていることは以下の通り。

  1. 各コンテナで、ここまでのworkflowで行ったキャッシュを取得する
  2. DBのセットアップ
  3. RSpecの実行
  4. 結果の収集
jobs:
  # 省略
  test:
    docker:
      - image: circleci/ruby:2.5.1-node-browsers
         environment:
           RAILS_ENV: test
           TZ: "/usr/share/zoneinfo/Asia/Tokyo"
      - image: circleci/postgres:9.6-alpine-postgis
    working_directory: ~/project
    parallelism: 4
    steps:
      - restore_cache:
          keys:
            - v1-repo-{{ .Environment.CIRCLE_SHA1 }}
      - restore_cache:
          keys:
            - v1-bundle-{{ checksum "Gemfile.lock" }}
      - restore_cache:
          keys:
            - v1-yarn-{{ checksum "yarn.lock" }}
            - v1-yarn
      - run: bundle --path vendor/bundle

      # Database setup
      - run: bundle exec rake db:create
      - run: bundle exec rake db:schema:load

      # run tests!
      - run:
          name: run tests
          command: |
            mkdir /tmp/test-results
            TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)"

            bundle exec rspec --format progress \
                            --format RspecJunitFormatter \
                            --out /tmp/test-results/rspec.xml \
                            --format progress \
                            $TEST_FILES

      # Save artifacts
      - store_test_results:
          path: /tmp/test-results

      - store_artifacts:
          path: /tmp/test-results
          destination: test-results
  # 省略

これでいいはずですが、./circleci/config.ymlの構造が間違ってないか、確認しておいた方がいいでしょう。

circleci config validate -c .circleci/config.yml

コマンドがない場合は、CircleCI local cliを入れておくこと。

circleci.com

CircleCIを実行する

workflowが正しく動いているか確認してみます。

f:id:patorash:20180624114543p:plain

おおー、動きました!!見た目は理想通りです。

config.ymlのリファクタリング

このままだと、重複コードがたくさんあるので、リファクタリングを行います。 こちらのブログを参考にさせてもらいました。

medium.com

共通部分を以下のように括り出しました。

references:
  defaults: &defaults
    working_directory: ~/project

  ruby_docker_image: &ruby_docker_image
    image: circleci/ruby:2.5.1-node-browsers
    environment:
      RAILS_ENV: test
      TZ: "/usr/share/zoneinfo/Asia/Tokyo"

  restore_code_cache: &restore_code_cache
    restore_cache:
      keys:
        - v1-repo-{{ .Environment.CIRCLE_SHA1 }}

  restore_gem_cache: &restore_gem_cache
    restore_cache:
      keys:
        - v1-bundle-{{ checksum "Gemfile.lock" }}
        - v1-bundle

  restore_node_module_cache: &restore_node_module_cache
    restore_cache:
      keys:
        - v1-yarn-{{ checksum "yarn.lock" }}
        - v1-yarn

これを適用していきます。

適用した完成品がこちら。

version: 2

references:
  defaults: &defaults
    working_directory: ~/project

  ruby_docker_image: &ruby_docker_image
    image: circleci/ruby:2.5.1-node-browsers
    environment:
      RAILS_ENV: test
      TZ: "/usr/share/zoneinfo/Asia/Tokyo"

  restore_code_cache: &restore_code_cache
    restore_cache:
      keys:
        - v1-repo-{{ .Environment.CIRCLE_SHA1 }}

  restore_gem_cache: &restore_gem_cache
    restore_cache:
      keys:
        - v1-bundle-{{ checksum "Gemfile.lock" }}
        - v1-bundle

  restore_node_module_cache: &restore_node_module_cache
    restore_cache:
      keys:
        - v1-yarn-{{ checksum "yarn.lock" }}
        - v1-yarn

jobs:
  checkout_code:
    <<: *defaults
    docker:
      - *ruby_docker_image
    steps:
      - checkout
      - save_cache:
          key: v1-repo-{{ .Environment.CIRCLE_SHA1 }}
          paths:
            - ~/project

  bundle_dependencies:
    <<: *defaults
    docker:
      - *ruby_docker_image
    steps:
      - *restore_code_cache
      - *restore_gem_cache
      - run: bundle check --path=vendor/bundle || bundle install --path vendor/bundle --jobs 4 --retry 3
      - save_cache:
          key: v1-bundle-{{ checksum "Gemfile.lock" }}
          paths:
            - ./vendor/bundle

  yarn_dependencies:
    <<: *defaults
    docker:
      - *ruby_docker_image
    parallelism: 1
    steps:
      - *restore_code_cache
      - *restore_node_module_cache
      - run:
          name: Install dependencies
          command: yarn
      - save_cache:
          key: v1-yarn-{{ checksum "yarn.lock" }}
          paths:
            - ./node_modules

  test:
    <<: *defaults
    docker:
      - *ruby_docker_image
      - image: circleci/postgres:9.6-alpine-postgis
    parallelism: 4
    steps:
      - *restore_code_cache
      - *restore_gem_cache
      - *restore_node_module_cache
      - run: bundle --path vendor/bundle

      # Database setup
      - run: bundle exec rake db:create
      - run: bundle exec rake db:schema:load

      # run tests!
      - run:
          name: run tests
          command: |
            mkdir /tmp/test-results
            TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)"

            bundle exec rspec --format progress \
                            --format RspecJunitFormatter \
                            --out /tmp/test-results/rspec.xml \
                            --format progress \
                            $TEST_FILES

      # Save artifacts
      - store_test_results:
          path: /tmp/test-results

      - store_artifacts:
          path: /tmp/test-results
          destination: test-results

workflows:
  version: 2
  build:
    jobs:
      - checkout_code
      - bundle_dependencies:
          requires:
            - checkout_code
      - yarn_dependencies:
          requires:
            - checkout_code
      - test:
          requires:
            - bundle_dependencies
            - yarn_dependencies

だいぶすっきりとしたコードになりました。

テストは速くなったのか?

重複は排除できたのですが、肝心のテストの速度は速くなるどころか、少々遅くなっています…😢

原因は明白で、jobを細かく分けたため、docker imageのロードに時間がかかっているためです(各ワークフロー毎にだいたい30秒)。元々30秒かかっていたところが、3段階に分かれたので、30*3=90秒かかるようになりました。しかしこれも運次第で、テストが実行されたホストにdocker imageのキャッシュがあれば1〜2秒で終わるのですが、ないと30秒かかるという状態です。完全な条件下では、ライブラリのインストールが並列になるので、多少は速くなるのでは?🤔と思います。

とはいえ、好みとしてはこちらのコードなので、これでしばらく運用してみようかなと考えています。

社内勉強会でMacの便利ツールを紹介したのでここにも載せておく

弊社は昨年末からエンジニアの退職が相次いでいるのですが、そういう中でも5月に新たな仲間を迎えることができまして、若手エンジニアの育成やっていくぞ!という気持ちでおります。また、開発部内も製品毎にグループが分かれている関係もあり若干縦割りなので、そういう垣根を取り払って気軽に情報共有していこう!ということで、今回から開発部内で広く勉強会を周知していました。前回までは、いわゆるいつものメンバーでの開催だったのですが、今回からは他のチームからも参加者がきてくれたので、引き続き周知していきたいと思います。

常日頃から思っているのですが、キーワードを知らないと調べることすらできないので、とりあえずキーワードだけでも覚えてもらって、もし興味が出たら調べてみてね、という感じで社内勉強会にて開発時に便利なツールを紹介しました。

無料のツール

fish

fishはshellの1つで、bashよりも使いやすいと感じているので、私はfishを使っています。Macの構成管理(Ansible)でも、fishを使うようにも設定しています。

fishshell.com

便利な点は、強力なサジェスト機能だと思います。あとは、プラグインが充実している点。fishermanというプラグイン管理ツールがあり、便利です。

patorash.hatenablog.com

不便な点は、だいたいのツールがbashzshを前提としてあることが多いので、ツールをインストールしてもそのままでは動かないことがある、とかですかね。そういうのも、プラグインで対応してあるものがあったりはしますが。最近はfishにも最初から対応してあるものが増えてきています。ありがたい。

ドキュメントですが、るびきちさんのfishシェル普及計画のページがありますので、英語が苦手な人でも導入しやすいかと思います。

fish.rubikitch.com

ghq

ghqコマンドラインのgitリポジトリの管理ツールです。gitリポジトリのcloneや、すでにclone済みのgitリポジトリのリストを表示することができます。

github.com

~/.gitconfigに設定を追加しておけば、cloneするときのディレクトリが指定できるので、コードが複数のディレクトリに散らばらないので、とてもよいです。ghqはpecoと組み合わせることでとても便利なツールになります。

peco

pecoは、標準出力から受け取った結果をフィルタリングしたり、選択可能にして次のパイプに渡すツールです。なんかうまくいえる言い回しがわかりませんが。pecoはそれ自体ではほぼ何もできないのですが、他のコマンドと組み合わせることで様々なことができます。

github.com

私がよく使うのは、

  1. ghq list -pの結果をpecoで処理してリポジトリディレクトリに移動
  2. git branchの結果をpecoで処理してチェックアウト
  3. git branchの結果をpecoで処理してブランチの削除
  4. historyの結果をpecoで処理して再び実行
  5. ~/.ssh/configからHostを抽出したものをpecoで処理してSSH接続
  6. psの結果をpecoで処理してPIDを取得してkill

などです。これらはfishの関数化してあり、すぐに呼び出すことができるようにしてます。例えば、1のやつとかはselect_repositoryという関数にしてあります。

gistで、私が作っているfishの関数があるので、紹介しておきます。

https://gist.github.com/patorash/1de94fcb7efeb847501d2fb7900c2deb

pecoは組み合わせ次第でいろんな使い方ができるので、qiitaとかで情報を追ってみると面白いでしょう。

有料のツール

Dash

Dashは、プログラミング言語や開発ツールのリファレンス・マニュアルをローカルにダウンロードして検索できるツールです。使い方がわからないメソッドの情報を、ググらないでもすぐに調べることができます。

kapeli.com

これは実は5〜6年前の社内勉強会のときに、当時パートナーエンジニアとして参加してもらっていた@aguuuさんに教えてもらいました。

Dashのメリットは、なんといってもローカルに保存してあるから検索が高速な点と、ローカルに保存してあるので、ネットが繋がらないところであってもリファレンスを参照できる点です。また、各種IDEやエディタにプラグインがあるので、それを入れておくと、IDEから直でDashを参照できます。例えば、RubyMineでcreate_tableという文字列を選択して、command + shift + Dを押すと、create_tableメソッドの使い方を表示してくれます。

値段は$29.9と、まぁまぁするのですが、メソッドの使い方などを検索する時間を買ったと思えば安いものかなと思います。試してから買えるので、まずは試してみるといいかなと思います。

Alfred

Alfredは、spotlightのさらにすごいやつ、みたいな感じです。キーボードだけでいろんな操作が可能になります。option + spaceで呼び出して、命令を入力したり、検索したりなど。計算もできるので電卓を起動したりする手間もありません(まぁそれはspotlightでもできますけど)。

www.alfredapp.com

これもまた、Okayama.rbで交流のある@logictktさんに教えてもらったツールです。

簡単なところだと、sleepと打つとスリープしてくれたり、restartと打つと再起動、shutdownと打つとシステム終了など…。サジェストも効くので、slと打つだけでスリープできます。

Alfredはアプリ内課金があり、パワーパックを購入するとさらに様々なことができるようになります。ワークフローといって、Alfredの入力を他のアプリに渡すことができたりします。GUIのpecoみたいな存在ですね。

さきほど紹介したDashのワークフローもあって、Alfredを呼び出してdash create_tableとか打つと、Dashで検索してくれます。わざわざDashのウィンドウを探さなくてもいいので、これもまた便利です。pecoと同様、様々なワークフローがネット上に紹介されているので、調べてみるといいでしょう(実は私もDashくらいしか使ってないですが…)

まとめ

以上、社内勉強会で紹介したツールでした。まぁ実際の発表では、めちゃくちゃザックリとした紹介の仕方だったんですが、その後にチャットワークでさらに情報交換が行われたりしたので、紹介してよかったなと思います。

他にも便利なツールがありましたら教えてください!

CircleCIのworkflowを使ってbundle updateのPRを自動化する

以前にHerokuとCircleCIを使ってbundle updateのPRの生成を自動化していました。

qiita.com

しかし、最近はCircleCIのworkflowを使えばHerokuを使わずともCircleCIのみで完結しそう!ということだったので調査してみました。

方法が公開されていた

circleci-bundle-update-prのREADMEで、CircleCIのワークフローを使ってPRを生成する方法が公開されていました。ありがたい!

github.com

.circleci/config.ymlに設定してみる

設定をほぼ丸々コピペしてみました。

  • Rubyのバージョンの辺りを最新の2.5.1に変更
  • restore_cacheのkeysを既存プロジェクトの設定に変更

とりあえず動くかどうか試すためにworkflowsのbuildブロックにbundle updateするjobを設定してpushしてみたところ、エラーで落ちました。

nokogiriが入らない

nokogiriは、C拡張のgemなので、素のruby: 2.5.1-alpineのdocker imageだとコンパイルできずにエラーになりました。とりあえずCircleCIにsshで接続して、必要になりそうなものをインストールしていきます。

参考にしたのは、以下のページ。

dora.bk.tsukuba.ac.jp

alpine-sdkを入れればよさそう。

apk add --update --no-cache alpine-sdk

その後、bundle installしたらnokogiriが無事にインストールされました!しかしまだbundle installの途中でコケる…。

pgが入らない

pgもC拡張のgemなのですが、postgresql-clientがないとコンパイルできないようでした。こちらも入れていきます。

apk add --update --no-cache postgresql-dev

この後、bundle installしたらpgも無事にインストールされ、bundle installが完走しました。

修正後の設定

以下が、修正後の設定です。nokogiriやpostgresqlを使ってない場合は素のままでいいと思いますが、参考程度に。 deployブロックのusername, emailは自身のgithubアカウントに置き換えてください。

また、CircleCIのプロジェクトに環境変数 GITHUB_ACCESS_TOKEN を設定しなければなりません。 GitHubPersonal access tokensより、トークンを発行して、それをCircleCIのプロジェクトのBUILD SETTINGS » Environment Variablesから設定してください。

version: 2
jobs:
  build:
    # snip
  continuous_bundle_update:
    docker:
      - image: ruby:2.5.1-alpine
        environment:
          TZ: "/usr/share/zoneinfo/Asia/Tokyo"

    working_directory: /work
    steps:
      - run:
          name: Install System Dependencies
          command: |
            # See also https://circleci.com/docs/2.0/custom-images/#adding-required-and-custom-tools-or-files
            apk add --update --no-cache git openssh-client tar gzip ca-certificates tzdata alpine-sdk postgresql-dev
            gem install -N bundler
      - checkout
      - restore_cache:
          name: Restore bundler cache
          keys:
            - gems-{{ .Environment.COMMON_CACHE_KEY }}-{{ checksum "Gemfile.lock" }}
            - gems-{{ .Environment.COMMON_CACHE_KEY }}-
      - run:
          name: Setup requirements for continuous bundle update
          command: gem install -N circleci-bundle-update-pr
      - deploy:
          name: Continuous bundle update
          command: circleci-bundle-update-pr <username> <email>

workflows:
  version: 2
  build:
    jobs:
      - build:
          # snip
  nightly:
    triggers:
      - schedule:
          cron: "00 10 * * 5"
          filters:
            branches:
              only: master
    jobs:
      - continuous_bundle_update

まとめ

CircleCIだけで完結するようになったのはありがたいです。また、cronのようにスケジュール設定できるとのいうのも、色々使えそうな気がします。 また、workflow自体も、テストが通ったら自動デプロイができたり、テストが通ったら通知を送って、手動で承認をするとデプロイが動くなど、細かい運用フローにも対応できそうで、よさそうです。とりあえず個人プロジェクトではテストが通ったら自動デプロイするところまでやってみようと思います。

RubyKaigi2018に行ってきたので社内報告会を行った

f:id:patorash:20180602121807j:plain

今年は久々にRubyKaigiに参加しました。場所は仙台。参加費は会社から出してもらえるのでありがたい〜。とても刺激を受けた3日間でした。

戻ってきて早速、社内報告会を行いました。ただし、英語力が乏しいのと、Ruby自体の実装とかCの話とかは本当によくわからないので、めちゃくちゃざっくりとした報告に…。 とりあえず、自分が面白かったと思った話題について。

ちなみに、今回の聞く方針としては、

  1. 機械学習系のやつを聞いて、詳しくないなりに、社内にフィードバックする
  2. Rubocopの話とか押さえたい
  3. 自分たちの手で高速化できそうな話を聞く

という感じで考えてました。

TruffleRubyすごい

3日目のキーノートで話されたTruffleRubyに驚きました。TruffleRubyは、Oracle Labsが開発しているGraalVM上で動くRubyです。

github.com

GraalVMは多言語に対応した仮想マシンです。

www.infoq.com

上記のページに、

とあるので、そこからTruffleRubyという名前がついてるのでしょう。ちなみに、「トリュフ」だそうです。

7倍速い!

Rubyは現在、3x3 Rubyを目標として、Ruby 3はRuby 2の頃よりも3倍速くなることを目指しています。キーノートでは、Rubyベンチマークで使われるOptCarrot(Rubyで作られたファミコンエミュレータ)を使って、パフォーマンスの計測をしていました。

  • Ruby 2.0では、28fps
  • Ruby trunkでは、55fps
  • TruffleRubyでは、開始数秒は1〜10fps程度だけれど、突然180〜200fpsに!

3倍速いどころではなく、7倍速いのにびっくりです。JITコンパイラがめちゃめちゃ効いてるからということでした。そのため、最初は遅いと。どういうふうに最適化が行われているかという話がありましたが、だんだん話についていけなくなり…。わかったのは、まだ実用的ではなさそうなことでした。ただ、もう少ししたらrbenvとかで試せるようにする、ということでした。Webアプリケーションでの恩恵がどの程度になるのかはわかりませんが、期待大だなと思いました!

DeepLearning

深層学習はRubyは弱いというイメージがありましたが、だいぶRubyだけで完結できる世界が近づいてるという印象を受けました。全くの門外漢なので、ChainerとTensorflowという名前だけは聞いたことがある…という程度なのですが、MXNetというApacheプロジェクトのものがあるのを知ることができました。

話は基本的にはmxnet.rbと、Red Chainer(ChainerをRubyでポーティングしたもの)でした。

MXNet

MXNetは多言語サポートをしていて将来的にはRubyもサポートされること、多くの企業・大学がサポートしていること(Amazon、MS、MIT、カーネギーメロン大学など)、利点が多いことなどをあげられていました。Apacheプロジェクトということで、他のデータサイエンス系のApacheプロジェクトのメジャーツールとの連携が期待できることや、ONNXをサポートしていることなどが挙げられていました。ONNXは、ツール間で学習内容の交換ができる形式のようでした。

現在、mxnet.rbを開発中で、開発者を募集中とのことでした。

Red Chainer

Red ChainerはPython製のChainerをポーティングしたものだそうです。開発者を募集していて、gitterで意見交換をしたり、speeeさんのところで毎月イベントをされてるそうです。

Rubyらしく書けるのが特徴ということでしたので、自分がもし深層学習やってみる場合はこの辺りから手をつけてみようかなと思いました。

Cumo

Cumoは、深層学習用のミドルウェアのようでした。CumoのCの字は、CUDAからきています。Ruby/Numoとの互換性を高く置いていて、NumoをCumoと1文字変えるだけで動くことを目指しているとか。

Cumoが必要になった背景は、GPUで演算するRubyのやつがなかったので、作り始めたとか。なぜGPUが使いたいかというところの話で、やはり圧倒的にパフォーマンスが違うようでした。CPUで30日かかる処理が、GPUだと4日程度。その理由はGPUの圧倒的なコア数。

Red ChainerはまだGPUが使えないのですが、Cumoを使うようにすると、パフォーマンスが75倍速くなったとか…。圧倒的すぎる…。

まだまだ課題はあるとのことでしたが、Ruby機械学習・深層学習が身近になりそうな話題でした。

メモリ・GC

Rubyはメモリ消費が多いので、省メモリの話とかも多かったです。アーロンの話では、Ruby2.5から2.6に変えると、Railsの起動時のメモリ消費は4%改善するとか。Rubyをバージョンアップするだけでいいので、これは期待大です。

先に、話に出ていたgemとかを並べときます。

  • allocation_tracer
  • env_mem

あと、Linux環境においてですが、jemallocを使うようにRubyコンパイルするときのconfigureオプションで設定すると、10〜12%くらい速くなったとか。

スピードアップまとめ

その他

あとはDDD用のWebフレームワークのhanamiの話を聞きに行ったりもしたのですが、正直あんまりよくはわかりませんでした…。英語力のなさと、hanamiの事前情報のインプットくらいはしておけばよかったなと思いました。

AnyCableというActionCableの代替の話も、パフォーマンスの違いとか聞けて興味深かったです。自分はまだActionCableを使ったことがないので、サンプル程度でも作ってみようという気になりました。

あとはJRubyの話とか。GraalVMが出てきたので、JRubyどうなってしまうんやろ?という感じはありますが…。

今年はRubocopの話が多めだったなぁという印象でした。弊社でもLint系のものは使っていきたいよねという話は時々しているので、Rubocopの中の話とかが聞けて参考になりました。先人の踏み抜いた知見がフィードバックされて自動コードレビューが行われていくというのは、ありがたいことだと思うので、早めに入れていきたいと思います。

その他、開発したくなる〜!という刺激をたくさん受けるキーノートやセッションが盛りだくさんでした!

会社の報告会では、上記のような話と、Matzのキーノートについてと、ノベルティ配布会を行いました。

来年は福岡で開催なので、弊社からも大勢行けるようにしていこうと思います。

f:id:patorash:20180602180018j:plain

RubyKaigiのスタッフのみなさま、スポンサーのみなさま、ありがとうございました!

赤ちゃんの子育て中にあったらいいものリスト

注意:この記事はまだ書きかけですが、公開してます。随時更新予定です。

twitterのフォロワーさんたちで子供が生まれた人がチラホラおりましたので、短い育児経験ながら、子育て中にあったほうがいいものとか、これは買うのやめとけ!(別の買え!)というもののリストを作ってみようと思います。なお、これは私の主観が入っておりますので、ご了承ください。

買った方がいいもの

ベビーシート

まずオススメなのは、マムズキャリーです。これは車に乗る人のみになりますが、めちゃくちゃ重宝しますのでオススメです。

注意点としては、子供のサイズ的に1年間しか使えないのですが、1年しか使えないからと言ってベビーシートの選択肢から外すのは勿体無いです。むしろ1年後に他のチャイルドシート・ジュニアシートを買った方がいいと思います。

持ち運びできる

カゴのような構造なので、持ち運びできます。ちょっとした移動くらいだったら、ベビーカーに移して…とかしなくてもそのまま運べちゃう!うちでは、病院に行くときや外食するときはこのまま運んでます。外食の場合は、お座敷でも大丈夫な分、ベビーカーよりもいいかもしれません。

また、赤ちゃんが寝てしまっても、マムズキャリーを外してそのまま移動できるから、ほぼ起こしてしまう心配がありません。家に帰ってきても寝ていたら、そのまま家に運んで、起きるまで置いといてます。

最高にコスパいい

6,000円くらいで買えます。うちは西松屋で買いました。正直、ベビーシート、チャイルドシートってどれがいいのかすごく迷うし、しかも高いものはかなり高いです。これ買って1年間を過ごしている間に大いに迷ってそれから次のチャイルドシートを買うのがいいんじゃないか?と思います。私はチャイルドシートにもなるジュニアシートを買えばええんやで」という育児の先輩方のアドバイスに従って、そうしました。

抱っこ紐

抱っこ紐もいろんな種類というか、いろんなブランドがひしめき合っていて、さらに高い…。高いやつは確かにモノはいいのですが、比較しているとだんだんわからなくなってきます。最終的にうちはこれにしました。

コスパがいい!

1万円以下だけれど作りはしっかりしています!ホールド感もよく、安定性がしっかりしていて、他の有名ブランド品と遜色ないと思って決めました。

両手が使えるようになる

実はこれの前にもっと安い肩掛けの抱っこ紐を使っていたのですが、安定感がよくなくて、結局腕が自由に使えなくて困ったことがありました。

抱っこしていないと赤ちゃんが泣くときは、こういう抱っこ紐をつけて部屋を練り歩くというだけでも結構泣き止んでくれます。そういうときに両手が使えるってのは重要なことで、泣き止ませながら他のことができるわけです。私の場合は抱っこ紐をつけて部屋の中をウロウロしながら読書してます!

コードレスクリーナー

はっきり言っちゃうと、ダイソンのコードレスクリーナーがいいです。普通の掃除機にも使えるし、ハンディクリーナーにもなるし、吸引力はすごいので、他のハンディクリーナーを別で買うくらいなら、ダイソンでいいと思います。ダイソンならなんでもいいというわけではなく、オプションをなるべく多くつけて買いましょう。布団用とハンディ用は必須です。

サッと掃除できることが重要!

コードレスがいいのは、掃除したいところだけサッと掃除できることです。コード付きだと、やる気がたまらないとなかなかできません。赤ちゃんが寝る周辺だけ、布団用のヘッドでガーッと掃除して、他は気になるところをチョコチョコと…くらいでいいのです。

あと、成長してきて幼児用のおやつを与えるようになると、おやつの食べカスとかがそこら中に散らばります。そういうときにも、コードレスクリーナーはサッと掃除できるので助かります。

車の中も掃除できる

コードレスクリーナーの強みは、車の中も掃除できることです。チャイルドシートの周りは本当にめちゃくちゃおやつのカスだらけになります。そんなときにもダイソンのおかげで綺麗に掃除できています。

保温調理器

保温調理器は子育て世帯じゃなくてもオススメできますが、子育て世帯はマジであったほうがいいと思います。うちはシャトルシェフを使っています。あまりに便利なので、友人の赤ちゃん誕生祝いにシャトルシェフを贈ったほどです。

ずっと火の側にいなくてもいい

例えば、時間のかかる煮込み料理(カレーやおでんとか)を作る際は、焦げないようにするためにずっと鍋の側にいなければなりませんが、そうはいっても赤ちゃんが寝返りしだすと、ちゃんと呼吸できているか見ないと危険です。なのであんまり長時間台所に立つのは難しいところですが、保温調理器は材料を入れて沸騰したら密閉するだけであとは放ったらかしにできるので、赤ちゃんの側にいられる時間が増えます。

それに、なんといっても時間が作れるところがいいです。昼食作りのときに夕食分の材料も一緒に切っておいて、煮込んで放置しておけば、それが夕食になるので、夕食作りの時間がカットできます。子育て中はいかに自分の時間を確保するかが大事なので、料理にかかる時間が減らせるのはいいことです。

ちなみに、長時間煮込む際にかかるガス代や電気代(IHの場合)もかからなくなるので、長期間で考えると家計にも優しいです。

離乳食作りに最適

うちは子供の離乳食用のお粥を作るときにシャトルシェフが本当に、本当に、大活躍しました。離乳食のお粥は、まずは10倍粥、次に5倍粥、次に3倍粥…のように、徐々にお粥の濃度を濃くしていきます。しかし、水分が飛びすぎたり焦げたりして本当に難しいです。そんなとき、シャトルシェフで沸騰してから放置しておけば、水分が飛びすぎたり、焦げる心配が一切ありません。特に、焦げは保温後に加熱してないのだから、絶対に焦げません。シャトルシェフでお粥を大量に作って、冷凍保存はよくやっていました。

また、他の離乳食でも同様に使えます。離乳食は柔らかめにしないといけないので、長時間煮込まなければなりません。このときも、沸騰したら保温調理器に入れて30分〜1時間放置。これで柔らかい離乳食ができます。

保温調理器は台所のルンバ的存在

ルンバは自動お掃除ロボットですが、これは別に時短にはなりませんが、自動でお掃除してくれますね。シャトルシェフも同様、時短にはなりませんが、自動で調理してくれる、そんな存在です。ルンバはそこそこなお値段しますが、シャトルシェフは大きさにもよりますが1万円以下で買えます。1万円程度で調理にかかる時間を大幅に削減でき、かつ煮込み料理が美味しくでき、しかも光熱費が減らせるので、一石三鳥ですよ!

続きはまた後で

時間ができたらまた書いていきますのでお楽しみに。

Railsのエラー画面の謎挙動にハマッた

Railsのcontrollerで、トランザクションを使っているところで、rescueブロックの処理が書いてあるのにそこに辿り着けていない現象に遭遇しました。

悩んでいることをチャットワークに書いたら、同僚の @kazuhisa1976 さんが、小さいRailsアプリを作って検証してくれたのですが、そこで面白い挙動が見つかりました。

検証環境を作る

小さいRailsアプリを作る

まず、小さいRailsアプリを作ります。

rails new sample
cd sample
bin/rails g scaffold User name:string
bin/rails db:migrate

Userモデルにvalidationを加えます。

class User < ApplicationRecord
  validates :name, presence: true
end

UsersController#createで、検証のためにトランザクションを使ってみます。保存に失敗したら、root_pathにリダイレクトするという設定です。

class UsersController < ApplicationController
  # 省略
  def create
    @user = User.new(user_params)
    ActiveRecord::Base.transaction do
      @user.save!
    end
    redirect_to @user, notice: 'User was successfully created.'
  rescue => e
    redirect_to root_path
  end
  # 省略
end

Railsアプリを起動しておきます。

bin/rails s

これで、とりあえずの検証環境は出来上がりました。

検証する

正常系

まずは、nameを入力して投稿してみます。

f:id:patorash:20180519084703p:plain

当然ながら成功。

f:id:patorash:20180519084744p:plain

異常系

次は、nameを何も入力せずに投稿してみます。nameが空なので、@user.save!が失敗して、root_pathにリダイレクトされるはずです。

f:id:patorash:20180519084635p:plain

おおっと!リダイレクトされませんでした!rescueブロックに辿りつかずに、@user.save!ActiveRecord::RecordInvalidが発生していると言われました。私が遭遇した現象と全く同じ!

f:id:patorash:20180519084959p:plain

原因を探る

エラー画面のWebコンソールで、実験してみましょう。

f:id:patorash:20180519085705p:plain

はい。実はこのRailsアプリには、root_pathが定義されてません。それが原因です。例外の情報が入っている変数eもあることから、rescueブロックには実は辿りついています。

そうなんです。rescueブロックで発生したエラーの原因を表示せず、元の例外が発生した原因を表示してしまうということです。

修正する

このRailsアプリでいえば、routes.rbでroot_pathを定義してあげれば、直ります。

Rails.application.routes.draw do
  resources :users
  root to: 'users#index' # root_pathを定義
end

まとめ

rescueブロックで例外が発生すると、大元の例外の情報を基にエラー画面が作られてしまうので、rescueブロックに辿り着けないようなエラー画面が表示されたら、rescueブロック内にブレイクポイントを置いて1つ1つ検証していくのがいいです。恐らく、今回と同様に変数かメソッドの定義がないなどが原因です。

自分のエラーの原因も、メソッドがないことが原因でした。 作った当初はRails3か4くらいで、paramsがHashを継承していたのですが、Rails5にアップグレードして(今使ってるのは5.1.4)Hash継承ではなくActionController::Parametersになった影響で、mapメソッドがなくなっていました。

似たようなコードを書くと、以下のような感じ。

# users_paramsはfields_forで作られた配列のデータ
def update
  ActiveRecord::Base.transaction do
    users_params.each do |id, values|
      user = User.find id
      user.assign_attributes values
      user.save!
    end
  end
  redirect_to users_path
rescue => e
  @users = users_params.map do |id, values| # ここでundefined method
    user = User.find id
    user.assign_attributes values
    user.valid?
    user
  end
  render :edit
end

参考情報はこちら。

qiita.com

そこで、以下のように修正。

rescue => e
  @users = users_params.to_h.map do |id, values| # to_hメソッドを挟む
    # 略
  end
  render :edit
end

今回は管理画面側のため、網羅的にテストを書いていなかったことが気づけなかった原因だったので、例外時のテストも追加しておこうと思います。