LIVESENSE ENGINEER BLOG

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

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 からお気軽にお問い合わせください!

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

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

PHPerKaigi 2019 にシルバースポンサーとして協賛しています #phperkaigi

f:id:boscoworks:20190327172224p:plain こんにちは。id:boscoworks です。
2018年末まで転職ナビのエンジニアリングマネージャを勤めていましたが、年明けからは全社横断でエンジニア採用を推進する業務をしています。
リブセンスでは、企業文化やプロダクトの特徴をより多くのエンジニアの方に知っていただくために、様々な技術カンファレンスのスポンサー活動を実施しています。
間もなく開催されるPHPerKaigi 2019には、シルバースポンサーとして協賛しています。

PHPerKaigi 2019

  • 2019年3月29日(金)〜31日(日)
  • 練馬区立区民・産業プラザ Coconeriホール

イベント詳細: phperkaigi.jp

当日参加される方には、リブセンスからちょっとしたノベルティをプレゼント致します。
入口で運営の方から受け取るトートバッグのなかに入っています。どんなノベルティなのかは当日までのお楽しみです!
もしお気に召しましたら、ハッシュタグ #phperkaigi をつけて、ぜひツイートしてくださいね。

リブセンスとPHP

リブセンスは、創業事業であるマッハバイト転職ナビなどのプロダクトの一部でPHPを利用しています。
私もPHPerとしてリブセンスで開発をしており、以前にPHPバージョンアップで一苦労したのをよく覚えています。振り返るともう2年以上前なんですね。

made.livesense.co.jp

PHPerトークン

PHPerKaigi 2019では、PHPerチャレンジという参加型のイベントを実施されるそうです。

phperkaigi.hatenablog.com

リブセンスからもPHPerトークンを準備しています。

#リブセンスやわらかペチパー

です。
やわらかとは一体何なのでしょうか。ヒントはノベルティにあります。
記号の#は半角ですのでお間違いなく。
高得点を目指しながらPHPerKaigiを楽しみましょう!

RubyのRedis Client LibraryをCluster Modeに対応させた話

こんにちは、アルバイト事業部の春日です。アルバイト求人サイトである マッハバイト のサーバーサイドを担当しております。 Redis GemCluster Mode に対応させる Pull Request を出す機会に恵まれたため、本日は振り返りも兼ねてそれについてお話させていただければと思います。

背景

Ruby on Railsで実装しているマッハバイトでは、将来的なユーザー増加を見越して2015年03月頃にRedis ClusterをSession Storeとして使い始めました。 当時はまだRedis3.0が出たばかりだった記憶があり技術選定時に twemproxy なども検討されていました。 Reshardingなどもできる運用ツール もあってRedis本体がNativeでサポートしている、という理由でRedis Clusterを採用しました。

しかしRedis3.0で初めてCluster Modeが登場したというのもあり、Cluster Modeに対応した定番Gemがまだありませんでした。 RailsのSession Store機構が定番のGemセットでうまく扱えずにログイン周りなどを自前実装することになりました。

