| @技術/プログラミング

CircleCI.png

もうすぐ Ruby 2.5 が出そうだけど、このブログの Ruby のバージョンを 2.4 に上げた。

Ruby 2.4 がリリースされたあとすぐに Lokka を Ruby 2.4 に対応させようと頑張ってみたのだけど、 Ruby 2.4 に上げるためには ActiveSupport のバージョンアップが必要だった。しかし padrino-helper 0.11.4.1 が古いバージョンの ActiveSupport に依存しており、 Ruby 2.4 に対応した ActiveSupport を利用するためには padrino-helper をバージョンアップする必要があった。 padrino-helper をバージョンアップしてみると 0.12 から大幅な変更が入ったようで、ビューの表示がぐちゃぐちゃになった。 layout が適用されず中身の partial ビューだけ表示されたり、 HTML が過剰にエスケープされたり。 padrino-helper は 0.11 時代にあった脆弱性を修正して標準ですべての文字列出力を escape するようになったっぽい。しかし Lokka の方がそれに対応できておらずビューがぐちゃぐちゃになっているようだった。

Lokka はヘルパーメソッドやモデルで HTML を文字列から生成しているところがあって、そういうところをこまめに html_safe していったところ余分なエスケープは解消されていった。利用している gem でも文字列から HTML タグを生成しているところがあったので、そういうところもつぶさに調べていって無心で html_safe したり mark_safe していった。

とりあえずこのブログを Ruby 2.4.2 でしばらく運用してみて問題なかったら Lokka 本体の方に Pull Request を出すことにしよう。

あと今回ついでに CircleCI でテストしてテストが通ったら勝手に Production に deploy されるようにしておいた。 CircleCI ほんと便利。

追記 2017-12-03

Pull Request 出しておきました。

| @ブログ

以前、 The Old Reader について書いたとき、クローリングが遅くて不満だ、というようなことを書いていた。

The Old Reader はフィードの更新間隔がクソで、プレミアムアカウントのユーザーが登録しているフィードは 30 分おきにフィードをフェッチするとあるんだけど、全然そんなことなくて、平気で 4 時間遅れたりする。偏りがあって、頻繁に更新されるブログのフィードは頻繁にチェックされるけど、頻繁に更新されないブログのフィードはあまりチェックされないみたい。クローラーのつくりとしては正しいのかもだけど、利用者としては不満が残るなぁ。

The Old Reader - portal shit!

そしたら以下のようなコメントを Pinboard でたまわってた。

フィード・リーダーのクローラーが遅い問題の話。Feedlyもそうだけど、クローラー側のポーリングは4時間ごとくらいで良くて、ユーザー(サービス)側がPubSubHubbbub使って欲しいな。

https://pinboard.in/u:hail2u/b:e25b4cfe2a4a

確かにその通りなのでこのブログでも PubSubHubbub 使うようにしてみた。 Sidekiq とか入れたら Worker にやらせることできていい感じにできるんだろうけど、とりあえずこんな感じで乱暴に通知してる。

class Entry
  after :save do
    if Lokka.production? && !draft
      system("curl -s https://pubsubhubbub.appspot.com/ -d 'hub.mode=publish&hub.url=https://portalshit.net/index.atom' -X POST")
    end
  end
end

爆速で The Old Reader や Inoreader に更新が反映されることを確認したので Feedly とかでも更新後に即反映されるようになってると思う。

| @技術/プログラミング

autodoc-generation-flow.png

autodoc を導入して Rails プロジェクトで Request Spec を書くと自動的にドキュメントが更新されるようにした。 autodoc 自体は前々職の頃から利用していて大変お世話になっていた。ただ最初の頃は手元で AUTODOC=1 bundle exec rake spec:requests して手動でドキュメント更新していた。ドキュメントが更新されるかどうかは担当者の心がけ次第なのでよくなかった。

前職では CircleCI を使っていて、デプロイや Asset Precompile など CI でいろいろやるのが当たり前だったので、 Pull Request が Merge されたタイミングでドキュメント生成するように .circleci.yml をカスタマイズしてた。

