LIVESENSE ENGINEER BLOG

リブセンスエンジニアの活動や注目していることを発信しています

LIVESENSE ENGINEERING BLOG

MENU

imgixをIESHILに導入してみた感想

こんにちは、不動産情報サイトのIESHILでエンジニアをしている須貝です。1年半ぶりくらい2回目の登場です。前回はCircleCIでbundle updateする話をしたのですが、あれはもうdependabotに置き換えました。自前で作るより遥かに高機能で良いです。
そこにも少し関連する話で、IESHILの開発では「ビジネスのコアの部分に関係しないものはできるだけ自分で作らない」ことを大切にしています。例えばWebアプリケーションのインフラはHerokuですし。少人数でプロダクト開発に集中するには合理的な判断だと考えています。で、今回はその一環としてimgixを導入した話をしたいと思います。

imgixって何?

まず「imgixって何?」という方に非常に簡単にご説明しますと、リアルタイムに画像を処理してくれてさらにCDNもセットになっているSaaSです。「いみじっくす」と読むそうです。

もう少し調べたらimgixのPress Kitにも読み方についての情報がありました。やはり「image + icks」だそうです。日本語っぽく読むなら「いめーじっくす」なんでしょうか。imgixが公開しているビデオを見ると「いみじっくす」と言っているように聞こえるので私は「いみじっくす」で行こうと思います。

なお、国内では日経新聞さん、一休.comさんといったところでの導入事例があるようです。

特徴は主に下記のような感じです。

  • 画像のURLにクエリパラメータを指定することでリサイズ、変換などが簡単にできる。APIの種類も豊富なので、かなり色々なことができます。詳細はAPIリファレンスをご覧ください。
  • WebP対応が簡単です。上記のパラメータに関連する話なのですが、auto=formatをつけるとWebPに対応しているブラウザにはWebPで画像を返します。Lighthouseの警告が減ってうれしいです。
  • バックエンドにS3を指定できるので、すでにS3を利用していれば導入が非常に簡単。Google Cloud Storageにも対応しています。
  • 料金体系は実際にアクセスがあった枚数ベースでの請求がメインで、それプラス転送量となっています。ですので毎月使った分だけ支払う形です。ここは後ほどもう少し詳しく説明します。

つらつらと挙げましたが、ひとことで言うととにかくいろいろ簡単にできます。これに尽きます。

導入時にやったこと

IESHILには下記のようにマンションの画像が載っておりまして、以前はこれらの画像はAWSのEC2上に構築した画像リサイズサーバーから配信をしていました。

f:id:ssugai:20190619184014p:plain

構成としてはS3に画像の元データがあり、リクエストがあった場合にリアルタイムに元データをリサイズをして配信していました。なお、当時はCDNを利用しておらず…。ですのでパフォーマンスの観点からも良くない状況が続いていました。
このEC2上で行っていた画像変換処理をimgixに置き換えました。特に技術的に難しいことはなく、行ったことは

  • これまで使用していたS3のバケットをそのままimgixに設定
  • Webアプリケーション側で画像を参照している箇所を少し修正した

くらいです。本当に拍子抜けするくらい簡単でした。

その結果

Before

imgix導入前。TTFBで300〜400ms程度。

f:id:ssugai:20190619185825p:plain
imgix導入前

After

imgix導入後。TTFBで15ms 🚀 こちらはCDN経由なのでBeforeとそもそも比較にならないくらい速いです。

f:id:ssugai:20190619185914p:plain
imgix導入後
imgixのダッシュボードで確認してみたところ、IESHILの場合はCDN経由とそうでないものを合わせた平均レスポンスタイムは140〜160msのようです。

導入してみた感想

以下、実際に導入してみた感想です。

良かった点

  • 繰り返しになりますが、導入が非常に簡単です。
  • クエリパラメータでの画像変換が多彩で強力です。ちなみにimgixにはSANDBOXというツールがありまして、ここでパラメータを変更しながらリアルタイムに画像の変化を試すことができます。いろいろとパラメータを試していたら半日くらい溶かしました。

気になる点

  • 唯一懸念点を挙げるなら料金体系でしょうか。imgixの料金はアクセスがあった画像のユニーク数(Master images)1000枚あたり$3 + 転送量が1GBあたり¢8となっています。コストの大半を占めるのがMaster imagesに対する課金です。ですのでプロダクトによってはコストパフォーマンスがあまり良くない可能性があります。ちなみにMaster imagesの数は月ごとにリセットされます。

Herokuを知っている方からは「なんでCloudinaryじゃないんだ?」などといったツッコミがあるかもしれませんが、Herokuのアドオンは従量課金ではないので……。というわけで私はimgixを導入して良かったと思います。このエントリーが画像配信周りでSaaSを検討している方の一助となれば幸いです。

大学で得た知識を活かしてエンジニアを目指す学生の方を支援する『プロダクトエンジニア養成講座』を開催します

f:id:boscoworks:20190617132803j:plain
プロダクトエンジニア養成講座

こんにちは。id:boscoworks です。

2018年末まで転職ナビのエンジニアリングマネージャを勤めていましたが、年明けからは全社横断でエンジニア採用を推進する業務をしています。

リブセンスは、『あたりまえを、発明しよう。』というコーポレート・ビジョンの達成に向け、データと技術を駆使してプロダクト開発をしています。

今夏、リブセンス初の試みとして、自分のプログラミング経験をリアルな開発現場で活かしていくための『プロダクトエンジニア養成講座』を開催します。いわゆるオンラインスクールのようなプログラミング言語習得のための講座ではなく、プロダクトを生み出していくためのノウハウをリードエンジニアから学んでいく、ちょっと変わったタイプの講座です。気になったかたはぜひエントリーしてくださいね。

プロダクトエンジニア養成講座とは

プロダクトエンジニア養成講座 | 株式会社リブセンス | 採用情報

大学において、ITやデータの活用を研究のテーマに据えたところが随分と増えました。理系の学生のみならず、文系であってもプログラムに日常的に触れているかたがいます。年々学生の知識レベルやITリテラシーが上昇しているのを嬉しく思うと同時に、学生がこんなにすごいんだから先輩も頑張らないと、というプレッシャーも感じます。

一方で、金銭的・時間的・地域的など、さまざまな理由から、せっかく得た知識をWebサービス開発に活かし、実際に不特定多数のユーザに使ってもらう経験を積めない学生もいます。大学生なので授業を受けて学業を修めるのは当たり前のことですし、真面目に取り組まれているかたを尊敬します。そんな方々が、どうしたらエンジニアになれるか分からず、就職活動で困ってしまうのは、とてももったいないと私達は考えました。

今回、リブセンスで実際にプロダクト開発をリードしているエンジニア2名が、プログラミング経験のある学生のかたを対象とした『プロダクトエンジニア養成講座』のメンターをつとめます。約4週間のトレーニングのあとには、きっとエンジニアとはどんな職業なのか、よくわかっていることでしょう。

コース

Webアプリケーション

最終的に Ruby on Rails を用いてWebアプリケーション開発を出来るようになるべく、現場で使う知識やノウハウを基礎から学び実践します。

ネイティブアプリ

スマートフォンで動作するネイティブアプリを、基礎から応用まで学び、アプリ開発を実践します。

応募資格

  • 在学中であること
  • なんらかのプログラミング言語を体験したことがあるかた(完全に未経験のかたは今回対象外です)

スケジュール

予告なく変更となる場合がありますので、必ず募集要項をよくご確認ください。

  • エントリー: 2019年6月14日(金) 〜 定員になり次第終了 ※絶賛募集中です!!
  • 選考: 書類選考と面接を順次実施します。
  • 講座の実施: 2019年8月〜9月 (約4週間。開始日については柔軟に対応いたします)

メンターのご紹介

@tchikuba (Webアプリケーション)

プロフィール

エンジニア歴15年。フリーランスから正社員になりました。現在はIESHILエンジニアチームのエンジニアリングマネージャーやってます。

サーバーサイドエンジニアがベースですが、最近は専らデータエンジニア的なことや採用もやってます。

メッセージ

個人的なミッションとして、エンジニアを始めクリエイティブを世の中に増やしたいと思っています。

その一環で個人的にプログラミング初心者向けの書籍執筆やエンジニア向けのメンターも行っています。未経験インターンのメンターとして、エンジニアに興味のある学生の方が、エンジニアという仕事に携わる良いキッカケとなれるようお手伝いできれば嬉しいです。

@neko (ネイティブアプリ)

プロフィール

他社でインフラからサーバサイド、ネイティブアプリまで幅広く開発を経験し、2015年9月にリブセンスに入社しました。

入社後は主にiOS、Androidアプリ開発に従事し、現在はネイティブアプリグループのリーダーとしてマネージメント業も兼務しています。

メッセージ

今までインターン等の経験がなくとも、エンジニアという職業に少しでも興味を持っている学生の皆さんに、実際の開発現場を経験してもらえる機会を増やしたいと思っています。

エンジニアという仕事がどんなものであるか、現役のエンジニアは普段どのように技術に向き合っているか等、今後皆さんがプログラミングを学び、エンジニアを目指すうえで、少しでも役立つ経験を積めるような場を作っていきたいです。

終わりに

今回の試みは、学生のかたに対するきっかけづくりの支援です。きっかけがあることで、自分の実力を伸ばすかたもいらっしゃるしょうし、今後の就職活動の方針をより具体的に考えられるようになるかたもいらっしゃるでしょう。もちろん就職先にリブセンスを選んでくれたら、それはどんなに嬉しいことかとも思うのですが、本当に目指すべきは、ともにソフトウェア開発を楽しめるひとの人口を増やして、開発者コミュニティを強大にしていくことです。

将来の仕事の選択肢のひとつにエンジニアを候補に入れている学生のかたにはぜひエントリーをしてもらいたいと思っています。

recruit.livesense.co.jp

技術で課題解決に挑む学生を支援するプログラム『Code for Happiness 2019』の募集を開始しました

f:id:boscoworks:20190607110802j:plain
Code for Happiness 2019

こんにちは。id:boscoworks です。

2018年末まで転職ナビのエンジニアリングマネージャを勤めていましたが、年明けからは全社横断でエンジニア採用を推進する業務をしています。