そこで定番とは言えないものの Redis本体の作者がつくったCluster Client Library を本番環境で使用することにしました。 Gem化されていないので vendor/* 配下に直接コピーして使ってました。それでも問題なく動作していたのですが、徐々に自前実装しているセッションロジックまわりのメンテがキツくなってきました。

その後、この Redis本体の作者がつくったライブラリをGem化する試み や別のGemが出てきたみたいですが、どれも活発に使用されている気配はありませんでした。 できれば我々はビジネスロジックに集中したいため、そうすると定番Gemセットを使うには Redis Gem に頼らざるを得ません。

Redis Gem のメンテナの方々は Redis本体 もメンテしている方が多く、本体のメンテが忙しくてClientソフトの方までなかなか手が回っていない印象を受けました。 Cluster Mode対応のissueは何個かあがっていて需要はあったみたいなので、チャンスと捉えてPull Requestを出すことにしました。

時系列

出来事
2017-09 Pull Request を出すも活動的なメンテナがいなくてしばらく放置される
2018-05 Sidekiqオーナーが出した救いの手issue によりメンテナが増える、このタイミングでRailsをメンテしているようなShopifyのRubyistたちが加わったのが大きい
2018-06 RailsもメンテしてるShopifyのRubyistの方に丁寧に的確にレビューしてもらう
2018-07 マージされる
2018-10 Ruby Prize 2018 に並ぶも他が強過ぎて普通に選考から外れる

最初は Redis本体の作者がつくったライブラリ のリファクタリング程度のものでしたが、レビューを通してテストコードを拡充していくとリファクタだけでは全然考慮が足りていなかったことに気付き、あわてて全面実装し直しました。 RailsもメンテしているShopifyのRubyistの方にはこの場を借りて感謝を申し上げます。本当にありがとうございます。頭が上がりません。

RedisのCluster Modeについて

Slot

Redis ClusterのShardnigはCommandのKeyから 算出 されるSlot値をもとに行われます。

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

CLUSTER KEYSLOT コマンドを使うとKeyを指定してSlot値を得ることができます。

(以降の例はローカルに7000-7005ポートで1レプリカずつの計6インスタンスを立てているケースを想定) 下記の例だと key19189 のSlotを持つNodeに格納されることがわかります。

$ telnet localhost 7000
cluster keyslot key1
:9189

Slot値は0番から16383番までの16384個を取ります。Cluster構築時にSlot RangeをどのNodeに割り当てるかを明示的に指定します。 Cluster操作の運用ツールとして redis-trib.rb を使うとそこらへんの分割計算をしてくれます。 なお最新のRedisだと redis-trib.rbredis-cli に統合されました。Ruby以外でRedis Clusterを使用している環境では運用し易くなったと思います。

マッハバイトの本番環境ではレプリカ1つずつの計6台を運用しており、Master Nodeそれぞれに以下のSlotを割り当ててます。

  • Master1 0-5460
  • Master2 5461-10922
  • Master3 10923-16383

HashTag

Redisでは複数のKeyを指定できるCommandがあります。Cluster Modeだとそれに気を使う必要があります。Nodeを跨いだ複数Key指定はエラーが返ります。 下記の例だと key1key2 が異なるNodeに格納されていて CROSSSLOT エラーが返ってます。

$ telnet localhost 7000
mget key1 key2
-CROSSSLOT Keys in request don't hash to the same slot

これには複数Key指定コマンドの使用を諦めるか HashTag を使って意図的に同一ノードにKeyをまとめる必要があります。 下記の例だと {key}1 {key}2 は同一Nodeに格納されるようになります。MGET の結果が読みにくいですが [a b] の配列が返ってます。 余談ですが Redis Protocol はシンプルなので配列以外は読み易いです。

$ telnet localhost 7002
mset {key}1 a {key}2 b
+OK
mget {key}1 {key}2
*2
$1
a
$1
b

HashTagを使用すると中括弧内の文字列でSlot値を 算出 するようになります。 しかしこのHashTagを多用するとせっかくのShardingが偏るので注意が必要です。

Redirection

Redis ClusterのNodeはRequestをProxyしてくれません。指定したKeyが送信したNodeとは別のNodeに格納されている場合はリダイレクトを要求してきます。 SRE本 にもカスケード障害の章に以下の記述があります。

通常は、通信経路上に循環が生じることがありうるので、ユーザーのリクエスト処理の経路内でレイヤー内通信をするのは避けるようにした方が良いでしょう。 その代わりに、クライアントに通信をしてもらうようにしましょう。 例えば、フロントエンドがバックエンドに通信したものの、そのバックエンドの選択が適切ではなかった場合、そのバックエンドは適切なバックエンドへのプロキシとして振る舞うべきではありません。 その代わりに、問題のバックエンドがフロントエンドに対し、適切なバックエンドへリクエストのリトライを行うよう伝えさせてください。

下記の例だと key17001 番ポートで動いているNodeにあると教えてくれます。

$ telnet localhost 7000
get key1
-MOVED 9189 127.0.0.1:7001
quit
+OK

7001 番に繋ぎ直すと今度はリダイレクトを要求されなくなりました。(セットしていないため値は空)

$ telnet localhost 7001
get key1
$-1

Resharding

Cluster稼動中にshardingに偏りが目立ってきたり、新しいNodeを追加してさらに分散させたいときとかに Resharding ができます。 手動で CLUSTER コマンドを打ってもできますが、煩雑なので基本的には redis-trib.rb などの運用ツールを使います。

Resharding中は特殊なResponseが返る場合があります。Reshardingの流れは以下です。

  1. 移動先Nodeに対して CLUSTER SETSLOT コマンドで CLUSTER SETSLOT Slot値 IMPORTING 移動元NodeID を打って開始宣言する
  2. 移動元Nodeに対して CLUSTER SETSLOT コマンドで CLUSTER SETSLOT Slot値 MIGRATING 移動先NodeID を打って開始宣言する
  3. 移動元Nodeに対して CLUSTER GETKEYSINSLOT コマンドを打ってKeyリストを得る
  4. 移動元Nodeに対して MIGRATE コマンドを打ってKeyたちを移動させていく
  5. 任意のNodeに対して CLUSTER SETSLOT コマンドで CLUSTER SETSLOT Slot値 NODE 移動先NodeID を打って移動したSlotの所在を確定させる

この1, 2番の宣言から5番の確定までの間にRedisは -ASK というResponseを返す場合があります。RequestしたKeyが今どっちのNodeにあるか ASKING で尋ねる必要があるためです。

RedisのCluster Mode Clientの責務

Commandを正確に対象Nodeに送信する

Redis Cluster Clientは最悪Redirectionのみ対応できていれば機能はします。ですが無駄な通信は発生させない方が良いのでCommand送信先Nodeを正確に把握している必要があります。 基本的には以下の工程が必要になります。

  1. 送信するCommandからKeyを抽出する
  2. KeyからSlot値を算出する
  3. Slot値からNodeを特定する
  4. Scale Readしている場合は更新系コマンドはMaster Nodeに、参照系コマンドはSlave Nodeに送信する

Commandの細かな情報、Keyの位置や更新/参照系などは COMMAND コマンドが教えてくれます。 4番のScale Readは READONLY コマンドをSlave Nodeに打つと使えるようになります。

Redirection対応はあくまで上記のNode特定から漏れたときのセーフティネットと考えておいた方が良いかと思います。 また、Redirection対応に加えてResharding中のResponseの対応も必要です。 仕様ドキュメント にも以下の記述があります。

A client must be also able to handle -ASK redirections that are described later in this document, otherwise it is not a complete Redis Cluster client.

Commandごとの性質の違いを考慮する

RedisのCommandSET GET のようなものからPub/Sub、Luaスクリプトなど多岐にわたります。

単純にKeyを指定する系のCommandは普通にKeyからSlot値を得てNodeを特定して送信できます。

KEYS などのCommandはすべてのNodeに対して送信して結果をマージする必要があります。 しかしそもそも計算量の大きいCommandなので本番環境で使う機会はないでしょう。

Luaスクリプト は各Nodeに登録してあげないと不便です。単一Nodeに登録しても他のNodeでは使えません。

Pub/Sub コマンドだけは例外で、Redis ClusterではどのNodeに対して送信しても 伝搬 して動作してくれます。

Transaction対応

Transaction は2パターンの使い方があります。

  1. MULTI などのコマンドを個別に送信する
  2. Pipelining で一度に送る

Redis Clusterだと1番のやり方に問題が発生します。 MULTI を打つときにどのNodeに対して打てば良いのかわからないためです。 Redis ClusterのShardingはCommandのKeyに対して分散させるため、KeyのないCommandは送信先Nodeを特定できません。

よって2番の Pipelining を使ってTransactionをまとめて単一Nodeに送信することになります。

また2番でもTransaction内のCommandのKeyが複数Nodeに対するものであった場合に一貫性が保てません。 TransactionをNodeを跨いで担保することはできないため、ユーザーはTransaction内で単一Nodeにしかアクセスしないことを意識しないといけません。

Pipelining対応

Pipelining は悩ましい部分があります。 Transaction とは違い、複数Nodeに対するCommandが含まれていても、Clientソフト側でPipeliningを分割してあげて複数Nodeに送信してあげても良い気はします。 ですがたいていのClientソフトはTransactionでPipeliningを使用しているので、あんまり複雑にするよりはTransactionに合わせて単一Nodeのみに限定した方がシンプルになる気もします。 Redis Gem では後者のシンプルな方に倒しましたが議論の余地はあります。

Node変更対応

Clusterに対してNodeの追加やFailoverなどの変更が発生した場合にClientソフトは検知できないといけません。 CLUSTER NODES コマンドを使用すると最新のCluster情報を得ることができます。 Master/Slaveロールや割り当てられているSlot範囲などがわかります。

$ telnet localhost 7000
cluster nodes
$697
b22b58c1b9c711920a8e4e0d680ad19e842a54aa 127.0.0.1:7004 slave 27769953b7b22889a1d61c9aaf29d72b6931b0ff 0 1539232690326 5 connected
ebd83490643a0c129334dd47f6bfa761a0e72ef6 127.0.0.1:7002 master - 0 1539232690326 3 connected 10923-16383
50d8a34b0c9e10477587e9ed21e743c7852f05e4 127.0.0.1:7005 slave ebd83490643a0c129334dd47f6bfa761a0e72ef6 0 1539232691832 3 connected
5447d2b5ab8a0efff66d55a392099f142d30296c 127.0.0.1:7000 master - 0 1539232691330 1 connected 0-5460
27769953b7b22889a1d61c9aaf29d72b6931b0ff 127.0.0.1:7001 myself,master - 0 0 2 connected 5461-10922
b01855140ec22f9da217f3c379bcc4e0e33b6c78 127.0.0.1:7003 slave 5447d2b5ab8a0efff66d55a392099f142d30296c 0 1539232689826 4 connected

b22b58c1b9c711920a8e4e0d680ad19e842a54aa などの文字列はRedis Clusterが内部で管理しているNode IDです。 これとは別に 127.0.0.1:7000 などのIPとPORTの組み合わせ文字列で特定するケースもあるので、Node特定時にはコンテキストに応じて使い分けが必要です。

Redis Gemについて

Redis Gem はスター数も少なくはなく、Ruby界隈で主流のRedis Clientソフトです。 Ezra Zygmuntowicz さんが最初に実装したらしいです。

Cluster Modeの使い方

2018年10月現在、GitHubのエッジ版、または 4.1.0.beta1 のバージョンでCluster Modeを使用できます。

# Nodeごとの接続情報を配列でclusterオプションに渡します。
nodes = (7000..7005).map { |port| "redis://127.0.0.1:#{port}" }
redis = Redis.new(cluster: nodes)

# Nodeの接続情報は1つだけでも構いません。
# 内部で引数で指定されたNodeに対してCLUSTERコマンドを打ってNode情報を取り直しているからです。
redis = Redis.new(cluster: %w[redis://127.0.0.1:7000])

# Scale Readさせたい場合はreplicaオプションをtrueに指定します。
# デフォルトはfalseでMaster Nodeしか接続しません。
Redis.new(cluster: nodes, replica: true)

redis-rails Gemを使ってRailsアプリケーションでSession StoreやCache Storeとして使う場合は以下の設定で動きます。 なおRails5.2ではこのGemを使わなくてもActiveSupport側でCache Storeが使えるようになっているみたいです。 リリースノート

Rails.application.configure do
  # Redis Session Store (redis-rails Gem)
  nodes = (7000..7005).map { |port| "redis://localhost:#{port}/0/session" }
  config.session_store :redis_store, servers: { cluster: nodes }, expires_in: 1.month

  # Redis Cache Store (redis-rails Gem)
  nodes = (7000..7005).map { |port| "redis://localhost:#{port}/0" }
  config.cache_store = :redis_store, { cluster: nodes }

  # Redis Cache Store (ActiveSupport)
  nodes = (7000..7005).map { |port| "redis://localhost:#{port}/0" }
  config.cache_store = :redis_cache_store, { cluster: nodes }
end

Redis::Distributedについて

これはClient側でキー分散をサポートしている機能で こちら で言及されているやつです。

Clients supporting consistent hashing

Redis Cluster Modeとは一切関係がありません。こちらのRedisはノーマルモードで起動したものに対して使うみたいです。

なので Redis Gem は3つのモードをサポートしていることになります。

  1. 単一ノーマルRedisのClient
  2. Client側でサポートしている分散モードのClient Redis::Distributed
  3. Redis Cluster ModeのClient (今回私が実装したやつ)

Redis::Distributed は一部のCommandに対応していなかったり可用性も別に担保しないといけなかったりします。 一度削除されかけましたが周辺Gemがこれに依存している部分もあって反発に合い、Redis側にCluster Modeが誕生した現在でもまだ残っています。

構成

Redis クラスに公開インターフェースが揃ってます。ここにないCommandは method_missing に拾われます。 Redis クラスではCommandを配列として加工して後述の Client クラスに渡し、サーバーからのResponseをRuby用に最終加工したりしてます。 Redisからの生Responseは各種接続Driverが最低限のRuby用加工だけやってくれます。そこから先のBoolean化やHash化などのリッチデータ化の加工は Redis クラスでやってます。

接続周りの煩雑な処理は Client クラスに委譲されてます。 Cluster Modeでは Cluster クラスに委譲し Cluster クラスは内部で Client クラスに委譲しています。

Redis::Distributed クラスはその設計から外れていて独自に公開インターフェースを定義しています。 なので Redis クラスと Redis::Distributed の二重メンテが発生しています。

テスト

CIの設定ファイル を見るとRubyとRedisの各バージョンと各種接続DriverとのMatrixでテストされていることがわかります。

ローカルでテストする場合もCIと同じく make を使うと楽な気がします。私は最初は手元でDockerでコンテナを利用していましたが最終的には make を利用しました。

$ bundle install --path=.bundle
$ make start          # ノーマルの単一Redisが起動
$ make start_cluster  # Cluster Mode用のRedis6台が起動
$ make create_cluster # redis-trib.rb や redis-cli でClusterを組む
$ make test           # テストを通しで実行
$ make stop           # ノーマルの単一Redisの停止
$ make stop_cluster   # Cluster Mode用のRedis6台の停止

./tmp/* 配下に資源をダウンロードしてビルドして動かします。初回は redis-server 実行ファイルの生成に時間がかかりますが、以降はRedis本体側に更新がなければその工程はスキップされます。 テストを1ファイル単発で実行したいときは以下のように実行できます。

$ bundle exec ruby -w -Itest test/hogehoge_test.rb

テストツールは test-unit を使ってます。 minitest ではありません。

テストにおける共通処理やsetup/teardown系の処理は ヘルパー に記述されています。

Redisの基本データ型であるHash, List, Set, SortedSet, Stringなどのテストケースは Lint に定義されて共通化されています。 Single Mode Client Redis::Distributed Cluster Mode Client ではこれらを include してテストしてます。

モック はスレッドを立ててRedisプロトコルをしゃべらせて使ってます。 引数でコマンドとその生Responseを指定する形で使います。

def test_hogehoge
  redis_mock(save: ->(*_) { '+OK' }) do |redis|
    assert_equal 'OK', redis.save
  end
end

Redisのバージョンごとに異なるテストケースではバージョン指定ヘルパーを使って限定します。引数で指定したバージョン以降のRedisでないとテストが実行されなくなります。

def test_fugafuga
  target_version '3.2.0' do
    # test code
  end
end

反省 x お願い x これから

反省: 英語力の必要性

お恥ずかしながら私は致命的に英語ができません。今回のPull Requestでは英語ができないとスタートラインにすら立てないことを再認識させられました。 レビュアーの方にも私のカタコト英語にお付き合いいただき申し訳ない気持ちでいっぱいです。勉強します。

お願い: リアルワールドの高負荷環境での動作確認

現在マッハバイトでは Redis Gem のCluster Mode対応版である 4.1.0.beta1 を本番環境で使用しております。Scale ReadはせずにMaster Nodeのみを使用しております。

マッハバイトの本番環境は基本的にオンプレで、Redis Clusterもデータセンターで稼動しています。 Pull Requestでいただいたコメント ではCluster ModeをONにしたAWSのElastiCacheでも動作報告があがってます。

マッハバイトでは動作しても、より大規模で高負荷なサービスでこの 4.1.0.beta1 を使用したときに、ちゃんと長期的に動作し続けられるか見えない部分があります。 もし良かったらRubyで組まれてる他のサービスでも試験的に導入いただき、フィードバックをもらえたらうれしいです。

これから: TODO

  • マッハバイトのセッションまわりの独自実装を定番ライブラリを使うように修正(Must)
  • Redis Gem にてREADMEにCluster Modeについて追記(Should)
  • Redis Gem にてRedis5から使えるようになるらしい Streams の対応(Want)

合わせて読みたい

エンジニア学生支援プロジェクト "Code for Happiness" を開催しました

こんにちは、人事の羽山です。 リブセンスでは、今年からCode for Happinessという学生エンジニアの支援プロジェクトをはじめました。 9月28日を以って約2ヶ月にわたるプロジェクトが終了したので、その報告をさせていただきます。

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

Code for Happinessとは?

エンジニアリングを通じて社会課題解決を目指す学生の開発を支援するプロジェクトです。支援対象の学生には、開発費として一人あたり50万円の支援金提供とリブセンス在籍エンジニアによる約2ヶ月の開発サポートを行いました。

幸せから生まれる幸せ を企業理念に掲げ あたりまえを発明しよう というビジョンへ向かうリブセンス。そこには、社会を豊かにする新しい価値を生み出し続けたい思いが込められています。もともとは、リブセンスも学生同士で起業した会社です。しかし、志はあるけど費用面や技術面などの問題で実現できていない学生もたくさんいます。彼らを支援したい思いで立ち上げたのが今回のプロジェクトです。

リブセンスとしても今年がはじめての取り組み。果たしてどんな展開を迎えるのか、運営側の私も緊張しながらプロジェクト初日を迎えました。

最終審査に臨んだのは3チーム!

Code for Happinessでは、書類審査通過者が約2ヶ月の開発に臨みます。中間審査・最終審査を経て通過者には50万円の開発費支援が行われます。今回は総勢47名 (23チーム) からの応募がありました。最大6名を支援する予定で書類選考を行い、5名 (4チーム) の応募が採択されました。

私達がしたいのはビジネスコンテストでなく、志がある学生への開発支援です。提案の優劣はつけません。どの応募も独自の創意工夫があり、技術的にチャレンジしていて、審査員も頭を悩ませました。その中でも、世に出たときの社会的価値と開発の実現可能性を考慮し、最終審査に残ったのが下記3つの提案です。

プロダクト: vote

  • 起案者
    • 辰巳憲太郎さん (一橋大学4年)
  • キャッチコピー
    • 政治に対する意見発信をより身近に
  • 概要
    • 政治に関する意見発信は日本ではタブーとされており、リアルな会話においてもSNSに関してもなかなか議論しにくいという現状がある。このような問題を解決するため、Voteというサービスを開発。機能は三つあり、1. リアルタイム世論調査 2. 政治ニュースの閲覧機能、意見交換機能 3. 政治専門SNSを使用できる。各機能は匿名で利用でき、より手軽の政治的な意見発信を行うことができる。

プロダクト: HiTrip

  • 起案者
    • 小貫将太さん (中央大学4年)
  • キャッチコピー
    • 何を "誰とするか" でひとり旅はもっと面白くなる
  • 概要
    • ひとり旅をする人は数多くいる一方、旅先でのコミュニケーションがなく孤独に感じている人も多いという課題があることに注目。それを解決するためにひとり旅の日本人旅行者が、旅先の地域に住む人が提供する地域体験に全員がひとりで予約できるサービスを開発。地域の体験に参加でき、地域に住む人や他のひとり旅行者と交流できる。

プロダクト: Connect

  • 起案者
    • 室田雅貴さん (新潟大学大学院修士1年)
    • 小林航大さん (新潟大学大学院修士1年)
  • キャッチコピー
    • 定番化したおでかけに新しいワクワクを
  • 概要
    • 地元の楽しみ方は地元の人が一番良くわかっている。でも観光ガイドブックではそれを知ることができない。ならば、地元の人が案内すれば良い。そんな発想から生まれたサービスが「Connect」です。自分たちが訪れたスポットを地図上に点として投稿。それを続けることで観光コースが出来上がります。投稿者の体験に基づいたコースなので、観光ガイドブックとは違う発見に出会えます。

最終審査の結果は...

学生より各チーム20分の発表を行った後、審査員の能登 (VP of Engineering) より審査結果の発表です。

f:id:livesense-blog:20181005102313j:plain

結果は、全員最終審査通過!学生の皆さん、改めておめでとうございます!

学生インタビュー

最後に、感想を学生にインタビューしました。みんないいこと言うものだから、運営側の私も目頭が熱くなります。涙を隠しながら話を聴かせてもらいました。

小貫将太さん (中央大学4年)

f:id:livesense-blog:20181005103826j:plain 応募のきっかけは、私自身起業をしているのですがまだ自社のプロダクトがなく、自分たちのプロダクトを持ちたいと思ったためです。今回のプログラムでは、リブセンスから技術と開発費の援助をいただけるとのことだったので、これはチャンスと思い応募してみました。 プロジェクトは山あり谷ありでしたね (苦笑) 実は、応募時に提案したサービスと、最終的に出来上がったサービスが完全に別物なんです。開始1ヶ月はずっとメンターとプランの壁打ちでした。ほとんど開発していません (笑) コアバリューを定義し直したり、案出し100本ノックをしたり…悩み続けていましたが、その結果思い切ってプランを変更。 今回提案した「HiTrip」の開発を行いました。自分のやりたいことにたどり着けたのだから、必ずリリースします。楽しみにしていてください! そして、一緒にサービスを大きくしてくれる仲間も募集中です!

辰巳憲太郎さん (一橋大学4年)

f:id:livesense-blog:20181005103814j:plain Twitterでフォローしてた桂さん (リブセンス桂大介) のTweetを見てCode for happinessを知りました。以前よりアプリをリリースたことはあったのですが、ちょうど新しいものを作りたいと思ったタイミングだったので応募しました。 メンターからは新しい知識をたくさん教えてもらいました。企業が実際にどうアプリを実装しているか、運用しているかなどのノウハウは働いている人に聴かないとわかりません。そういったインターネットで検索しても出てこない情報は目から鱗でした。今回開発した「Vote」は日本ではタブーとされがちな政治の領域に踏み込んでいます。アプリの性質上、リリース後アプリが荒れる可能性あると正直思ってます。それでも、世の中がどう動くのか検証してみたい思いが強いです。 リリースまであとひと踏ん張り頑張ります!

室田雅貴さん・小林航大さん (新潟大学)

f:id:livesense-blog:20181005103759j:plain 今回のプロジェクトを一言で言うならば「青春」でしたね。特に最後の数週間は二人で研究室に篭り一日中開発してました。スタートアップの立ち上げってきっとこんな感じなんだろうなと思ってました (笑) 実は以前から新潟を良くするサービスを作りたいと思ってたんです。でもなかなか行動できずにいました。だからCode for happinessを知った時には背中を押された気分でした。正直、書類審査を通ると思っていなかったので通過の報せをもらったときは驚きましたが、それで覚悟が決まりました。 開発を始めるとたくさんの気づきがありました。一番衝撃だったのは「自分たちよりもユーザーの欲しいものを優先したくなる」感覚です。今回はリリースする前提なので、ユーザーが欲しいものを作る必要があります。そうなると、どんどん自分でなくユーザーに目が向いていくんです。この夏の青春で終わらぬよう、これからも努力していきたいです。そして、今回が人生初のリリースです。たくさんの人に使われるとを考えると緊張しますが、たくさんの人に使ってもらえるならばそれは幸せなことです。ユーザーからのフィードバックを得ながらどんどん良いものにしていきたいと思います!

まとめ

リブセンスとして初の取り組みということもあり、運営側も企画から運営まで試行錯誤の繰り返しでした。 でも、最後に学生から「一歩踏み出すことができた」「必ずプロダクトをリリースする」等前向きな言葉を聴けて、開催して本当によかったと実感しています。 これからもリブセンスは世の中を豊かにする あたらしいあたりまえ の発明に取り組んでいきます。少しでも興味あれば、コチラ Livesense Engineering Contact からお気軽にお問い合わせください。

f:id:livesense-blog:20181005103600j:plain 学生とメンター、審査員の村上 (リブセンス代表取締役社長)・能登 (VP of Engineering)・桂 (リブセンス共同創業者) で記念写真

画像配信システムにCDNの導入を試みた話

こんにちは。9月よりインターンとして参加しているインフラグループの幸田です。
現在リブセンスでは、高速化の取り組みを進めており、その一環として今回は画像配信システムへのCDN導入検証を行いました。

この記事では導入検証を通して見つけた、最低限確認しておきたい キャッシュヒット率 に関わる設定を2つ紹介します。

検証の背景とか

ステージング環境での検証 というフェーズで、インターン課題として取り組みました。
最低限キャッシュを無視する設定(PASS設定)しかされておらず、まさに「とりあえず通してある」という感じです。

ここから本番環境へ導入を進めるための導入検証をはじめました。

前提とか設定とか

前提として、今回導入検証を行うのは画像の配信を専門に行う 画像配信システム です。
CDNには Fastly を使用しました。
(以下Fastlyに合わせて画像配信システムのことを Origin と表現します)

ステージング環境は社内からしかアクセスできないので、そのままだとキャッシュヒット率の計測がうまく行えません。
そのため今回検証するステージングのOriginには、本番トラフィックの1/3をミラーリングし、ある程度トラフィックを確保した状態で、動作確認や検証を行いました。
この時点でのキャッシュヒット率は平均で30%~35%程でした。

とりあえずやってみよう

「キャッシュさせて高速にコンテンツを配信する」という観点で見た時、キャッシュヒット率が低くCDNとしての役割を果たせていないと思ったので「 思い当たる設定を見てみる 」ことにしました。

まずはTTLの設定を見てみる

キャッシュの話になった時、初めに思い浮かぶ設定値といえばTTL(Time to live)ではないでしょうか。
TTLは「どのくらいの期間キャッシュとして保持するか」という設定です。

Origin側の設定ファイルを確認しましたが指定はなく、更に前提でも書いたようにFastlyで明示的な設定も施していませんでした。

TTL指定しなかったらどうなる?

Fastlyではデフォルト値としてTTLが 120s に設定されています。
OriginやFastlyで特に指定がない場合はこの数値が適用されます。

つまり2分間だけキャッシュとして保持され、期限が切れるとOriginから取得して再度2分間キャッシュするという動きになります。
そのためキャッシュヒット率低下に繋がります。

TTLを設定する

「明示的にTTLを設定しなかったために 120s が適用され、キャッシュされる時間が短かった」というのが今回の原因なので、 明示的にTTLの値を設定 してみましょう。

TTLの設定方法はいくつかありますが、今回はOriginで Surrogate-Control というヘッダを付与して設定しました。
ヘッダに関する詳しい内容はこちらを参照してください。

nginxでの設定例です。

+    add_header Surrogate-Control "max-age=604800";

Surrogate-Control は今回で言うとFastlyなど 間に入るProxyサーバにのみ有効なTTL値を設定するためのヘッダ です。

通常 Cache-Control で設定したTTLはブラウザなどにも適用されますが、 Surrogate-Control は今回の場合だと、Fastlyだけで取り扱われ、その後取り除かれます。
そのためブラウザからこのヘッダは確認できません。

今回の Surrogate-Control の値は 604800s1 にしてみました。

TTLの設定値について

今回は「検証のために、ある程度長くしてみよう」という考えから 604800s を指定しましたが、本当にこの設定値が適切なのでしょうか。

個人的には正解がないような気がしていて、扱うコンテンツによっても変わってくるものだと思います。
なのでこのあたりの設定値は、コンテンツの種類や更新頻度を総合的に判断した上で設定する必要があります。

例えば、

  • あまり更新されることがないから、(仮に、少しの間古い情報が返されたとしても)大きな問題がない場合
  • そこそこ高頻度で更新されるので、数日間保持されると困る場合

など。
「すぐに更新してくれないと困るものなのか?」や「逆にちょっとくらい遅れても困らないものなのか?」という点に着目するとより適切な設定ができるかと思います。

極端な話をすると「何ヶ月も更新されることがないのであれば、何ヶ月も保持していい」ので。

キャッシュの更新どうする?

またキャッシュの更新をTTLだけに任せると「TTLが過ぎるまではOriginでコンテンツが更新されても、キャッシュされた古いコンテンツを返すのか?」という話も出てきます。

これについては、 CacheBusting と呼ばれる手法を用いることで解決することができます。
GETパラメータで更新日時などを付け加えて別のURLにすることによって、再取得させる方法です。

それ以外にもFastlyには 強力なPurge機能 が備わっています。Purge機能とはキャッシュを削除する機能です。
Originでコンテンツを更新した時にこのAPIを叩くような実装をすると、すぐ(ミリ秒単位)にFastly上からキャッシュを削除することができます。
CacheBusting の場合だとTTLが過ぎるまではサーバ上に残ることになるので、少しだけセンシティブな内容を扱う時はPurgeするやり方がオススメです。

ん?ブラウザ変えたらHITしなくなるな?

キャッシュのHIT,MISSの洗い出しをしようと思い、主にGoogleChromeのDevToolsを眺めていました。
普段はFirefoxを使用しているのですが、DevToolsが優秀なのでこういう検証をする時にはChromeも使っています。

その過程で「Firefoxで何度も表示させていたページを、Chromeで確認するとMISSになる」という現象から、User-Agent(以下UA)を取り扱うVaryヘッダに目をつけました。

Varyヘッダを取り扱うとどうなるか

そもそもVaryヘッダは「同一のURLでもヘッダの内容が違うから、別のコンテンツとして扱ったよ。キャッシュとかするならそこらへんよろしくね」という命令にあたります。
よくある例だと、UAのデバイスの情報をもとにコンテンツの出し分けを行っている時、CDNやL7LBに別物として認識させるようなケースでしょうか。

Fastlyでは2特に指定をしない限り、Varyヘッダを取り扱います。
つまりUAなどが含まれていた場合、 UA毎にキャッシュを生成します
UAはクライアントの様々な情報を格納しているため種類が多く、キャッシュヒット率に影響します。

今回の場合だとOriginでUAが付与されていたため、まさに「UA毎にキャッシュが生成されている」という状態でした。

じゃあVaryヘッダ消そう

この問題に対する対処法はいくつかあります。

  • FastlyでVaryヘッダを削除してキャッシュする
  • OriginでVaryヘッダを適切に設定する
  • FastlyでUAを正規化する

など。

今回は1つ目の、 FastlyでVaryヘッダを削除してキャッシュする という方法を採用しました。

前提でも話したように、今回対象となるのは画像配信システムであり画像以外のコンテンツを扱っていません。
またUAを利用してのコンテンツの出し分けも行っていないため 「 Varyヘッダを扱う必要がない 」 という理由から、削除する方法を取りました。
VaryヘッダにはUA以外にも Accept-Encoding なども含まれていますが、今回の場合は不要なので削除しました。

それ以外にも「FastlyのダッシュボードからGUIで設定可能であり、素早く試せる」というのも理由の1つです。

設定

Fastlyの設定画面からGUIで設定することができます。

1.対象のダッシュボード画面を開きます。
2.[CONFIGURE]を開いて、CONFIGURATION > Clone activeをクリックします。これで現在の設定がCloneされます。
3.サイドメニューより[Content]を開き、CREATE HEADERから画像のように設定します。
f:id:livesense-blog:20180914173330p:plain

効果を確認

時系列で話すと、Varyヘッダの削除→TTLの設定という順番で検証を行いました。
なので、Varyヘッダの設定を入れたのが左の赤ラインで、TTLの設定を入れたのが右の赤ラインです。
当初平均で30%程だったヒット率が今だと平均で80%近くまで上がってきています。

  • Varyヘッダの設定単体で見ると、約20%UP
  • TTLの設定単体で見ると、約25%UP

といった感じです。 f:id:livesense-blog:20180914173345p:plain

おわりに

この記事では最低限チェックしたいポイントを上げましたが、他にもヒット率を上げるための方法はいくつもあると思います。
また今回は画像配信だけを行うシステムを対象にしたのであまり複雑な設定をしなくても済みましたが、他のコンテンツも扱う場合だとUAが必要になったりなど色々複雑になってきます。
特に 個人情報などセンシティブな内容が絡んでくる場合は、設定を慎重に行う必要があります。

FastlyではVCLと呼ばれる言語を利用して複雑な処理を行うこともできるので、工夫次第でもっと最適な設定ができそうですね。

おまけ:感想的な何か

2週間という短い期間でしたが、内容の濃いインターンでした。
「なんかFastly流行ってるしカッコいいからこれがいい」という理由で、インターン課題を選定しましたがCDNに対する理解がより一層深まりました。
また技術的な部分はもちろん、ドキュメントを作成する時のコツなどそれ以外のこともたくさん学べました。

インターン時は「コーヒーの伝道師」と呼ばれ、社員の方に慕われたりなどすごく楽しかったです(笑)

最終日の焼肉も美味しかったです!関わってくださった社員の皆様ありがとうございました!


  1. 604800s = 1週間

  2. デフォルトのVaryヘッダの取り扱い方については、各CDNによって異なるので注意が必要です。

GopherCon 2018に行ってきました!

イエシルでサーバーサイドエンジニアをしている@mom0tomoです。
コロラド州デンバーで行われたGopherCon 2018に、会社から支援金を受けて行ってきました。

海外カンファレンスに参加するのは初めてだったので、新鮮なことばかりで毎日がとても刺激的でした。 今回は中でも特に印象に残った出来事をご紹介します。

なぜGopherConなのか?

わたしはふだんRuby on Railsを使っていますが、リブセンスでは一部のシステムでGoが使われています。

また、わたしはプライベートでWomen Who Go Tokyoというコミュニティの運営スタッフとしてイベントを主催しており、国際的なカンファレンスに参加して世界のGopherと会うことで、自分自身の技術に対するモチベーションをあげること、またそこで受けた刺激を社内外に伝えることを目的として参加しました。

f:id:mom0tomo:20180913053302j:plain
Women Who Goの仲間と

カンファレンスの日程

2018年のGopherConは4日間にわたって開催されました。

1日目はプレイベントとして、様々なワークショップが開かれました。
ワークショップのチケットは別に購入する必要があるため、ワークショップには参加しないけれども会場内をぶらぶらするという人も見られました。

わたしはBuffaloというRails likeなフレームワークを使ったWeb Developmentのワークショップに参加しました。
朝から1日かけて一通りの開発ができるようになるまで、開発者自ら講師になっていただきながら、初心者でもわかるように丁寧に教えていただきました。

他にも機械学習やパフォーマンス改善など、同時開催なのでひとつしか参加できないのが惜しいほど多種多様なワークショップが開催されていました。

f:id:mom0tomo:20180913052606j:plain
会場外観

2日目と3日目が本編で、大小のルームでセッションやトークが行われました。
また、2日目の夜には公式のパーティが野外で盛大に行われました。「Gopher Band」というバンドが登場し、迫力あるロックの生演奏を前にみんなでお酒を飲んだりご飯を食べたり、夜もふけるまで楽しく過ごしました。

f:id:mom0tomo:20180911221343p:plain
Gopher Band

最終日はコミュニティデーということで、OSSへのコントリビュートの仕方や実際に機器を使ったIoT開発のワークショップ、GoogleのGo開発チームの議論に参加できる時間などが取られていました。

最終日を待たずに帰ってしまう方もいるようで、コミュニティデーは本編よりも参加者は少なめでしたが、少人数だからこそのメリットを生かし、参加者同士やOSS開発者、コミュニティのリーダーの方との交流をじっくり取ることができて、充実感のある内容でした。

f:id:mom0tomo:20180911214649p:plain
コミュニティデー

わたしはOSSにコントリビュートするワークショップに参加し、athensというプロジェクトを用いてコントリビュートの基本的な方法を学びました。

ワークショップでははじめに、OSSにコントリビュートする上での心構え(気張らずに、みんなのためにコントリビュートしよう)や、初めてのコントリビュートまでの流れやオススメポイントなどをお話しいただきました。OSSへの熱い想いのこもったトークはわくわくするもので、その後すぐに同じテーブルの参加者とわいわい楽しく開発をはじめました。

華々しいオープニング・クロージングトーク

2日目の本編は、TEDさながらの迫力あるトークで幕を上げました。

f:id:mom0tomo:20180911214709p:plain
オープニング

トーク中はリアルタイムで正確な文章起こしがディスプレイに流れており、会場はみな驚きました。

Goを使った何らかの技術を使っているのか、はたまた人間が文字起こしをしているのか?と話題になっていましたが、どうやらプロの筆記者の方が数名その場でタイピングしていたようです。

いずれにせよ、オープニングおよびクロージングトークは非英語ネイティブでも容易に内容が理解できて楽しめる一つのコンテンツになっていました。

LTを含む全てのトークはyoutubeで視聴することができます。
Gopher Academy - YouTube

コミュニケーション、ネットワーキングの時間

日本で自身が参加したことのあるカンファレンスや技術イベントと比べて特に違いが目立ったのは、参加者の方々が対面でのコミュニケーションに注力していることでした。

いままでわたしが国内で参加した技術イベントでは、Twitterのタイムラインを追うとイベントの流れがわかるというほど、Twitterでのコミュニケーションが盛んでした。
しかし、GopherConは参加者の人数が多い割にハッシュタグを追ってもあまり情報が流れて来ません。

不思議に思って仲良くなったアメリカからの参加者の方に聞いて見たところ、普段会えない人と対面でコミュニケーションをとり、ネットワークをつなぐことで今後のコミュニケーションを円滑にしたり仕事に繋げることが目的としていると教えてもらい、違いに納得しました。

ネットワーキングの時間は朝食、ランチ、パーティなどふんだんに用意されていて、参加者の皆さんも気さくな方が多いのでテーブルで話が弾みます。 またコミュニケーションの時間にはセッションの感想等を話し合ったり、人によっては直接スピーカーに話しかけに行ったり、対面での会話や議論が多数交わされていました。

参加していない人も楽しめるTwitter実況は便利だと思う一方、対面でコミュニケーションの輪を広げて行くスタイルも参加する醍醐味を感じられて良いと思いました。

もちろん参加者全員がコミュニケーションが得意というわけではなく、大勢の知らない人と話すのは苦手だという人もいたり、自分なりのトーク技術を公開している人もいました。

わたしも初対面の人と話すのは得意ではありませんが、参加者はオープンに話題を振ってくれる気さくな人ばかりだったので、居心地よく過ごすことができました。

また公式のパーティに加え、現地のGopherによるmeet upやWomen Who Goディナーなど、知り合いをつくって交流するためのイベントが複数開かれていました。

印象的だったセッション

全てのセッションとトークが魅力的でしたが、ここでは特に気づきの大きかったものとして、アクセシビリティについてのセッションをご紹介します。

このセッションでは、目の不自由な方がエディタのコード読み上げ機能を使ってプログラミングをする例を挙げ、実際の読み上げ音声を使いながらアクセシブルなコードとは何かを説明していただきました。

一例を挙げれば、Goでは変数名を短くする、また型名の後ろに変数名を書くという特徴があります。

短い変数名が先に来て型名が後に来ることにより、エディタ等のコード読み上げ機能を使うプログラマにとって、素早く変数名を聞き取りすぐに意味を捉えることができます。


ロジカルに書くこと。
発音できるように書くこと。
首尾一貫して書くこと。


どれもコードを書く上で意識すべきと言われている基本的な原則ですが、アクセシビリティと関連づけて考えられたことはあまりないのではないでしょうか。

アクセシビリティに配慮して書かれたコードは、みんなにとって良いコードになる。

この言葉がひときわ輝く、すばらしいセッションでした。

下記にセッションの内容がアップされています。
ぜひ皆さんもご覧ください。

Writing Accessible Go

Go2に向けてのドラフトの話

2日目の午前のセッションが終わったと同時に、突如ビデオメッセージが流れ始めました。

話し手はあのRuss Coxさん!内容はGo2のドラフト構想についての発表で、会場は興奮の渦に包まれました。

f:id:mom0tomo:20180913052845p:plain
Russ Cox氏

詳しくは下記のページからアクセスできます。 https://go.googlesource.com/proposal/+/master/design/go2draft.md

また、日本語で意見を述べることが可能なフォームもあるので、興味のある方はのぞいてみてはいかがでしょうか。

おわりに

リブセンスでは半期に1名海外カンファレンスのための渡航費等を支援しており、誰でも立候補することができます。 もちろん, 海外カンファレンスには登壇者として招待されて行くのが憧れですが、すべてのエンジニアにチャンスが与えられるのは意義深いことです。

今回のGopherCon参加では概算で40万円ほどかかりましたが、その費用のほとんどを会社に負担してもらいました。
4日間のカンファレンス参加チケットに宿代等を合わせると小さくない金額になるので、支援してくれた会社に感謝しています。

今回の記事を読んでいただき、海外カンファレンスに行きたいと思った方、自分ならこんなものを持ち帰ってくるというアイディアのある方は、 ぜひリブセンスにお越しください。

リブセンスでは一緒に働きたい!という方をお待ちしています。

Tech Award 2018を開催しました

こんにちは、就活会議でエンジニアをしている大政です。 リブセンスでは、昨年からTech Awardという技術表彰をはじめました。

Tech Awardとは

技術的に優れた取り組みであったり、事業のKPIに直接的にはつながりにくいレガシー改善について、直近1年間の業務として行った取り組みを表彰するイベントです。 Edge (先進的)とLegacy (レガシー改善) という2部門を設けて、プロジェクトごとにエントリーしてもらい、当日プレゼン発表をするというものです。 プレゼン発表はノウハウの共有・質疑応答を目的としたもので、詳細な内容はエントリー時に社内Wikiにドキュメントを記載していただき、プレゼンの上手さではなく、取り組みの内容で審査をしています。

f:id:taise:20180827143426j:plain
Tech Award 2018 の メインビジュアル

審査員のみなさん

社外からも素晴らしいエンジニアの方を招致して審査をしました。

f:id:taise:20180829140908j:plain
左から藤 吾郎さん(Bit Journey, Inc.) 、能登 信晴さん (Livesense Engineer Leader)、田中 勇輔さん (Akatsuki Inc.)

f:id:taise:20180829140938j:plain
谷村 琢也さん (Livesense 執行役員)、中野 悦史さん (Livesense インフラストラクチャーグループリーダー)

プログラム

Edge部門で3エントリー、Legacy部門で5エントリーありました。 どのエントリーも創意工夫であったり、技術的なチャレンジに溢れるものでした。そのうちのいくつかは、後日こちらのブログでもご紹介できると思います。

Edge部門

タイトル 概要
AB テスト : 多腕バンディット基盤 "Brain Optimizer” ABテスト / 多腕バンディットを社内の複数のサービスで実施出来る基盤の実装 / 構築
メルマガ配信基盤 KiKi キューワーカー型の大規模メルマガ配信基盤を作成し、10 年来の PHP フレームワーク上のバッチで動いていたメールマガジン群を移行
Heroku Private Spaces移行 HerokuのCommon Runtime環境(米国リージョンにある)で運用中のサービスを、Heroku Private Spaces(AWSの東京リージョン)に移行

Legacy部門

タイトル 概要
人は1年でAWSの環境をモダンに改善できるのか 設定内容や構築ドキュメントが残っていないAWS設定や運用サーバ自身の設定を「可視化」及び「コード管理」をして「変更のしやすさ」、「設定の適用」を容易にする
転職会議 同時登録フルリニューアル セキュリティサポートの切れた会員登録の基盤のアーキテクチャリニューアル
【転職会議】契約管理業務のシステム化 バックオフィスの方々が手動で運用・管理していた、転職会議BUSINESSの契約から請求までの一連のフローをシステム化
転職ドラフト:問い合わせ効率化 問い合わせフォームによくある単語を入力すると解決方法が表示される仕組み
転職会議 新商品:Business PR 開発(Cosmeticsプロジェクト) レガシーを避けながらAPIサーバーを作って新機能追加

プレゼン発表

プレゼン発表当日は、エンジニア・ディレクターを含めて50名以上がオーディエンスとして参加し、プレゼン発表は笑いあり感嘆ありで、リブセンスらしい大変あたたかい雰囲気で行われました。

Edge部門にエントリーされたプロジェクトは、まだ誰も踏み抜いていない技術的な問題に取り組んでノウハウが蓄積されていたり、スケーリングの問題を分散システムの構築で真っ向から解決して運用までしっかりのせていたりと、聴き応えのあるプレゼンばかりでした。

Legacy部門にエントリーされたプロジェクトは、避けることができないレガシーなコード/インフラとの戦いであったり、リブセンスの事業環境に依存した課題をエンジニアリングで解決したものが多く、表に出てきにくい工夫が詰まっていて非常に興味深いプレゼンばかりでした。

また、藤さん・田中さんから、本質的な鋭い質問をたくさんいただきました。発表者だけでなく、オーディエンスのエンジニアも大変刺激になったと思います。

f:id:taise:20180829141326j:plain

Tech Award 2018 大賞

昨年と今年のTech Awardは、Edge部門とLegacy部門の2部門で、それぞれ大賞を決めています。 両部門を合わせた総合1位のような賞を作っていないのは、技術的に先進的な取り組みも、レガシー改善も、今のリブセンスにはとても重要なもので、優劣つけられないと考えているためです。 なお、大賞を受賞すると少なくない額のAmazonギフト券が贈呈されます。

Edge部門: メルマガ配信基盤 KiKi

受賞者

KiKi 開発チーム

  • 岡前 直由
  • 山内 雅斗

受賞理由

  • 分散処理、並行処理、スケーラブルなアーキテクチャをちゃんと設計して作っている
  • 止まった時のリカバリや運用のことまで見据えて作っている

f:id:taise:20180829141240j:plain

Legacy部門: 人は1年でAWSの環境をモダンに改善できるのか

受賞者

  • 藤村 宗彦
  • 山浦 清透

受賞理由

  • AWS の改善に追従する仕組みを作っている、レガシーになりにくい仕組みになっている
  • 運用ルール決めるのが難しいが、それもやりきっている
  • コードで管理するのが当たり前となっている

f:id:taise:20180829141412j:plain

まとめ

リブセンスは、様々な事業領域で「新しいあたりまえを発明」して、ユーザーに価値を届けるべく日々試行錯誤をしています。 そしてそれには、いま以上に高い技術力が必要だと考えており、今回ご紹介したTech Awardを始めとした様々な施策が社内で行われています。

今年で2回目を迎えたTech Awardは、昨年から進化をして開催することができました。
果たして来年はどんな技術的に優れた事例が大賞を取るのか、いまから楽しみですね。
もしかしたら、この文章を読んでくださったあなたが手にしているかもしれません。

リブセンスでは優秀なエンジニアを募集しております。
ご応募お待ちしております。

募集情報 | 株式会社リブセンス | 採用情報

f:id:taise:20180829141735j:plain