いま仕事しに行ってるところでは .circleci/config.yml を version 2 にしていて、 version 2 からは workflow の概念が導入されたので、頑張ってシェルスクリプトで条件分岐させたりする必要がなくなった。 .circleci/config.yml は以下のような感じになってる。

version: 2

shared: &shared
  working_directory: ~/app
  docker:
    - image: circleci/ruby:2.4.1-node
      environment:
        PGHOST: 127.0.0.1
        PGUSER: username
        RAILS_ENV: test
        REDIS_HOST: localhost
    - image: circleci/postgres:9.6-alpine
      environment:
        POSTGRES_USER: username
        POSTGRES_PASSWORD: pasword
    - image: redis:3.2-alpine

jobs:
  build:
    <<: *shared
    steps:
      - checkout
      # Restore bundle cache
      - &restore_cache
        type: cache-restore
        key: app-{{ checksum "Gemfile.lock" }}
      # Bundle install dependencies
      - &bundle_install
        run: bundle install -j4 --path vendor/bundle
      # Store bundle cache
      - &save_cache
        type: cache-save
        key: app-{{ checksum "Gemfile.lock" }}
        paths:
            - vendor/bundle
      # Database setup
      - &db_setup
        run:
          name: Database Setup
          command: |
            sudo apt install postgresql-client
            bundle exec rake db:create
            bundle exec rake db:structure:load
      - type: shell
        command: bundle exec rubocop
      # Run rspec in parallel
      - type: shell
        command: |
          mkdir coverage
          COVERAGE=1 bundle exec rspec --profile 10 \
            --format RspecJunitFormatter \
            --out /tmp/test-results/rspec.xml \
            --format progress \
            $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)
      # Save artifacts
      - type: store_test_results
        path: /tmp/test-results
      - type: store_artifacts
        path: coverage

  generate-doc:
    <<: *shared
    steps:
      - checkout
      - *restore_cache
      - *bundle_install
      - *save_cache
      - *db_setup
      # Generate document
      - type: shell
        command: |
          AUTODOC=1 bundle exec rake spec:requests
      - run:
          name: Setup GitHub
          command: |
            export USERNAME=$(git log --pretty=tformat:%an | head -1)
            export EMAIL=$(git log --pretty=tformat:%ae | head -1)
            git config --global user.email "${EMAIL}"
            git config --global user.name "${USERNAME}"
      - run:
          command: |
            git add doc
            git commit --quiet -m "[ci skip] API document Update

            ${CIRCLE_BUILD_URL}"
            git push origin ${CIRCLE_BRANCH}

workflows:
  version: 2
  build-and-generate-doc:
    jobs:
      - build
      - generate-doc:
          requires:
            - build
          filters:
            branches:
              only:
                - master

master ブランチでのビルドのときだけ generate-doc という job が実行されるようになっている( master ブランチ以外のビルドではドキュメント生成がスキップされる)。buildgenerate-doc で重複してる部分が多いので YAML のアンカー機能を使って重複を整理しているけど結構長い。 generate-doc ジョブでドキュメントが生成されると勝手に GitHub の master ブランチに対して push する。このときコミットメッセージに [ci skip] という文字列を付けておけば、 CircleCI はビルドをスキップするので延々とドキュメントの自動更新ビルドが走り続けることはない。

おかげでいまはテストさえ書けば、実際の API と同じフォーマットのドキュメントが自動生成されるのでとても便利になったと思う。

ちなみに JSON Schema というのもあって、これは JSON に仕様を書くとドキュメントやらモックサーバーを作ってくれるものらしい。めっちゃ便利そうだけど、ちゃんと使うのにはそれなりに仕組みを整える必要がありそうで手を出していない。 autodoc の作者の r7kamura さんのブログにも書いてある通り、 autodoc の便利なところは以下だと思う。

実際にアプリが生成した内容からドキュメントを生成するため、実装とドキュメントの乖離が少なく抑えられる。 また、テストを書くことの見返りが増えるため開発者がテストを書くのを推進しやすい。

全てがJSONになる - ✘╹◡╹✘