リブセンスは、『当たり前を発明しよう。』というコーポレート・ビジョンで謳っているとおり、プロダクトを通じて社会の課題に対してポジティブで不可逆的な変化を起こすことを目指し活動しています。

昨年に引き続き、技術により社会の課題解決に挑む学生を支援するプログラム『Code for Happiness 2019』を開催します。技術力に自信があり、その力を社会課題の解決に活かしたいと考えている学生の方は、ぜひエントリーしてくださいね。

Code for Happiness とは

Code for Happiness 2019 | 株式会社リブセンス | 採用情報

エンジニアリングで社会課題に挑みたい。自分で社会貢献に役立つものをつくりたい。そう考えている学生の方に対して、リブセンスが様々な支援を行う仕組みです。インターンシップ制度とは異なります。

支援の内容

支援金の支給
  • Code for Happiness 2019 に採択された方に、初期支援金として 10万円 を支給します。
  • 中間審査を通過すると、支援金として 20万円 を支給します。
  • 最終審査に承認されると、支援金として 20万円 を支給します。

支援金を生活費や開発費に当てることで、開催期間の約2ヶ月間をソフトウェア開発に集中することが出来ます。

リブセンスの現役エンジニアによるメンタリング

Code for Happiness 2019 に採択された方には、1名の弊社エンジニアがメンターを務めます。絶賛稼働中のプロダクトを支えるエンジニアから、技術・プロダクトの両面でアドバイスを受けることが出来ます。

スケジュール

予告なく変更となる場合がありますので、必ず募集要項をよくご確認ください。

  • エントリー: 2019年5月31日(金) 〜 2019年7月5日(金) ※絶賛募集中です!!
  • 選考: 書類選考と面接を順次実施します。
  • 採択連絡: 2019年7月19日(金) までに順次連絡します。
  • キックオフ: 2019年7月31日(水)
  • 中間審査: 2019年8月28日(水)
  • 最終審査: 2019年9月25日(水)

昨年のようす

昨年は総勢47名 (23チーム)からエントリーがありました。最終審査に残ったのは4名(3チーム)。

厳しい審査をくぐりぬけ、2ヶ月にわたって開発に没頭した学生の方々には充実した表情が浮かんでいました。

詳しいようすは弊社人事の羽山がまとめていますので、ご興味がある方はぜひご覧ください。

made.livesense.co.jp

終わりに

一般的なインターンシップ制度とは違って、アイディアからプロダクトに至るまで全て自分の手で作り上げていくのが Code for Happiness です。昨年も、短いエントリー期間だったにもかかわらず、多くの優秀な学生の方からエントリーを頂きました。最終審査に残るものはどれも甲乙付けがたい立派なもので、白熱した審査になりました。今年も Code for Happiness を通じて斬新なソフトウェアを目にするのを楽しみにしています。

recruit.livesense.co.jp

Bitrise + fastlane で自動化していることを紹介します

f:id:roana0229:20190529192957p:plain

デイキャンプとカメラにハマっているネイティブアプリグループの堤下(@roana0229)です。

みなさんはCI導入していますか?
今まで弊社のiOS,AndroidアプリではCIが導入できていない現状がありましたが、2月末にリリースしたマッハバイトiOS版の開発を機に導入して、自動化していることを紹介します。

CIサービスは色々ありますが最近はよくBitriseを導入しているのを見かけます。ビルド環境への対応の早さやGUIでの操作しやすさから弊社もBitriseを導入しました。

静的なチェックの自動化

UnitTest以外では、静的なチェックでdangerを利用しています。dangerではgitの差分が扱いやすいようになっています。例えば、Swaggerを利用していてるのでAPI定義のyamlとswagger-codegenによって生成されるコードの差分をチェックしています。