autodoc で master ブランチへの Merge をトリガーにしてドキュメントを自動生成するというポリシーでは、 B/E 側の作業中に F/E の人が API のドキュメントを見られなくて不便だという問題は確かに存在する。しかし JSON Schema で事前に仕様を固めて実装前にモックサーバーやドキュメントを提供できたとして、果たして事前に決めたとおりに B/E も F/E も実装できるのだろうか。きっと作っていく途中で「やっぱりアレはコレに変えたい」みたいのが双方から出てくると思う。

↑の r7kamura さんの記事では他に外の API をモックするダミーサーバーを JSON Schema で作ったりしてる。確かにすでに仕様が固まった外部の何かをモックするサーバーのセットアップなどには便利なのかもしれない。ただいまのところは autodoc での後追いドキュメント自動生成で自分は事足りるかなという感じがしている。

| @労働

Kaizen Platform

Qiita:Team エントリのレベルが高い

CEO や CTO 、プロダクトマネージャーの書く Qiita Entry のレベルが高く、 Qiita:Team のタイムラインがはてブのホッテントリのようだった。ブックマークできるもんならしたいという感じ。お金を儲ける仕組みってこうやって作り出されていくんだなぁと思いながら眺めてた。技術顧問の伊藤直也さんが残していった名エントリも結構あった。 Kaizen エンジニア行動指針とか。

SRE (インフラチーム)のレベルが高い

インフラが盤石だった。 SRE は二人しかいなかったがとても仕事が速く、困ったことがあって Slack のインフラ相談チャンネルで相談したらたいてい 3 分くらいで問題が解決してた。

yosudo さんは問題解決能力が高すぎていまは SRE ながら VP of GA (総務部門のドン)やってるし、 glidenote さんは SRE 業務をこなしつつも R&D して新しめの技術をどうやってビジネスにフィットさせていくかを追求したりしてる。

SaaS や IaaS をフル投入したモダンな開発体制

SaaS や IaaS を使うのはかっこいいからとかエンジニアが楽しいからではなく、その方が最終的にかかるお金が少なくて済むし、事業の圧倒的な成長に対応(スケール)できるから。詳しくは yosudo さんのスライドをご覧ください。

コードのクオリティが高い

Kaizen Platform も 5 年目に入っているのでプロダクトのコードがレガシー化しているところがあるのは否めないけど、レガシーと言っても本当に悲惨なレガシーコードとは比較するべくもなかった。ユニットテストはちゃんと書いてあるし1、インフラは AWS だし平均的な水準が高い。メインのシステムは Ruby と Rails で構築されていたけど、一部の機能を Go lang でマイクロサービス化したり、ログ集約サーバーは Node.js で構築されていたり、場所によっては C で書いて高速化してあるところもあり、特定の技術に依存するのではなく目的、状況に応じて柔軟に使う技術を選定していくところがこれまでにない感じだった。エンジニアが Ruby しかわからないから Ruby をずっと使いますというような消極的な技術選定ではなかった。

プロダクトマネージャーがいる

プロダクトマネージャー( PM )がいる会社で初めて働いた。データ取りとかは PM 自身ががんがん SQL 書いて取るし Domo や Redash などの BI ツールを PM 自身が使いこなしているので「○●のデータを本日 11 時までに大至急抽出してください!!!、!(現在 10 時)」みたいなことを割り込みで依頼されるということはなかった。プロダクトマネージャーは技術、ビジネス、市場、顧客のすべてに精通していてリーダーシップがあり、ミニ CEO のような人たちだと思った。

QA チームがいる

PM のほかに QA チームの人がいて、 QA 観点で動作検証したり仕様に指摘をしてくれてめっちゃありがたかった。しかも QA チームの皆さんが優秀。エンジニアが自分で作って自分で動作検証するのではどうしても先入観が入ってしまって検証が不十分になると思う。むかし働いていたブラック企業では午後 10 時まで開発したあと午前 4 時まで自分で作ったやつを動作検証してエクセルエビデンススクショ職人業もこなしてたけどあれは本当に不毛だった。エンジニアが作ることに集中できる環境はすばらしい。

社長が近い

社長室などはなく、 CEO も普通にフリーアドレス席に座って仕事しているので CEO がすごく身近な存在で話しやすかった。会社にいる人たちも CEO に対して従順というよりは、その道のプロの人たちが集まっていて、若い CEO を助けるために一肌脱いでる感じがあった。雇われているというより雇われてやってる感じ。 CEO のことを親しみを込めて「スドケン」と呼び捨てにするし、むやみやたらに上下関係を作って序列通りにひれ伏すのはださいという雰囲気があった。

セールスが強い

こんなに強力なセールスがいる会社で働いたのは初めてだった。砂漠でこたつを売ってくる男の異名を持つ人がいたり。元リクルートの人たちを中心とした精強なセールスチームがいて、大企業からどんどん契約をとってきてた。自分はこれまで中小零細企業とか上場企業でも個人向けサービスをやってる会社でしか働いたことなかったので、こんなに大人っぽいカッチョイイセールスの人たちがいる会社で働いたのは初めてだった。

社員の趣味がエクストリーム

雪山スキーとかサーキット走行とかフルマラソンとか雨が降らない限り毎週末キャンプしてるとか鎌倉に住んで毎朝サーフィンしてから仕事とか、社員の趣味がエクストリームだった。アウトドアでなくても仏像なぞり描きと御朱印集め、囲碁、観葉植物、美食、マイル乞食、不動産運用、マンション管理組合理事など、趣味が中途半端な人がいなかった。

性善説とアンチマイクロマネジメント

社員は悪いことをしないという前提で会社が動いているように思った。承認システムみたいのがあるにはあったけど、新しい SaaS を使いたいときには CTO に相談すればよかった。マイクロマネジメントを嫌う風潮もあり、休みをいつ取るかなどは自由だった。細かいマネジメントをする方が無駄にコストがかかるし、そもそもマイクロマネジメントが嫌いで大企業を辞めてきた人たちで構成される組織だった。

求められる水準が高い

マイクロマネジメントされない一方で、求められる水準は非常に高い。四半期ごとの目標設定と評価面談では、目標として設定したことをこなすだけではマイナス評価となる。目標を達成するのは当然で、求められていること以上の成果を出すことができたかが評価軸だった。

採用フローで期待されていることを説明される

自分が受けた頃は採用にとても時間をかけていて、これこれこういうことを期待しています、ということを丁寧に説明された。 2 回くらい現場の人と面接して最後に重役と面接して「お、キミいいネ、採用!」という感じではなかった。むしろ最初に CTO と話して次に CEO に口説かれ、あとから将来同僚となるエンジニアやプロダクトマネージャーと話すという感じだった。採用フローでポジションや求められる役割について説明があり、入ってから「俺はマネージャーだったのに平で雇いやがって」というような期待のミスマッチが起こりにくかった2

情報が閉じられていない

会社の売り上げ、状況は公開されていた。毎週金曜朝から行われる All Hands で数字の共有があり、目標に届いていないなどのアナウンスがあった。また隔月で行われる全社合宿(合宿だけど日帰り)でも会社と事業の現状について説明が行われ、社員全員で今後の方向性についてディスカッションを行う文化があった。

仕事をしていく上で、会社の経営状況について知らされていることは重要だと思う。かつて働いていたブラック企業は顧客が自分たちにいくらお金を払ってくれているかを教えなかった(教えたら自分たちが搾取していることが従業員にばれるため)。お客さんがいくら払ってくれているかを従業員が知らないと払ってくれている金額に応じた仕事をすることは難しいと思う。


厳しいなと思うところもあるけど、意欲がある人には機会が与えられる職場だと思う。総じてパワーのある会社だった。

なんでこんなことを書くのかというと、実は Kaizen Platform を先月で退職していた。すべて自分の能力不足が原因で、決してペパボを辞めたときのような積極的な退職ではなかった。前回の退職は前進だったけれども今回の退職はまさに撤退という言葉がふさわしい。なので退職日に合わせて記事を書くことができなかった。

Kaizen では入社早々に障害を起こしてしまったり、アサインされたプロジェクトを頓挫させてしまったりで在籍期間中に大したバリューを出すことができなかった。