swagger_file = "swagger/api_doc.yaml"
swagger_auto_generated_files = ".+/Data/API/.+"
has_swagger_changes = !git.modified_files.grep(/#{swagger_file}/).empty? || !git.deleted_files.grep(/#{swagger_file}/).empty?
has_swagger_auto_generated_changes = !git.modified_files.grep(/#{swagger_auto_generated_files}/).empty? || !git.deleted_files.grep(/#{swagger_auto_generated_files}/).empty?
if has_swagger_changes
  if has_swagger_auto_generated_changes
    message("`api_doc.yaml`,`自動生成されたファイル(Data/API/**)`の変更が含まれています。")
  else
    warn("`api_doc.yaml`が変更されていますが、`自動生成されたファイル(Data/API/**)`に変更がありません。`Data/API/**`ファイルを変更する必要がないか確認してください。")
  end
end

PRには、このようにdangerによるコメントが付けられます。

f:id:roana0229:20190528203131p:plain
API定義と自動生成されるファイルに変更がある時のコメント
f:id:roana0229:20190528203249p:plain
API定義のみ変更がある時のコメント

Swaggerの導入は今回が初めてだったこともあり、自動生成されるだけのファイルやAPI定義だけ更新されていたり、意図しないAPI実装の変更をしてしまわないように気付くことができるようになりました。

他にも、開発ブランチの最新の状態なのにXcodeで開くと警告が出ていたり、実ディレクトリとXcode上のディレクトリがズレている経験ってありませんか?
Dangerfileはrubyで記述されているため任意のコードを実行しやすく、realm/SwiftLintの警告やvenmo/synxが適応されているかなど、他のツールを用いたチェックを行っています。

def check_synx
  `bundle exec synx app.xcodeproj >/dev/null`
  synx_diff = `git diff app.xcodeproj/project.pbxproj`
  fail("`synx`を実行すると差分が発生しました。`bundle exec synx app.xcodeproj`し、`app.xcodeproj/project.pbxproj`の差分をコミットする必要があります。") unless synx_diff.empty?
end

swiftlint.config_file = '.swiftlint.yml'
swiftlint.binary_path = 'Pods/SwiftLint/swiftlint'
swiftlint.lint_files inline_mode: true, fail_on_error: true, additional_swiftlint_args: '--no-force-exclude'
check_synx

静的なチェックであるためCI実行してからPRに適応されるまでかかる時間も短く、GithubのBranch protection rulesを設定しておくことで、簡単にブランチの最低限の品質を保つことができます。

細かいところが気になって、本質的なコードレビューから意識がズレてしまうことが過去にあったのですが、これらによりレビュー時に意識しなくてはいけないことを減らしコードに集中したレビューができるようになりました。

アプリ配布の自動化

アプリをリリースする時、一定の決まった作業がありますよね。これらはfastlaneを利用して、リリースのために必要なことは下記の3つで完了するようにしています。

  1. fastlaneでリリース準備用のコマンド実行する
  2. リリースPRのレビューを経てmasterにマージする
  3. (申請後に)Firebase CrashlyticsへdSYMをアップロードする

fastlaneでリリース準備用のコマンド実行する

リリース準備の処理はこのようになっています。

lane :release_update_version do |options|
  update_commit_to_latest(branch: options[:branch] || "develop") unless is_ci?

  update_version

  release_summary = get_done_cards_summary(github_token: ENV["Githubのトークン"])

  app_version = get_version_number(xcodeproj: "app.xcodeproj", target: "app")
  build_version = get_build_number

  git_add
  git_commit(path: "*", message: "v#{app_version} (build version: #{build_version})")
  push_to_git_remote

  pr_url = create_pull_request(
    repo: "リポジトリ",
    title: "#{app_version}(#{build_version})",
    base: "master",
    body: release_summary
  )

  sh("open #{pr_url}") unless is_ci?
end

今後CIでやることを見据えていますが、現在はローカルで実行しています。
そのため最初に実行される処理であるprivate_lane :update_commit_to_latestにより、期待しているブランチで実行されているか、ブランチを最新にして差分が発生していないかをチェックしています。

private_lane :update_commit_to_latest do |options|
  UI.user_error!("branch:ブランチ名 が必要です") unless options[:branch]

  base_branch = options[:branch]
  ensure_git_branch(branch: base_branch) rescue UI.user_error!("#{base_branch}ブランチで実行してください")
  ensure_git_status_clean rescue UI.user_error!("差分がない状態にしてください")
  sh("git pull origin #{base_branch}")
  ensure_git_status_clean rescue UI.user_error!("#{base_branch}を最新にした状態で差分が発生しました、差分がない状態にしてください")
end

次に実行されるバージョンのアップデートはprivate_lane :update_versionのようにしておけば、ビルドバージョンの更新 or アプリバージョンを更新した時にビルドバージョンを1にすることができます。

private_lane :update_version do |options|
  bump_type = options[:bump_type] || UI.select("バージョンをアップデートしますか?: ", ["only build_version", "major", "minor", "patch"])
  if bump_type == "only build_version" then
    increment_build_number(
      xcodeproj: "app.xcodeproj"
    )
  else
    increment_build_number(
      build_number: 1,
      xcodeproj: "app.xcodeproj"
    )
    increment_version_number(
      bump_type: bump_type
    )
  end
end

その後にget_done_cards_summaryというRubyスクリプトにより、完了したタスクを整形しdevelop to masterPRが作成されます。
開発タスクはGithub Projectsで管理していてるため、APIを利用して完了したタスクを吸い上げ、その内容を整形した状態をPRに記載した状態で下記のようなPRが生成されるようになっています。

f:id:roana0229:20190528203625p:plain
リリース用のPRイメージ

整形しやすいようにPRには機能追加 or 修正が判別できるラベルを付けて判別しています。

f:id:roana0229:20190528203644p:plain
PRに指定する判別用ラベル

これにより、今回のリリースに含まれる内容を手作業でまとめ直す必要もなく、PRを共有するだけで良いため、リリース前にエンジニア以外が実機でテストするタイミングにも役立てています。

PRが作成されると、CIによりdeploygatestaging環境のアプリを配布され、slackに通知されます。このタイミングで修正が必要になった場合、通常通りPRを作り、developにマージすることでリリース用PRでもCIが再度走り、アプリが配布されます。

developへのマージ前でも任意のタイミングでBitriseをGUI上から操作して、ブランチを指定しdevelopment環境のアプリを配布しています。

リリースPRのレビューを経てmasterにマージする

リリースビルドを手元で行うと時間がかかり、ターミナルをまだ終わってないかとチラ見して集中力が削がれてしまっていました。そのため、今はマージするだけでCIによりリリース作業が行われるようにしています。

lane :release_to_store do |options|
  slack(
    slack_url: ENV["Slackの通知用URL"],
    pretext: "ストアへのリリース処理を開始します(is_ci?: #{is_ci?})",
    message: "ビルド時の情報 Git Commit Hash が正しいか確認してください",
    channel: "#チャンネル"
  )

  app_version = get_version_number(xcodeproj: "app.xcodeproj", target: "app")
  build_version = get_build_number
  release_summary = get_done_cards_summary(github_token: ENV["Githubのトークン"])

  build_ios_app(
    workspace: "app.xcworkspace",
    configuration: "Release",
    scheme: "app",
    clean: true,
    export_method: "app-store",
    output_directory: "./fastlane/generated",
    output_name: "app.ipa"
  )
  upload_to_app_store(
    force: true,
    team_id: "チームID",
    app_identifier: "アプリID",
    ipa: "./fastlane/generated/app.ipa",
    skip_screenshots: true,
    skip_metadata: true,
    skip_app_version_update: true,
    run_precheck_before_submit: false
  )

  set_github_release(
    repository_name: "リポジトリ",
    api_token: ENV["Githubのトークン"],
    name: app_version,
    tag_name: "#{app_version}(#{build_version})",
    description: release_summary,
    commitish: "master",
    is_prerelease: true
  )

  slack(
    slack_url: ENV["Slackの通知用URL"],
    pretext: "Release版のストアへのアップロードが完了しました!",
    message: "GitHub Releases: https://github.com/リポジトリ/releases",
    channel: "#チャンネル"
  )
end

※環境変数FASTLANE_USER,FASTLANE_PASSWORDupload_to_app_store時に利用するアカウントを指定することができます。

リリース作業が開始されたこと・終了したことをSlackに流しておくことで、他の人もリリース作業のステータスがどうなっているのかを知ることできます。

また、リリースPR作成時にも使用したスクリプトを使い、Github Releasesにリリースの履歴を残しています。リリース履歴にPRのURLも載っている状態になるので、過去の類似した変更やどういう意図で変更が入ったのかが追いやすくなりました。

リリース作業が完了した後にWorkflowのScriptステップでdevelopをmasterに追従するようにしています。忘れそうになることが多いかつ必ずしないといけないことなので、1ステップ追加するだけでサクッとできてとても助かっています。

Firebase CrashlyticsへdSYMをアップロードする

クラッシュ解析にはFirebase Crashlyticsを利用していて、dSYMをアップロードする必要があり、これにもコマンドを用意しています。

lane :upload_dsym_to_crashlytics do |options|
  app_version = get_version_number(xcodeproj: "app.xcodeproj", target: "app")
  build_version = get_build_number
  download_dsyms(
    team_id: "チームID",
    app_identifier: "app-identifier",
    output_directory: "./fastlane/generated/",
    version: app_version,
    build_number: build_version
  )

  upload_symbols_to_crashlytics(
    dsym_path: "./fastlane/generated/app-identifier-#{app_version}-#{build_version}.dSYM.zip",
    gsp_path: "./GoogleService-Info.plist"
  )
end

初めはdSYMのダウンロードを手動でやっていたのですが、同じことを毎回繰り替えすことになってしまうので、リリースするアプリのバージョンを元にApp Store ConnectからdSYMのダウンロードとFirebase Crashlyticsへアップロードまで行っています。

本来はこれも任意のタイミングではなく、自動で行いたいのですがupload_to_app_storeのあとApp Store Connect側での処理の完了を知ることができず、ストアの申請をしたあとに実行するようにしています。

fastlaneで自動化するために気をつけていること

最初はローカルで実行していたこともあり、CI上での動作の殆どはローカルでも同じことが試せるようにfastlaneでロジックをまとめています。

一つ気を付けた方が良いなと思ったのは全てをFastfileに書こうとしないということです。最初はごりごりFastfileに書いていったのですが、いわゆる神クラスのように何でも行われる便利ツールに近づいていってしまいました。例えば、ライブラリの更新処理などbash記述のみで完結する場合はupdate_library.shという感じにするだけで十分でした。

任意のbashスクリプトを一覧から呼び出せるfastlaneのコマンドを用意したりするのは便利かもしれませんが、各処理の詳細をfastlaneが知る必要がないのであればその方がFastfileの肥大化を防ぐことができます。継続的に管理していくファイルなので、可読性を保てるように意識した結果この考えになりました。

Bitriseで設定している全体像

紹介してきたもので実際に構成されているWorkflow,Triggerはこのようになっています。

Workflow

Workflow Description
primary 静的なチェック、UnitTestの実行
primary-with-deploy-beta 静的なチェック、UnitTestの実行
deploy-betaの実行
deploy-debug development環境のアプリを配布
deploy-beta staging環境のアプリを配布
release AppStoreConnectへアップロード
merge-master-into-developの実行
merge-master-into-develop developmasterに追従する

Trigger

Pull Request
f:id:roana0229:20190528205748p:plain
f:id:roana0229:20190528205816p:plain
f:id:roana0229:20190528205830p:plain

Push
f:id:roana0229:20190528205839p:plain f:id:roana0229:20190528205954p:plain

BitriseはWorkflowのあとに特定のWorkflowを実行できる仕組みがあるため、各Workflowは細かく別けて必要な時につなげています。Triggerに指定されていないdeploy-debug, deploy-betaは任意のタイミングで手動実行しています。

また、developへのPush時にはPR時と同じprimaryを実行していますが、これはBitriseのBuild CachesがPRのTriggerではキャッシュ更新ができないため、Push時にも行っています。

おわりに

以上、Bitrise + fastlaneで自動化していることの紹介でした。

今回初めてのCI導入でしたが思い切って入れてみて、体験の良さを感じています。過去の開発では、UnitTestの準備ができていないしCIを導入しても効果なさそうだな…ということがあったのですが、今ではUnitTestの有無に関係なくCIを導入すべきだなと思っています。

また、今回紹介したようなCI環境を用意したことで、開発以外のビルドをローカルで行うことがなくなり待ち状態でPCリソースが奪われることが減ったため、結果的に自由に使うことが出来る時間が多くなりました。

他にも、ライブラリの自動更新検知やその時のキャッシュをローカルでも利用できるようにしたりなど、まだやれていないことが今後も新しく出てきたりすると思うので、自動化出来ることを増やして行こうと思います。

データ分析勉強会〜SQL活用〜 を開催しました #OthloTech

f:id:boscoworks:20190512165525j:plain

こんにちは。id:boscoworks です。

2018年末まで転職ナビのエンジニアリングマネージャを勤めていましたが、年明けからは全社横断でエンジニア採用を推進する業務をしています。

リブセンスでは、大学や専門学校に在籍中の方を対象に、プロダクトの魅力やエンジニア文化の特徴を知ってもらうための取り組みを推進しています。

名古屋近辺で活動をされている、クリエイティブな学生たちが集まるコミュニティ "OthloTech" さんとデータ分析をテーマにしたコラボイベントを開催しましたので、そのようすをレポートします。

OthloTech とは

f:id:boscoworks:20190512140155j:plain

名古屋で専門知識を有するクリエイティブな学生が、横のつながりを生み出し、互いを刺激しあうことで楽しく技術を磨くことが出来るコミュニティだそうです。

月に1度程度勉強会・ハンズオンを開催しており、IT・Web系の企業とコラボして知識を共有したり、企業との交流をしたりしています。今までの実績を見ると、名古屋に限らず東京に拠点をおく有名なWeb企業も出資して勉強会を開催しています。

今回は幸運にも弊社人事とOthloTech運営の学生さんが面識があったこともあり、勉強会開催が実現しました。

データ分析勉強会〜SQL活用〜

othlotech.connpass.com

リブセンスらしいエンジニアリングで、かつ学生さんがなかなか触れることのないような知識の共有をテーマに据え、弊社からIESHILのチーフプロダクトマネージャー稲垣と、同じくIESHILのデータエンジニア小沼が登壇しました。

非エンジニアのSQL活用が加速させる事業成長

www.slideshare.net

データの利活用でプロダクトの成長を促進する手法は、昨今のWebプロダクト開発ではよく見かけるケースとなりましたが、プロダクトマネージャー(Webディレクター)が実際にどのようなデータ利活用を開発現場で行っていて、具体的なメリットはどんなものか、社外からはなかなか分かりづらいものです。

リブセンスに入社するまでSQLに触れたことすらなかった稲垣が、社内でSQLのトレーニングを実践し、開発現場で体験した事例を紹介しました。

f:id:boscoworks:20190521161608j:plain

リブセンスではビジネス職でもSQLを駆使しています。データの利活用の背景には、分析を気軽に行える環境の整備が社内に整っていることや、社内のエンジニアがSQLに関する質問に答えてくれるような距離の近さがあります。また、社内にはSQL100本ノックというドリルがあり、クリアすることでスキルアップをしているプロダクトマネージャが現場で活躍しています。

大量の物件情報を短時間で処理するハック

speakerdeck.com

データエンジニアという職種は、近年注目が集まりつつも職責があいまいです。膨大なデータをプロダクトの価値に変換するためには、データアナリストや機械学習基盤のプログラムが処理をしやすいかたちにデータを加工し保存することが必要不可欠です。

データ分析とWebのフロントエンドで、それぞれ適切なデータベースの選定はどうすればよいでしょうか。行指向DBと列指向DBのそれぞれについてご紹介しました。

ところで肝心の大量の物件情報を短時間で処理するハックは何かというと・・・答えはスライドをご覧ください。

ハンズオン

f:id:boscoworks:20190521161734p:plain

IESHILが取り扱う不動産情報のサンプルデータを用いて機械学習を実践するハンズオンを行いました。

今回は特別に Arm Treasure Data 様のご厚意により、イベント開催時のみ特別にWorker数を増やして、大人数が同時にクエリ実行しても大丈夫なようにしていただきました。ありがとうございます。

サンプルクエリを実行していきながら実際に機械学習を行い、簡単なクエリでは精度が悪くうまくいかないこと、そしてそれを解決するアプローチについて実践しました。

懇親会

f:id:boscoworks:20190512165938j:plain

最後はピザを囲んで懇親会。目標の「横のつながりを生み出し技術を磨く」ことはできたのでしょうか・・・。

終わりに

f:id:boscoworks:20190512163334j:plain

勉強会を通じて学生さんたちとも交流できましたし、リブセンスの文化や技術について知ってもらうことが出来ました。

OthloTech 運営の皆さん、そして今回の勉強会にご参加頂いた皆さん、どうもありがとうございました。

リブセンスでは、勉強会やイベントを一緒に開催してくださる技術系の学生団体を募集しています。イベントや懇親会の運営費用をご支援させていただける他、エンジニアや人事などの登壇もご相談できます。

お問い合わせはこちらからお願いします。

RubyKaigi2019へ行ってきた(そしてRubyGems.orgで2FAをしよう)

賃貸物件検索サービス、DOOR賃貸の開発・運営をしている岩田です。 個人的にプログラミングの母語(?)がRubyなので、普段からよく使っております。DOOR賃貸もRailsで作られています。

また、リブセンスはRubyをほとんどの事業部で活用しています。Redis Gem を Cluster Mode に対応させる Pull Request を出したりもしています。 made.livesense.co.jp

そんなリブセンスには、カンファレンス参加の費用を会社が全額補助する制度があります。 そんなこともあって先週開催された RubyKaigi2019 に行ってきました。 rubykaigi.org

この記事では、今回の会議中に何度かRubyGems.orgで2段階認証をしてほしい、というアナウンスがありましたので、その手順をまとめてみました。 また私は2度めの参加なのですが、来年に向けて参加のコツのようなものをまとめておきました。

RubyGems.orgで2FAをしよう

今回は何度かRubygems.orgで2FA(2段階認証)をしてほしい(もしくはパスワードを使いまわしせず強いものにする)ということが告知されました。

これはbootstrap-sassのgemでアカウントの乗っ取りが発生し、任意のコードを実行できるように書き換えられてしまったことなどから、認証方式を追加したようです。

github.com

私も社内で実施しているランダムランチ(シャッフルランチ)の組み合わせを作るためのgemなどをrubygemsで公開しています。これはしっかり設定せねば、と思い実際にやってみました。

rubygems.org

2FAはRubyGems.orgのプロフィール画面から設定が可能です(画面上では MULTIFACTOR AUTHENTICATION 「多要素認証」となっていますね)

2段階認証していない状態
2段階認証していない状態

手順は Setting up multi-factor authentication - RubyGems Guides に詳しくありますが、軽くまとめておくと手順としては以下のようになります。

  1. 「Register a new device」を押して、画面に現れるQRコードをお使いのアプリなどで読み込む
  2. アプリで表示される数値を入力する
  3. 次の画面で表示される Recovery codes を安全な場所に保管する(デバイスをなくした時などに必要になります)

設定が完了した状態
設定が完了した状態

gem の公開・更新はたいていCLIから行うと思いますが、そのときに2FA(MFA)を使用する手順はUsing multifactor authentication in command line - RubyGems Guidesにあります。 2FA(MFA)を設定した直後の状態では、ブラウザからのアクセスのみでOTPが必要なようですが、これを「UI and API」にすることでCLIの操作でもOTPが必要になります。

2FA(MFA)をCLIからも使用する
2FA(MFA)をCLIからも使用する

私も簡単なbugfixをしたものを公開し直してみました。

まずCLI上でサインインします。ここでOTPを入力することになります。

$ gem signin
Enter your RubyGems.org credentials.
Don't have an account yet? Create one at https://rubygems.org/sign_up
   Email:   ganta.viii@gmail.com
Password:

You have enabled multi-factor authentication. Please enter OTP code.
Code:   963852
Signed in.

新しいバージョンをbuildします。

$ gem build group_mixer.gemspec
  Successfully built RubyGem
  Name: group_mixer
  Version: 0.5.3
  File: group_mixer-0.5.3.gem

その後、rubygems.org に push しました。ここでもOTPが必要です。

$ gem push group_mixer-0.5.3.gem
Pushing gem to https://rubygems.org...
You have enabled multi-factor authentication. Please enter OTP code.
Code:   573830
Successfully registered gem: group_mixer (0.5.3)

以上で、バージョンアップは完了です。日々、セキュリティを気にしながら開発していきたいですね。

カンファレンスに参加するときのまとめ

  1. チケットは決意を持って買う
  2. 前後の日程を含めたスケジュールを立てる
  3. 会場、宿泊先、その他(川など)を含めた現地の交通は入念にチェック
  4. 安全に気をつけて楽しむ!

チケットを買う

今回のRubyKaigiでは、去年の年末ごろに販売が開始されました。

rubykaigi.doorkeeper.jp

参加を決めているなら、お値段がかなり値引きされているので SuperEarlyBird がおすすめです。 ただしすぐになくなってしまうので、絶対に行く、という強い決意を持って、ポチッと購入しましょう。 会社が全額補助する制度があると、安心して決意することができますね。

前後の日程を含めたスケジュール

チケットを買って行くことを決意したら、次は何でいつ行くかを決めましょう。

注意点は、スケジュールの3日間だけではなく、前日・後日にもイベントがあることです。 rubykaigi.org

また、カンファレンスは観光地で開催されることもあります。 ですので、可能であれば少し長めにホテルを取っておくと、いろんなところに行けるので良いですね。

今回は福岡で、私は後日福岡城跡の大濠公園周辺でゆっくりしておりました。 f:id:iwtn:20190425132208j:plain

会場、宿泊先、その他(川など)を含めた現地の交通をチェック

朝に会場に行く手段、1日が終わった後のパーティーなどに移動する際の交通手段などは、できれば事前に調べて置くと当日楽です。 RubyKaigi2019が開催された博多では、地下鉄・バス・レンタル自転車など多様な交通手段がありました。

ただ、GoogleMapなどで当日でも交通手段を調べることはできますが、多少は余裕を持っていったほうがギリギリにならず、心理的な余裕も確保できます。 私は一度バス乗り場がわからず、迷ってしまいました。

更に、RubyKaigi2019では、様々なスポンサーがあり、朝食や朝・帰宅時のバスも提供されていました。 rubykaigi.org これらも考慮に入れた日程を予め立てておくと、スムーズに移動できて快適です。

楽しむ(安全に気をつけて)

カンファレンスそのものの内容はすべて興味深いものばかりでしたが、セッション以外でも楽しめる要素がいっぱいありました。

ブース

スタンプを集めながら、そのブースの人とサービスについて説明を受けたり会話したりノベルティをもらったりします。 いろんなゲームをするブースもありました。みなさん、いろんな工夫をされていましたね。

なお、リブセンスでは転職ドラフトがブースを出しており、缶バッジとステッカーを配布しておりました。 f:id:iwtn:20190425111014j:plain f:id:iwtn:20190425111155j:plain

パーティー

カンファレンスでは、メインの他のイベントがたくさんあります。

RubyKaigi2019では、以下のように様々なパーティーが開催されておりました。

rubykaigi.org

私も参加しておりましたが、海外からいらしたRubyistや前にお世話になったRubyistと話をしたりして、楽しい時間を過ごせました。

そのほか

カラオケなども開催されているようです。

#rubykaraoke hashtag on Twitter

リブセンスのメンバーも参加していたようです。楽しそうですね。

また、最終日には恒例の川沿いでの集まりなどもありました。夜の中洲は綺麗でした。 f:id:iwtn:20190425135156j:plain

参加しての感想

リブセンスでは9人がRubyKaigi2019に参加し、以下のような感想をもらっています。

高畠さん

人生2回目のRubyKaigi参加で、前回の会議中に仕様が決まった機能が実装されていたりと、文脈が理解できてきた面白さがあった。型を期待しつつも、コードの中には入れない方針をどう達成するか、スパンの長い流れの中での意思決定の難しさや面白さを感じた。

山内さん

「お前Ruby書かないんじゃないの?」って思われてるかもしれない 実際ほとんど書かないんだけど とはいえ我々が運用してるアプリケーションはRubyでできているので安定に動かしたり速く動かしたりするにはRubyのことを知らないといけない

内山さん

僕のRubyKaigi初参加は2015年で、MatzがRuby3の展望を掲げたのを覚えています。今回は、そのRuby3に向けて型/速度/並行性といったそれぞれの面で、登壇者の方たちが具体的な"進捗" をたくさん出されていて「はーすごい」と思わされっぱなしでした。

望月さん

今回初めてのRubykaigiに出席しました。Ruby3に向けての展望や進捗をRubyコミッターの方々から直に聞けてすごく刺激になった。 その一方で言っていることのほとんどが概要的にしか理解できず、もっと事前にRubyについての学習をしておけばより楽しめたと思う。

荒川さん

Ruby3に向けて、型やGuildなどのチャレンジングな取り組みの内容を聞けて大変刺激になったし、Ruby3のリリースが待ち遠しくなった。Rubyコミュニティ自体の盛り上がりも今回初めて直に体感でき、RubyKaigiを通じてRubyをより好きになれました。

池田さん

みんな気になっているであろうruby3の話しが聞けてよかった。型とGuildという大きいプロジェクトが進行していて、とても楽しみである。互換性の話など、広く使われるツールならではの問題などもあり、面白かった。後方互換を切ってまで実装したい機能なのかなど、コミッター間の議論に面白みを感じた。

おわりに

Rubyにおいてgemは強力の機能の一つですので、安全に公開・運用していきたいですね。gemを公開していてまだ2FAをやっていない人は早めに対応しておきましょう。

そして、次回のRubyKaigi2019は長野県の松本になりました!私の地元なので是非参加したいと思っております。松本にも市街の中心部を流れる女鳥羽川がありますので、楽しくなりそうですね!

try!Swift Tokyo2019にスタッフとして参加してきました!

こんにちは。ネイティブアプリグループの吉田(@66nylon_y)です。毎日楽しくiOSアプリの開発をしています。 先日行われましたtry!SwiftTokyo2019で当日スタッフとして参加してきたので、そのレポートを書きたいと思います。

カンファレンスにスタッフとして参加するのは今回が初めてだったのですが、 勇気を出して一歩踏み出してよかった!と心から思える貴重な経験をたくさんすることができました。 f:id:roku_y:20190415170603j:plain


try!Swiftとは??

www.tryswift.co

try!SwiftはSwift言語での開発における応用事例を発表しあう国際的なカンファレンスです。 過去にはアメリカ・ドイツ・インドなど世界中の国々で開催されてきたようです。 今回私は3月21・22・23日に渋谷で行われたtry!SwiftTokyo2019にスタッフとして参加してきました。

f:id:roku_y:20190415170359j:plain
これまでのtry!Swift
参加人数はなんと過去最多の900名だったようです。 基本的には1セッションのみの進行で、セッションの他にはスポンサーブースやAsk the Speakerのコーナー、畳の上でセッションを楽しめるコーナーなどがありました。 セッションには日本の方だけでなく海外の方も多数登壇されておりますが、 日本語と英語の同時通訳も行われているので英語に苦手意識があっても安心して参加することができます。 今回はなんとAppleで働いている方が2名も登壇されており、Appleで働いている方の話を生で聞くという貴重な経験をすることができました。


今回スタッフとして参加しようと思った背景

私がtry!Swiftに出会ったのはちょうど1年前のことでした。 Swift愛好会というSwiftのコミュニティがあり、当時通い始めだった私はそこでtry!Swiftの参加報告会を聞き、初めてその存在を知りました。 Swift愛好会の主催者の中にはtry!Swiftを主催されている方が多くいらっしゃるため、try!Swift開催後はSwift愛好会で振り返りのLTをやるのが恒例になっています。 このLTでtry!Swiftに少し興味を持ち、懇親会で詳しく聞いてみようとしたところ、 予想外に「来年はスタッフをやってみないか」と誘ってもらったのが私のスタッフをやるかどうかの葛藤の始まりでした。

時が経ち、今年2月のSwift愛好会で当日スタッフ募集の呼びかけがありました。 この頃の私は勉強会やカンファレンスで登壇してみたいという気持ちはありつつも、エンジニア歴も浅いためいまいち自分の力に自信が持てず、一歩踏み出せない状態にありました。 しかし、もしかしたらカンファレンスにスタッフとして参加することで知り合いが増え、壇上で発表をする心理的ハードルも、 発表後にフィードバックをもらいにいく物理的ハードルも低くなるのではないかと考えました。 この時点でもかなり迷いと自信のなさがあったのですが、 迷っていることをツイートしたところ、try!SwiftのOrganizerの方が何人もいいね!を押してくれました。 みなさんに背中を押してもらった気がして、スタッフへの応募をしっかりと決意することができました。

そして3月になり、私のもとにスタッフ当選のメールが届きました。 こうして私はtry!Swiftにスタッフとして参加することになりました。


スタッフとして私がやったこと

f:id:roku_y:20190416170011j:plain
セッション会場の様子

私は主に受付を担当しました。受付のメインの仕事は参加者のチェックイン作業とノベルティの配布です。 特に初日の朝は参加者の方が一気にいらっしゃるので、チェックイン作業もその後の導線の案内も混雑しており、スタッフ間で連携を取るのが大変でした。 しかし、これから始まるカンファレンスにワクワクしている参加者の方と、たくさん触れ合うことができてとても楽しかったです。

また、受付には質問にくる方も多く、チェックイン作業が落ち着いた後は参加者の方のQ&A対応をしました。 後述にもありますが、try!Swiftは海外からの参加者も多いので、 お手洗いの場所がわからない、Tシャツのサイズを交換したい、ワークショップの申し込みはどうしたら良いかなど、様々な質問が英語で寄せられました。 英語に自信のない私でしたが、ジェスチャーを交えながら、知っている英語を絞り出し会話することで伝わることも多く、とても嬉しかったです。 また、海外のSpeakerの方からスタッフにお土産をもらうこともあり、これにはとても感激しました! 受付業務の他には、後片付けや同時通訳機の整備などの裏方作業も手伝い、セッションも一部聞くことができました。


スタッフをして良かったと感じた点

ここからはtry!Swiftのスタッフをやってみて良かった!と思った点について3点ほど紹介したいと思います。

英語への苦手意識が弱まる・海外カンファレンスへの意欲が強まる

f:id:roku_y:20190415170058j:plain try!Swiftは国際的なカンファレンスです。そのため、海外からの参加者がとても多いです。 私は受付の担当をしており参加者の方とお話しする機会が特に多かったため、 「英語でコミュニケーションを取らなければならない」場面に度々遭遇しました。 英語に苦手意識のあった私ですが、絞り出して話せば意外と伝わることが多く、ちょっとした自信につながりました。 英語に苦手意識があるから避ける、のではなく、まずは伝えてみようという勇気が大切なんですね!

加えて、「意外と英語コミュニケーションいけるかも!」という気持ちから海外のカンファレンスにも参加してみたいという思いが強くなりました。 try!Swiftを通じて海外への窓が開いたような気がします。 とはいえ上手く話せなくてたくさん悔しい思いをしたので、きちんと英語を勉強しようと思います。これもまた貴重な学びですね、、

たくさんの仲間が増える・勉強会参加の心理的障壁が減る

今まで色々なカンファレンスに参加しましたが、その度に初対面の方に話しかけるのはとても勇気がいるものでした。 しかし、今回スタッフとして参加することで、他のスタッフと連携したり質問するために話しかけたりするなど、自然と多くの人と接する場が増えて、気付いたら仲良くなっていた、ということがありました。 カンファレンスを作る体験を通じて、スタッフの間でより思い入れのある深い絆が生まれたような気がします。 (余談ですが、スタッフ仲間との打ち上げでtry!Swiftについて語りながら嗜むお酒は最高に美味しかったです!)

また、顔見知りが増えたので他の行ったことがない勉強会にも顔を出しやすくなったなと感じます。 実際他の勉強会のお誘いもあり、こういった場で次に繋がる新しいコミュニティの存在を知るのはとても素敵なことだと感じました。

コミュニティの一部であることを感じることができる・コミュニティの活性化について考えることができる

f:id:roku_y:20190416165723j:plain 普段Swiftを使って開発をしているのでSwiftコミュニティの中にいるということは当たり前のことなのですが、開発をしていると案外気づきにくいものです。 今回Swiftのカンファレンスを運営するという立場に立つことで、自分もSwiftコミュニティの一部であると感じ、 さらに自分でもSwiftコミュニティの活性化に携わることができるということに気づくことができました。 例えば、自分は世界中のiOS Developerのどなたかが書いたコードから日々啓蒙されて成長し続けており、 逆に私が開発したものはいつか誰かの助けになりSwiftコミュニティの活性化に一役買えるのではないかという具合です。 日々の開発業務やアウトプットはコミュニティの活性化に繋がるものであるということを再確認し、自分たちの力で次の世代へつなぐことができるんだなと思いました。

2日目の最後のセッションでMayukoさんがApple開発者コミュニティを次に繋いでいくということについてとても良い話をしてくださったようです。 残念ながら聞くことができなかったので、後日YouTubeに上がるtry!Swiftのセッションの動画を楽しみに待ちたいと思います。



参加してみて思うこと

f:id:roku_y:20190416170220j:plain 今回スタッフとして参加することで仲間が増え、仲間との会話を通じて学びを得る機会がたくさん増えました。 それにより、2月の頃には全く思い描けなかったカンファレンス・勉強会での登壇も、挑戦するイメージを持てるようになりました。 今後Swift愛好会での登壇枠にも挑戦するとともに、今までよりも積極的なアウトプット活動に励んでいけそうです。

また、スタッフ挑戦前は社内で「成長している、できるようになっている」と評価をもらってはいたものの「本当にそうか?」と疑ってしまい、いまいち自信に繋がっていませんでした。 しかし社外の方とも広く交流することで、自分は思ったよりも成長できているということが客観的に見えるようになりました。 英語セッションであっても大枠を理解できる自分がいて、自らの成長に確証が得られ自信がつきました。

自分に自信がついたことで、日々の開発業務に対するモチベーションも変わりました。 今まではわからないことが多く焦ってばかりでしたが、今では新しい知見を学ぶのが本当に楽しいです。

さらに、海外の方との交流を通じて海外カンファレンスへの意欲も高まり、リブセンスの海外渡航支援制度を活用して、来年のWWDCにぜひ参加したいなと思いました。

今回私は初めてカンファレンススタッフとして参加しましたが、 try!Swiftではスタッフとしての参加が初めてでも事前MTGがあり、安心して当日に挑むことができました。 もしスタッフに興味を持っていただけたなら私が普段参加しているSwift愛好会に顔を出してみるのも良いと思います!


まとめ

今回の活動は、スタッフに応募する前の段階から勇気と決意の連続でした。 スタッフの仕事を終えて今思うことは、勇気を出してスタッフに応募して本当に良かったということです。 勇気を振り絞って得られたものは、かけがえのない経験や知見、そしてSwiftコミュニティの仲間でした。 今回スタッフに誘っていただいた方、一緒にスタッフを頑張った方、そしてtry!Swiftに参加された方々にとても感謝しています。

とにかく多くの刺激を受けてモチベーションが爆上げされたので、日々の開発・勉強にますます励みたいと思います!

f:id:roku_y:20190415170831j:plain
ちなみに後日反省会がありました。 反省会ではみんなでKPTを共有し、次回に向けて課題解決のための話し合いを行い、最後は来年のtry!Swiftに向けて気合の補充を行いました。

解決できる課題が色々と見つかったので、今年の反省を活かして、来年はもっと素敵なカンファレンスを作り、Swiftコミュニティを盛り上げる力になれればと思いました!

開発合宿に行きます

4/6(1日目)

9:46 待ち合わせ場所に向かう総武線快速から

こんにちは。マッハバイトの内山です。

今から4/6(土)、4/7(日)の二日間かけて開催される、リブセンスの開発合宿に行こうとしています。目的地は、千葉県香取市のこちらです。

www.thefarm.jp

リブセンスでは、だいたい半年に一回程度、開発合宿を開催しています。いつも、エンジニアブログに様子を載せたいねって話になるのですが、個人が各々の取組みをする開発合宿を後から誰かが記事にまとめるのって難しいねとなりがちでもあり、今回は初の試みとして、リアルタイムに更新してみようと思います。

10:00 東京駅 八重洲口 バス停留所
f:id:livesense-blog:20190406101027j:plain
f:id:livesense-blog:20190406101031j:plain

集合時間の待ち合わせ場所様子。なぜか登山用ザックの人が3人。ここからバスに乗ります。

10:33 高速バスの車内から

無事13人全員がバスに搭乗。最後に到着したのは水を吐くフグの人で、着くなり「集合時間って何時でしたか?」って言っていた。(10時ですw)

f:id:livesense-blog:20190406102241j:image

11:20 東関東自動車道を走るバス車内から

バス内ではバラバラに座ることになったので、各自好きなことをしているようです。寝る人、スマホする人、PCを触る人、本を読む人、隣のお客さんが連れてきた犬が気になる人、飲む人など。

f:id:livesense-blog:20190406113259j:image

12:59 THE FARM施設内から

今回の宿泊施設に着きました!

f:id:livesense-blog:20190406124139j:image

施設内には宿泊施設のほかカフェや温泉などが併設されていて、いい感じです。いい感じに開発以外のことをしたくなる誘惑がたくさんあります。

動物もいるし、カメラクラスタは高まってしまっている。

f:id:livesense-blog:20190406125939j:plain
f:id:livesense-blog:20190406124257j:plain
13:41 昼ごはんを食べた

併設のカフェでピザやポテトなどを購入し、借りている会議室棟で食事をとりました。

f:id:livesense-blog:20190406133847j:plain
f:id:livesense-blog:20190406134016j:plain
15:19「 開発 with カフェイン」の様子

カフェインによって開発をブーストさせる人がいる。

f:id:livesense-blog:20190406152510j:plain
f:id:livesense-blog:20190406152515j:plain
f:id:livesense-blog:20190406152503j:plain
f:id:livesense-blog:20190406152431j:plain
16:17 「開発 with アルコール」の様子

アルコールによって(酔って?)開発をブーストさせる人もいる。

f:id:livesense-blog:20190406154553j:plain
f:id:livesense-blog:20190406154559j:plain
f:id:livesense-blog:20190406154606j:plain
f:id:livesense-blog:20190406154603j:plain
17:50 開発をしていないときの様子

コーヒーを淹れたり、ポケモンGoをやったり、バドミントンをしたり、格ゲーをやったりしている。えっ、そのアーケードコントローラー持ってきたんですか...?

f:id:livesense-blog:20190406175234j:plain
f:id:livesense-blog:20190406175226j:plain
f:id:livesense-blog:20190406175310j:plain
f:id:livesense-blog:20190406175218j:plain
 19:45 夕飯を食べた(BBQ)

夕飯は会議室棟のすぐ横でBBQ。だいぶ肌寒くなってきたけど、炭火で暖を取りながら、肉をたらふく食べた。

f:id:livesense-blog:20190406194139j:plain
f:id:livesense-blog:20190406194144j:plain
 22:26 やっていることの共有をして、お風呂に入った

食事の後、各自のやっていることや状況を共有しました。

  • Nuxtでなんかやってる人
  • Reactでなんかやってる人
  • Raspberry Piでなんかやってる人
  • GoでWeb API作ってる人
  • React Nativeでなんかやってる人
  • Juliaでなんかやってる人
  • Chrome拡張を作ってる人
  • LINE Bot作ってる人
  • サーバサイドSwiftでなんかやってる人
  • Rの型検査を作ってる人

...など、かなりやってる内容は様々でした!(強いていうとJavaScript多めかな)

その後は温泉に入りました♨

23:28 夜は更けていく

f:id:livesense-blog:20190406233247p:plain

今日の更新はたぶんここまでです。おやすみなさい!

4/7(2日目)

9:40 朝食を食べました
f:id:livesense-blog:20190407094447j:plain
f:id:livesense-blog:20190407094500j:plain

ビュッフェ形式で、農園で採れたと思しき野菜を使ったメニューなどがおいしかったです。気候的にもテラス席で食べるのがちょうどよい感じでした。

午前中は各自、部屋で開発に勤しみます。

13:03 最後の共有回直前の様子
f:id:livesense-blog:20190407130027j:plain
f:id:livesense-blog:20190407130103j:plain

ヘッドフォンをして最後の追い込みをかける人。朝から飲んで最後の追い込みをかける人。

f:id:livesense-blog:20190407130003j:plain
f:id:livesense-blog:20190407130010j:plain

外で開発する人。なぜかPCを見ながら外を歩く人。

14:12 最終成果報告完了
f:id:livesense-blog:20190407141216j:plain
f:id:livesense-blog:20190407141206j:plain
f:id:livesense-blog:20190407141140j:plain
f:id:livesense-blog:20190407141155j:plain

最後は青空の下で、成果報告会をしました。進捗が出た人もいれば、NWトラブルに泣いた人もいたようです。

もともと実装していたものとはいえ、作ったChrome拡張を合宿中にChrome Web Storeで公開するところまでいった人も。

chrome.google.com

進捗が出た人も出なかった人も、皆が口々に「いい開発合宿だった」と言っていたのが印象的でした!

17:48 帰宅しました

高速バスで東京駅まで向かい、そこから各々帰宅しました。今回の開発合宿も楽しかったですね。お疲れ様でした!

以上、リアルタイム更新でした。

転生会議を支える技術「フロント・サーバー編」〜使ってみたいをふんだんに盛り込んでみた〜

はじめに

この記事は転生会議を支える技術「インフラ編」〜サイト爆速化への道〜の後編になります。
転職会議の2年ぶりのエイプリルフール企画として、「転生」をテーマに転職会議のエンジニアがそれぞれやりたいことを盛り込んだ内容になっています。

無料で豪華商品をゲット!今すぐ転生しろ!

このブログでは、前編と同じく「技術的挑戦をしながら転職会議システムのレンダリング最速を目指す。」について、詳しくご紹介します。

転生会議の技術要素

転生会議は次のような技術に支えられています.

  • GKE
  • Firebase
  • golang
  • Elixir
  • Phoenix
  • Vue
  • Nuxt
  • fastly
  • webp
  • nginx
  • Route 53

今回はフロント・サーバー編です! フロントを担当した山下と、APIを担当した中村、OGP画像生成を担当したse-yaの3人でお送りします。


やったこと1 Nuxt.js ✕ Atomic Design

フロントを担当した山下です。普段はサーバーサイドを中心に書いているのですが、Nuxtを使ってみたいとダダをこね、担当させていただきました。
今回は、本企画で使った技術と、実装する際に意識した点に関して書いていこうと思います。 今回は、Nuxt.jsを採用しAtomc Designを意識して実装を行いました。
どちらも業務で使ったことはなかったので、手探りの状態で使ってみたのですが、結果としてい非常に使いやすかったなという印象を受けたので、共有したいと思います。

Nuxt.js

Nuxt.jsとは、UIレンダリングをすることに焦点をあてたVue.jsアプリケーションを構築するためのフレームワークです。
公式サイトも非常に充実しており、create-nuxt-appという対話式でNuxt.jsのプロジェクトを構築するツールも提供されているので、難しい設定などはしなくても比較的容易にアプリケーション開発を始められるのではないかと思います。

ja.nuxtjs.org

また、デフォルトで状態管理を扱うvuexというライブラリがインストールされているので、ページ間をまたいだ状態管理が必要なアプリケーションも開発しやすいです。
本企画では、各ページでユーザーが選択した情報を保持しながらページ遷移する必要があったため、vuexを使ってみました。
Vuexでは、アプリケーションの状態をグローバルに管理することで、ページをまたいだ状態管理を比較的容易に行うことが出来ます。

app/store/answers.js

// 保持するデータを定義
export const state = () => ({
  answer: {
    story: null,
    avatar: null,
    body: null
  }
})

// 保持したデータに対してのgetter関数を定義
export const getters = {
  answer: state => state.answer
}
app/components/answer

import { mapGetters } from 'vuex'

export default {
  computed: {
    // 参照したいコンポーネント内で、定義したgetter関数を定義
    ...mapGetters('answers', ['answer'])
  },
  created() {
    // 定義したgetter関数でアプリケーションの状態を取得
    console.log(this.answer)
  }
}

また、外部との通信処理などをコンポーネントから切り出して書くことによって、コンポーネントではUIレンダリングのみに集中できるようになり、コードをシンプルに保つことが出来ます。

app/store/answers

export const actions = {
  async postAnswer() {
    // 通信処理を切り出して記述
    await this.$axios.post(‘https://hogehoge.com/api’, { params })
  }
}
app/components/form

import { mapActions } from 'vuex'

export default {
  methods: {
    post() {
      // 切り出した関数を実行
      this.postAnswer()
    },
    // コンポーネント内で、実行したい関数を定義
    …mapActions(‘answers’, [‘postAnswer’])
  }
}

vuexを使うことによって、処理を責務に合わせて分割することができ、アプリケーションをシンプルに保つことができます。
また、フロントにおける状態管理をUIと切り離して実装することができるので、デザイナーとエンジニアとの分担も比較的用意になります。

Atomic Design

Atomic Designとは、UI要素を最小の単位で分解し、一定のルールのもとに組合わせていくことによってページを構成していくデザイン手法です(と自分は解釈しています。)
実装する上で、以下を参考にしました。

design.dena.com

apbcss.com

UI要素を以下のようなルールで分割することによって、再利用性が高く変更に強いコンポーネントを作成していきます。

  • Atoms・・・UIの最小要素(ラベルボタンやフォームのパーツなど)
  • Molecules・・・複数のAtomsによって構成される要素
  • Organisms・・・データの取得などのドメインロジックを含んだ要素の集合
  • Templates・・・ページの枠組み
  • Pages・・・Templatesに必要な情報を流し込んだ、最終的なアウトプット

今回はNuxt.jsでAtomic Designを採用するために、以下のようなディレクトリ構成にしました。

- /app
  - components
    - atoms
    - molecules
    - organisms
  - layouts(Templatesにあたるディレクトリ)
  - pages(Pagesにあたるディレクトリ)

基本的に、organismsのコンポーネントで表示するのに必要なコンテンツを取得し、moleculesがatomsに情報を受け渡すという方針で作成しました。

app/components/organisms
<template lang="pug">
  .stories-content
    // データの子コンポーネントへの受け渡し
    Stories(:stories="stories")
</template>

<script>
// データの取得
import sample-data from '~/assets/jsons/sample-data.json'
import Stories from '~/components/molecules/Stories

export default {
  components: {
    Stories
  },
  data() {
    return {
      stories: sample-data
    }
  }
}
</script>
app/components/molecules/Stories
<template lang="pug">
  .stories
    // データの子コンポーネントへの受け渡し
    Story(v-for="story in stories" :key="story.name" :story="story")
</template>
app/components/molecules/Story
<template lang="pug">
  // 親コンポーネントから受け取った値を表示
  .story
    button
      img.story-icon(:src="require(`~/assets/images/${story.label}/stories/icon.png`)")
      span.name
        | {{ story.name }}
</template>

Atomic Designの手法を用いることによって、再利用性が高く変更しやすいコンポーネントを作れたかと思います。
また、それぞれのコンポーネントに対して責務が明確になるので、保守のしやすいアプリケーションを構築することが出きるのではないかと思います。

まとめ

NuxtもAtomic Designも、業務では初めて扱ったのですが非常に扱いやすく、メリットも多い印象を受けました。
何より、責務を分割することによってコードをシンプルに保つことが出き、書いていて気持ちがいいです!!


やったこと2 Elixir + Phoenixによるサーバーサイド実装

APIを担当した中村です。
普段はサーバサイド中心にやっており、Ruby on Railsや最近ではGolangを書くことが多いのですが今回初めてElixir + Phoenixという組み合わせに挑戦してみました。

APIについて

クライアントから受け取ったjsonをFirebaseのRealtime Databaseにpostするという簡単なREST APIです。
Nuxt + Firebaseでサーバーレスな構成が可能なのになぜAPIを間に挟むのか...? とお気付きの方もいることでしょう。
正直なところ今回このAPI自体は全く必要ありません。
今回は技術的挑戦+オーバーエンジニアリングがテーマということで、業務時間に堂々と遊んでみました(・∀・)

Elixirを書いてみた理由

新しいプログラミングパラダイムの言語に触れてみたかったからです。
今までJavaやRubyといったオブジェクト指向言語をメインで書いてきており、関数型言語をお仕事で書く機会はありませんでした。
関数型言語の中でも、ElixirはRubyとシンタックスが近く、Railsのような強力なMVCフレームワークのPhoenixがあるため、限られた時間で簡単に実装するのに向いていました。

実装にあたって

RailsでいうところのScaffoldのような機能があり、秒速でアプリケーションの初期構築が完了しました。
またライブラリ管理もmix.exsやmix.lockといったGemfileやGemfile.lockに近いものがあり、ほぼRailsに近い感覚で開発できました。
以下はコントローラのcreateメソッドです。
かなりRubyに近い見た目ですが、パターンマッチングやガード節を使ってみるなど関数型の特徴を取り入れてみました。

 def create(conn, params) do
    try do
      result = save_firebase(params)
      case result do
        {:ok, response} ->
          conn
          |> put_status(200)
          |> render("post.json", response: response.body)
        {:error, error} ->
          conn
          |> put_status(error.status_code)
          |> render("error.json", error: Poison.encode! error)
      end
    rescue
      exception ->
        Sentry.capture_exception(exception, [stacktrace: __STACKTRACE__])
        conn
        |> put_status(500)
        |> render("error.json", error: Poison.encode! exception)
    end
  end

苦労した点

今回は特に大きな苦労はなかったのですが...
FirebaseにはElixir用のSDKが存在しないため、Realtime Databaseの生成するREST APIを利用しました。
Firebaseのレスポンスを特に加工をするわけでもなく、そのままクライアントに横流しするコードを書いている時は(私は今、何をやっているんだろう...)という気持ちになりました。
また、転職会議で最速を目指すプロジェクトでありながら、Firebaseにpostしてレスポンスを待つ部分がどうしても遅く改善の余地が残りました。
今回はクライアントがAPIのレスポンスを待たないという形をとりましたが、休みの日にでもこっそり非同期でpostする処理を実装してみたいと思います。

まとめ

普段Railsを書く感覚で、短時間でアプリケーションを仕上げることができ楽しく開発ができました!
今回はRubyと比較したElixirのメリットを感じるところまではいきませんでしたが、要件に当てはまるプロダクトを作ることがあったら導入しやすいElixir + Phoenixの組み合わせを一つの選択肢に入れてみたいと思います。


やったこと3 Go言語によるTwitter OGP イメージ生成アーキテクチャ

転職会議で主にフロントからサーバーサイドを見てるse-yaと申します。
自分が担当したのは、前世の口コミをTwitter上に表示させるOGPイメージを生成するところです。
転生会議を担当したメンバーの一人から

  • 「se-yaさん空いてる?空いてるよね!」
  • 「前世の口コミをTwitterOGPで表示させる、レスポンスは早くしてね。」
  • 「よし、じゃあよろしく!」

と、怒涛のラッシュが来ましたのでやることになりました。
時間が殆どなかったので、ローコストで高いパフォーマンスを実現するためにざっと調べたところ Cloud Functionsにbeta版でgolangとpythonが使えると分かり、golangはパフォーマンスが良いとよく言いますし、 普段使わない技術を使うのもコンセプトだったのでgolangを採用しました。(他にGopherくんが可愛いのも理由ですかね(笑))

構成図

画像生成に時間がかかると、レスポンスが悪くなりユーザ体験が損なわれる可能性があったので、 処理時間を最小にするために、雛形をパターン別にCloud Storageに用意してそれをダウンロードし、合成した画像をアップロードする方法を取りました。
また、Cloud Funtionsはリクエスト数に応じてオートスケールするので、捌ききれなくなってもなんとかしてくれるだろうと思ってました。

よかったこと

1. Cloud Functionsで手軽にデプロイ・検証できること

サーバー不要でgcloudコマンド一発でデプロイからエンドポイント生成までやってくれて、ロジック作成だけに集中できました。
今回はhttpリクエストをトリガーにしたので、以下のようにResponseWriterとRequestを持つ関数だけ最低限用意すれば動くというわかりやすさも良かったですね。

package functions

import (
    "encoding/json"
    "log"
    "net/http"
)

type GenerateResponse struct {
    Status int64  `json:"status"`
    Result string `json:"result"`
}

func HelloFunction(w http.ResponseWriter, r *http.Request) {
    //logを使うとGCPのLoggingに、タイムスタンプ付きで書き込むことができる
    log.Println("Hello World!") 

    // JSONにする場合
    w.Header().Set("Content-Type", "application/json") 
    res, _ := json.Marshal(GenerateResponse{http.StatusOK, "Hello World!"})
    w.Write([]byte(res))
}

デプロイする場合は、goのモジュール機能を利用するので

export GO111MODULE=on

go mod init
go mod tidy
go mod vendor

gcloud functions deploy ${ENDPOINT_NAME} --runtime go111 --entry-point HelloFunction --trigger-http --region ${YOUR_REGION} --source . --project ${YOUR_PROJECT_ID}

と、runtimeにgo111を指定すれば、あとはよしなにやってくれます。
個人的にはAWS Lambdaより楽かと思います。

2. 早い!!

とにかく早かったですね。ざっと作った処理でも思った以上のパフォーマンスが出たのでそこにはびっくりしました。

3. エコシステムが優秀

golangだけでフォーマッタやテストツールなど揃ってるので安心して作ることができます。
今回はパッケージのインポートやフォーマット、linter周りで重宝しました。
そして、vim-goはいいぞ!(vim-go使ってる場合、GoImportsとすれば勝手にパッケージをインポートしてくれます)

_人人人人人人人人人人人_
> vim-goはいいぞ!! <
 ̄^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄

苦労したところ

1. フォントとサイズと中央揃えの戦い

日本語フォントを読み込んで画像を生成したのですが、半角と全角が1:2の割合でなかったため、 中央揃えを行う際に単純に、「1行の文字数の半分 x フォントサイズ」で書き込み位置を決められなかったのは 辛かったですね。。。
ここは愚直にトライアンドエラーで係数を算出してバランスの良い値を見つける作業を行いました。

2. PNGイメージがdecodeできない

雛形のイメージがPNGファイルで作られていたのですが、なぜか error image: unknown format となって 読み込めない現象が起こってました。
拡張子が.pngになっていたので自分はPNGファイルだと思いこんでたのですが、実は中身はjpeg画像だったことが原因でした。。。
fileコマンドで確かめるとたしかにjpegになっていたので、image/jpegをimportしてimage.Decodeできるようになり解決。
たどり着くまでにそれなりに時間を要しました。

最後に

制約なしでいろいろと作れるのは楽しいですね。リリース直後は上手くいくかどうかハラハラしましたが なんとかなってよかったです。
golangはかなり久しぶりに書きましたが、書いていて楽しい言語ですね。
Cloud Functions + golang + Cloud Storageを本番導入しても問題なさそうな感じで、個人的にアリかと思います。
ぜひ、皆さんも試してみてください!
あ、最後にもう一つだけ。。。

_人人人人人人人人人人人_
> vim-goはいいぞ!! <
 ̄^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄




転職会議ではマイクロサービス化を進めていることもあり、新技術を小さく試し、徐々に展開していく文化が根付いています。
今回は期間限定企画ということでかなり極端な例になりましたが、技術も楽しみながら社会のためになるサービスを作っていこうと日々奮闘しています。
少しでもご興味があれば、こちらLivesense Engineering Contact https://goo.gl/forms/jFO6b20jKck4zF0l2 からお気軽にお問い合わせください!

まだ間に合う!今すぐ転生しろ!

転生会議を支える技術「インフラ編」〜サイト爆速化への道〜

はじめに

転職会議では、2年ぶりとなるエイプリルフール企画を実施しています。 今年のテーマは「転生」、その名も転生会議です!

無料で豪華商品をゲット!今すぐ転生しろ!

「何か楽しいことしたいね」という気持ちで集まった、有志のエンジニアで始まった企画です。 有志で始めた企画のため、一番大切にしたいのは「楽しむこと」です。 さらに言うと、楽しむには業務として堂々と実施させてもらえることが重要であり、業務とするからにはプロダクトや組織に貢献すべきと考え、以下のように二つの目標を掲げました。

表目標:エイプリルフールに便乗して転職会議の認知を上げ、流入を増やす。(プロダクト貢献)
裏目標:技術的挑戦をしながら転職会議システムのレンダリング最速を目指す。(エンジニア組織貢献)

このブログでは、裏目標について詳しくご紹介します。 転生会議の裏では「使ってみたいけど、プロダクション適用にはまだ知見が足りないかも…」という技術をふんだんに使いました。 「やりすぎ」「オーバーエンジニアリング」は褒め言葉です!

転生会議の技術要素

転生会議は次のような技術に支えられています。

  • GKE
  • Firebase
  • golang
  • Elixir
  • Phoenix
  • Vue
  • Nuxt
  • fastly
  • webp
  • nginx
  • Route 53

1回で全部書くと長くなるので各担当者が二回に分けて転生会議を支える技術について述べていきます。 今回はインフラ周りを担当したSREの山内とだいたいなんでもやったyamitaniがお送りします。 ではインフラ編を張り切っていきましょう!

インフラ構成

表側にfastly、裏側にGKEを配置する構成です。
フロント部分はnuxt.jsで実装し、APIはElixirで実装されています。
OGP画像はCloud Functions上で動くGo言語のプログラムによって動的に生成されます。
画像を設置するデータストアはCloud Storageを利用し、データベースはFirebaseを利用しています。

それぞれ誰かが使いたいものを利用しました。

f:id:ieee0824x:20190402195244p:plain ※ 初期構想図です。

やったこと1: fastlyの導入【山内】

速度を追求するにあたって主にやったことは大きく分けて2つです。
1つはできるだけcacheを利用できるようにすることです。

これはfastlyを利用することにより簡単に実現できました。
nginxなどでcacheを実現しようとするとcache hit率を上げるためにcacheを共有することを考える必要があります。
分散させるとcacheを飛ばす処理などめんどくさいことが増えます。
その点fastlyだとcacheの分散について考えなくて良いですしcacheを飛ばすのもapiから気軽にできます。

基本的にはそのままの設定で導入して、リリース前は開発環境以外のアクセスを弾く用に設定していました。
cache時間等の設定は裏側に存在するnginx側の設定で設定していました。

今回はログイン機能が存在しなかったのでとてもシンプルな設定になりました。

やったこと2: 通信処理のチューニング【山内】

cacheの導入の他にやったのは通信処理のチューニングです。 fastlyを導入するとレンダリング完了までのボトルネックがレンダリング処理と通信処理に寄ります。

nuxt.jsで作られたサイトは特に何もしなかった場合次のような流れでレンダリングが行われます。

  1. rootのhtmlをダウンロードする
  2. htmlの記述に従いjsをダウンロードする
  3. ダウンロードしたjsからDOMが生成される
  4. 生成されたDOMに従い画像などの関連ファイルがダウンロードされる
  5. レンダリングが完了するまで2~4を繰り返す

レンダリングをするとき重いデータのダウンロードが発生するとダウンロード時間に引っ張られてレンダリングが遅くなります。
ダウンロード時間を減らすため転生会議では可能な限りデータを圧縮するよう務めています。

Mozilla FirefoxGoogle Chrome はWebPを利用することができるので積極的に採用しました。
ただしWebPに対応していないブラウザでは表示できないことと、開発時に画像を触りたいときWebPだと触りづらいので オリジナルのデータはPNGを利用しています。

WebP対応ブラウザへのWebP配信はnginxで行っています。
nginx側で Accept headerを解釈して配信しています。
次の用に Accept: image/webp,*/* で要求が来たとき 画像ファイル名.png.wepb を配信するようにnginxの設定を記述すると実現できます。

map $http_accept $WebP_suffix {
    default   "";
    "~*image/WebP"  ".WebP";
}

server {
    location ~ \.(png)$ {
        expires 7d;
        add_header Vary 'Accept';
        try_files $uri$WebP_suffix $uri =404;
    }
}

PNGの方も最適化しています。
PNGは zopflipng で再圧縮することにより容量を削減しています。
圧縮する画像にもよりますがオリジナルの画像の 20~80% 程度に圧縮することができます。

転生会議ではフロント部分はすべてnginxの静的ファイル配信機能を利用して配信しています。
静的ファイル配信機能を利用することでnginxの static_gzip を利用できます。
これはnginx側のCPU資源を利用しないことと zopfli という通常のgzip圧縮アルゴリズムより重いアルゴリズムでも安心して利用できることに繋がります。

$ find -E . -regex ".*\.(svg|html|htm|js|css|png)" -type f | xargs -I {} ls -la {} | awk '{ total += $6 }; END { print total }' | gnumfmt --to=iec
4.2M
$ find -E . -regex ".*\.(svg|html|htm|js|css)\.gz|(.*\.png)" -type f | xargs -I {} ls -la {} | awk '{ total += $6 }; END { print total }' | gnumfmt --to=iec
3.8M
$ find -E . -regex ".*\.(svg|html|htm|js|css)\.gz|(.*\.webp)" -type f | xargs -I {} ls -la {} | awk '{ total += $6 }; END { print total }' | gnumfmt --to=iec
941K

圧縮アルゴリズムのすごさがよくわかりますね。
配信物を圧縮することにより通信にかかる時間を圧縮することに成功しました。

しかし、これだけでは不十分でした。
例えば今回css上でttfファイルを呼び出しています。
ttfファイルのダウンロードを開始するにはhtmlをレンダリングしてjsをレンダリングしてcssを解釈してttfファイルをダウンロードするということになります。
これではただでさえ容量が大きいttfファイル(1.5MB)をダウンロードする時間が足を引っ張りレンダリングを遅く知ってしまうことに繋がります。
なので我々はttfファイルをpreloadすることにしました。
preloadすることによりhtmlのダウンロードが終了してhtmlの解釈が完了すると同時にttfファイルがダウンロードされるようになりました。
cssの解釈を待たなくて良いのです。
基本的に画像なども同様の方法を利用することでブロック時間を短縮できます。
素晴らしいですね。

違うドメイン名のサーバーから取得する資産のダウンロードが遅いという問題があります。
すでに接続済みのサーバーから取得する場合は名前解決の時間やTLSのセットアップにかかる時間が省略されます。
しかし違うドメイン名のサーバーに初めてアクセスするときは時間がかかってしまいます。
予め <link rel="preconnect"> をしておくことにより解決しました。
おおよそ15ms前後時間を短くすることができます。

これらのことを改善することではじめのhtmlのダウンロードの直後レンダリングが開始されるきれいな構成になりました。

f:id:ieee0824x:20190402195344p:plain

やったこと3: レスポンスを待たないUIにする【yamitani】

全ての要素を自分で0から構築してないですが、今回はfastly以外は全て触れてました。
各所メンバーのみなさんが実装してくださった部分のつなぎ込みが主な役割でした。
gRPC-Webも挑戦しようと思いましたが、速さを追求するために、普段はやらないサーバーからのレスポンスを置き去りすることにしました。
レスポンスを待たないUIになることで、画面がサクサク動いて非常に気持ちよかったです。

やったこと4: Twitter上でOGPを表示させる【yamitani】

Twitter上でのOGP画像の表示が苦労しました。

当初の計画ではSPA前提の構成で設計していたので、エイプリルフール前日の検証でTwitterのBotがjsのレンダリングに対応していないことにリリース4時間前に気づきました。
リリース時点ではOGP画像の表示対応が間に合わず、後から対応することにしました。

Twitter上でOGP画像を表示するために検討したこと。 1. rendertron など Headless Chromeでjsレンダリングにした結果を返す 2. Nuxt SSR対応 3. 口コミ投稿時にHTMLファイルを生成する

3は画面表示後に自動遷移させる必要があったのでやめました。

リリース初期はTwitterBotのみHTMLのレンダリング結果を返す1番で対応していたのですが、動作が不安定でした。
サーバーのメモリを食ったり、レスポンスの遅延が発生してOGPが表示されなかったりしました。
最終的には2番で対応することでOGPの表示が安定される状態まで持って行きました。
SSRだとUniversal JavaScriptに気を付けなくてはならないので全てSPAで対応したかった。
TwitterやFacebookなどCrawler Botを考慮するならまだまだSSR対応は必要ということがわかりました。
全てのBotがGoogleのBotみたいだと思わない方が良いということが知見になりました。

Nginxの設定

    location / {
        if ( $http_user_agent ~* "Twitterbot/1.0" ) {
            proxy_pass http://localhost:xxxx; # Nuxt SSR用のサーバー
        }

        if ( $http_user_agent !~* "Twitterbot/1.0" ) {
            root /path/to/src; # SPA静的ファイル
        }
    }

ローカルでの検証

curl -L -v -A 'Twitterbot/1.0' 'site_url'

感想

山内

最初はとりあえずfastlyを導入しておけば速くなるだろうって思っていました。
しかし日本で有数の速さを持つサイトに追いつこうと考えると上から下のレイヤーすべてを考える事になり楽しかったです。
たかがエイプリールフールの遊びと思わず本気で楽しめましたし、普段の本番環境でいきなり導入しづらい技術も試せたので有意義な施策だと思いました。

yamitani

SPAはサーバに負荷をかけない(fastlyで完結)BotのみSSR対応すればいいことがわかった。
業務ではAWSを導入しているので、GKEの管理画面を触るなど新鮮だった。
データ連携のつなぎ込み部分を担当していたので、色々な技術要素を幅広くさわれた。
技術的に遊ぶというお題目は十分達成できた!!

普段の業務では恒常的に安定してい運用できるようにしないといけないので、いろんな制約が吹っ飛んだ状態でシステム開発をすると楽しいですね。
みなさんもシステム開発を楽しもう!!

おわりに

転職会議ではマイクロサービス化を進めていることもあり、新技術を小さく試し、徐々に展開していく文化が根付いています。
今回は期間限定企画ということでかなり極端な例になりましたが、技術も楽しみながら社会のためになるサービスを作っていこうと日々奮闘しています。
少しでもご興味があれば、こちらLivesense Engineering Contact からお気軽にお問い合わせください!

まだ間に合う!今すぐ転生しろ!

次回フロント編は後日お楽しみください。