ただ Kaizen Platform で働いた 1 年 11 ヶ月は自分にとって非常に貴重な時間だったと思う。リモートワークのおかげでこれまでにない生活をすることができた。家族が忙しいときに家事をしたり子どもの幼稚園の送り迎えをしたり。普通のお父さんではできないようなことができた。

なにより通勤がないのが素晴らしかった。通勤は実際に移動してる時間は 30 分でも家を出る前の身支度が必要だったり、なんだかんだで朝晩に 1 時間ずつ時間を取られてしまう。家族がいる人の場合はこれが自分だけでなく家族の負担にもなる。通勤に取られる 2 時間があれば洗濯や炊事ができる。この 2 時間が生活にゆとりをもたらしてくれていたと思う。

ダイエットにも成功した。糖質制限をして減量したけど、普通に朝から出社して働いていたら食事の準備に時間をかけられず、摂取しやすい炭水化物中心の食事を続けていまもデブのままだったのではないかと思う。

半日休む必要はないんだけど、昼間数時間だけ自分のことに時間を割きたい、用事が終わったらまた仕事しよう、というようなことができたのがとても良かった。病院通いがとても捗った。

こういうの、ワークライフバランスと言ってしまえばありきたりな感じがするけど、ちゃんとワークライフバランスを保つのは尊いことだと思う。

反面、リモートワークにはつらい面もあった。何をやればいいかが明確ではない状態でのリモートワークはとても難しい(参照: リモートワーク)。チームメンバーの意識が統一されていない状況にもフィットしない。何をやるべきかを自分で明確にできる人遠隔でもリーダーシップを発揮できる人でないとリモートワークはこなせないと思った。自分にはその両方が欠如していた。誰からも管理されない状況で自分でやるべきことを明確にし仕事をしていくのは自分で自分を律することのできる人でないと難しい。サボろうとしているわけではないのに時間だけが経過していくのは非常にもどかしかった。小さい頃から親や学校の先生に「次はこれをやりなさい」、「あれをやってはダメ、これをやってはダメ」と指示監督されてきた人間が突如として管理されない状態で働くのはきつい。管理されて働いているときは管理者に対して不満を持ちつつも、管理されないと全く何もできない自分の不甲斐なさが露わになるばかりだった。

全体として、自分に足りない部分がわかった 1 年 11 ヶ月だった。上に挙げた、リモートワークをうまくやっていくための資質(何をやるべきかを自分で明確にできる、リーダーシップがある)は、オフィスでより良い仕事をしていく上でも必要なものだと思う。また自分のやりたいことに貪欲であるべきことを学んだ。エンジニアだけでなく、セールスやカスタマーサクセスなど違う職種の人と話をしていて感じたことだった。やりたいことがあるのに黙ってもじもじしててもやりたいことは回ってこない。やりたければ相応の準備をした上でやりたいと手をあげないとやらせてもらえない。空気を読まず、遠慮をしないことが良い仕事をしていくために必要だと感じた。

Kaizen でのリモートワーク失敗経験をどう今後の人生に生かすか。以下のツイートを繰り返し眺めながら悔い改めていきたいと思う。


  1. むしろテストコード多すぎて CI が遅くなるのが問題だったけど CircleCI に重課金してテストを並列実行して凌いでた。 

  2. ただしたまに逆のパターンが起こってた 

| @技術/プログラミング

Archive ページに React Router を導入して、年を切り換えたときにページ遷移なしで表示を切り替えられるようにした。あわせてカテゴリ一覧を表示して記事を絞り込み表示するようにした。

React はこれまで cdnjs からダウンロードしていて JSX の変換は Babel の browser.js を使ってブラウザーで行っていたけど JS の開発環境もちょびっとだけモダンにして npm モジュールをインストールして手元でいろいろやるようにした。一枚のスクリプトに書いていたコードはコンポーネントごとに分割して browserify で連結するようにした。 React を自前で配信するとファイルサイズも結構無視できない感じになったので minify なんかもするようにした。

F/E の情報収集、きらきらネームが多くて調べるのにかなり時間がかかった。 npm モジュールの名前とそれが何をやるためのものなのかがすぐに分からない。じいちゃんばあちゃんが横文字を覚えられないのに似てる。 Ruby もきらきらネーム gem は多いけど寿命が長いのである程度触ってれば覚えられる。 JS はライブラリの寿命が 1 年くらいだから雨後のたけのこのようにどんどん新しいやつが出てきて全然覚えられない。自分が高齢化してきてるだけで若い人だったらすんなり頭に入ってくるんだろうか。

| @技術/プログラミング

fish が流行ってるっぽいので移行してみた。 peco を使えるようにしたらほぼ問題ない感じで使えてビックリ。 ~/.zshrc 長くなってきてたし捨てられそう。よかった。

ちょっと触ってみた感じ、設定ファイルの配置場所が ~/.config/fish/ であること、 PATH の通し方が特殊であること、環境変数に値を入れるときは env FOO=bar のように env 付きでやらないと怒られることが変わってるなと思ったが、 fish スクリプトの文法が Ruby っぽくて書きやすい・読みやすいのはとてもよい。公式ページにある "Sane Scripting" の項目が面白かった。

Sane Scripting

| @技術/プログラミング

puma はメモリ食い

puma にして喜んでたけど二時間後くらいに NewRelic で様子を見てみたらメモリ 90% 以上消費してて swap ファイルもめっちゃでかいのができてて暴発一歩手前になってた。

一旦 puma をシングルモードからクラスターモードに変えて puma_worker_killer を導入し、 worker を定期的に再起動するようにした。ただ restart 後のメモリ増加傾向は変わらない。

引き続き NewRelic で様子を見ていると Python2 が一番メモリを食っている。このブログは Syntax Highlighting に Python の Pygmentspygments.rb 越しに使っている。これまで unicorn の worker プロセス 2 個で動かしていたときには最大でも Python のプロセスは 2 個で事足りてたけど、 puma にして 16 スレッド( puma のデフォルト)が起ち上がるので、 16 個の Python プロセスが起動していたっぽい。

これは意味ないなということで以前利用したことがあった Pygments の Ruby 移植版である rouge に変えてみることにした。こちらは Ruby なのでスレッドが別プロセスを起動してメモリ爆発ということにはならないと思われる。

puma_worker_killer と Pygments 置き換え後半日様子を見たが、これでメモリ消費量爆発ということはなくなった。

sidekiq でスレッドを増やしたときにも似たようなことを経験したけど、 Ruby から外部のプロセスを起動するようなプログラムを書いているときにマルチスレッドで処理をさせると、 Ruby のプロセスは増えなくても外部のプロセスがめっちゃ増えてそのせいでメモリあっぷあっぷになったり CPU の使用率が高まったりする。マルチスレッドプログラミングのご利用は計画的に。

異なるワーカー間でセッションを継続できない?

puma をクラスターモードにしたことで別の問題も発生した。なんとセッション情報が異なるプロセス間で共有されていないっぽい。なのでログインしてすぐに再ログインを求められるようになった。これは不便。原因を調査するには時間がかかりそうだったのでシングルモードに戻してみることにする。

まとめ

  1. デフォルトの 16 スレッドでは puma は unicorn に比べてメモリを沢山消費する可能性がある
  2. Ruby から外部のプロセスを起動するような処理に注意
  3. puma のクラスターモード( master worker モデル)でセッション情報がリセットされる問題に遭遇

追記

セッション情報が異なるプロセス間で共有されていない

これはちゃんと設定があって、 puma.rb に preload_app! を記述すればよかった。

If you're running in Clustered Mode you can optionally choose to preload your application before starting up the workers. This is necessary in order to take advantage of the Copy on Write feature introduced in MRI Ruby 2.0. To do this simply specify the --preload flag in invocation:

# CLI invocation
$ puma -t 8:32 -w 3 --preload

If you're using a configuration file, use the preload_app! method, and be sure to specify your config file's location with the -C flag:

$ puma -C config/puma.rb

# config/puma.rb
threads 8,32
workers 3
preload_app!

-- https://github.com/puma/puma#clustered-mode

これでプロセス間でセッション情報が共有されるようになった。