株式会社クイックのWebサービス開発blog

HAPPYなエンジニア&デザイナーのブログです

エンジニア組織を1年半で6倍の規模に拡大した話 ~エンジニア採用のコツ(概要編)~

f:id:aimstogeek:20180223171404j:plain今回は、エンジニア組織の立ち上げから、一気に採用を加速させて規模を拡大した話をします。

こんにちは。 「紙の書類って結局あとで見返すことないよなー。全部シュレッダーしちゃお!」って最近思っています。
WEB事業企画開発室マネージャーのnakayanです。断捨離が得意です。

ここ数年、「エンジニア採用が難しい。うまくいかない。」という話をよく耳にします。 なぜかというと・・・

採用に関する市況

2017年11月末データでは、中途採用における有効求人倍率2.46倍
その中でIT/Web系職種に限ってみると、なんと8.17倍です!(エンジニア以外も含む)
単純に考えて、IT/Web系職種の方が転職活動をすれば、8社の内定が出るという状況です。

参考(IT/Web系職種の中途採用有効求人倍率

5年前:2.86倍
3年前:3.16倍
2年前:3.19倍

5年前はソーシャルゲームが急激に伸びてきており、DeNAさんやグリーさんが、転職エージェントに手数料100%を払ってでも採用競争を行っていた時期です。
これ以上加熱することはないと言われていたのですが、その頃が可愛く見えるくらい、現在は未曾有の状況です。

自分達も採用に苦戦していることに変わりないのですが、取り組みの中でうまくいっている部分や意識していることをお伝えすることで、少しでもエンジニア採用を成功に近づけるヒントになれば嬉しいです。

クイックのエンジニア組織の略歴

夜明け前

f:id:aimstogeek:20180223171737j:plain nakayanが入社する前からのことですが、エンジニアは2名でした。
この2名は開発を行っていたわけではなく、データ抽出・加工やヘルプデスク対応がメインで、一部業務システムの企画という感じです。

黎明期(4年前~2年前)

f:id:aimstogeek:20180223171516j:plain 開発エンジニアとインフラエンジニアが入社。
要件定義や詳細設計に関しては社内で行うようになり、開発リソースのみを外注で確保する体制になりました。
サーバの設計・設置・運用を委託していた状態から、完全内製(自社での設計・運用)に切り替わりました。このタイミングで、サーバはオンプレからクラウドへ移行しています。
一応求人募集はしていたので、その後もちらほらとエンジニアが入社し、「エンジニアも増えてきたねー」という段階。

成長期(1年半前~現在)

f:id:aimstogeek:20180223171549j:plain 事業成長のさらなる加速を意識し、将来を見据えた組織体制を本気で考え始めたことをきっかけに、開発も含めた完全内製を実現できる組織を目指すことを決断しました。

具体的には 下記のような内容を経営陣にプレゼンし、積極採用の理解を得るに至りました。

  • 自社サービスを複数展開していく上で、外注の開発リソースだけではスピーディな対応に限界がある。
  • これまで強みとしてきたマーケティングのノウハウを活かし、ユーザーニーズを的確に捉えながら開発スピードと品質向上の両立を実現する。
  • 内製開発をベースとし、技術ノウハウの蓄積を行うことで効果的に外注を併用していく体制を実現する。
  • あらゆる業界でIT・Webがビジネスの根幹となっており、各社、内製化へと舵を切っている状況にある。
  • 転職市場でIT・Web系人材の採用は激化しており、現時点でも人材不足が叫ばれ、この先さらに加速する。
  • 手遅れになる前にWeb開発の内製化を進め、新卒でも採用・育成していけるような体制の構築、中途採用でもIT・Web系の人材が入社したいと思えるような組織・風土づくりを進めていく。
  • 将来の企業成長・事業拡大を目指すにあたり、「既存サービスの成長を牽引」「新規事業・サービスの創出」「社内インフラの整備・構築(セキュリティ含む)」など、デジタル変革によって高い成果を出すことのできる組織をつくる。

こういった経緯から採用を加速させ、試行錯誤しながらも多くの仲間を迎え入れることができました。
まだまだ拡大成長の初期段階だと思っていますが、1年半前と比べて約6倍の規模になっています。

現在の組織構成

あまり詳細をお伝えできないのですが・・・
「エンジニア組織」と言いながら、厳密にはエンジニア以外の職種も含んでいます。
でも大半はエンジニアです。

職種はこんな感じ

色々な職種が集まっているので、勉強会でそれぞれの知識やスキルを共有したり、プロジェクト(プロダクト)ごとに連携するような体制になっています。(まだまだチューニング中)

採用のコツ

ここからが本題です。

採用の事前準備

募集したいポジションの要件を明確にする

スキル・経験においての必須要件を明確にしないと、エンジニアの方には響きません。

(例)PHPでのWebアプリケーション開発経験2年以上
   要件定義~設計などの上流工程経験3年以上
   など

また、開発環境・作業環境を記載しておくことも重要です。
(ある程度整っていないと記載できないですが。。。)

「開発環境」「作業環境」というのは、以下のようなものを指します。

求人の募集内容は、社内のエンジニアに協力してもらって、きちんとエンジニアに響く内容に落とし込む必要があります。

採用経路

色々な採用の手段がありますが、営業などのビジネス職とWeb系職種の経路はかなり異なります。

求人メディア

転職エージェント

リファラ

  • 自社の社員紹介

SNS

弊社の採用実績としては、自分たちが同業であるということもあって、転職エージェントをうまく活用した採用がここまでは功を奏しています。
次いで求人メディアでの採用が多くなっており、一部リファラルでの採用もできつつあるという状況です。

一般的には、Web系職種の方はエージェント経由よりも自分たちで転職活動をしている傾向もありますので、SNSや求人メディアを有効活用することが重要になってきます。
もちろん、転職エージェントからも多数の推薦をいただけるので、うまく併用していくことが求められます。

弊社としても、まだまだWantedlySNSを活用しきれていない状況なので、ここはさらにパワーをかけて試行錯誤していく必要があると感じています。

選考(面接)

面接回数は極力少なくした方が、応募者にとっては良いです。見極めは難しくなりますが、面接は1回が理想です。
それが難しい場合は、2回には抑えないと採用確率は大幅に下がります。
というのも、転職市場は超売り手市場であり、多くの企業(特にWeb系のスタートアップなど)は一発面接でスピーディに選考を進めます。 何なら書類選考とか抜きに「とりあえず会ってお話してみましょう」のその場で決まります。

書類選考で1週間、1次面接で1週間、2次面接で1週間、最終面接で1週間のように、1ヶ月もかけていると「他の企業に決めました」となってしまします。

選考ステップが多いことも問題ですし、応募者への連絡が遅いことも大問題です。
応募者の方からすれば「連絡が遅い=あまり自分に興味ないのかな」という意図で伝わります。

面接内容

もちろんスキルや経験はお伺いしますが、いかにも面接という形式ではなく対話を重視します。
人となりやコミュニケーション、自社のチームや文化にフィットするかが重要です。
そして、エンジニアが面接をする、もしくは同席するべきです。
技術のことをまったく分かっていない人が面接をすると「この会社大丈夫かな?」となってしまうので、エンジニア同士で技術的な話題で盛り上がれる方が良いです。

ちなみに、私が採用時に一番意識して見ているポイントは以下です。

  • 素直さ
  • 成長意欲(向学心)
  • 責任感(継続力、成し遂げる力)

あとは自分達が大切にしている、以下に共感し体現してくれるかという点もプラスで見るという感じです。

  • 適応力(チームワーク、柔軟性)
  • 顧客第一(ユーザーファースト)

先ほど「いかにも面接という形式ではなく対話」と表現したのは、一方的に質問をして聞くのではなく、こちらの熱意や思い、自分達のこともしっかりと知ってもらう必要があるということです。

例えば、

  • 具体的に担当してもらう業務
  • チーム体制
  • 期待していること
  • 会社として・事業としての未来、目指していること
  • 面接官自身の考えやビジョン

これらを理解・共感してもらって、「一緒に働いてみたい」と思ってもらえることが理想です。
入る前と入った後でのギャップを限りなくゼロに近づけていくための努力をします。

面接だけに限らず、場合によっては一緒に飲みや食事に行き、フォーマルではない場でお互いの素の姿を知ることも重要だと考えています。

どんな採用でも言えることなのですが、「一緒に働きたいと思えるか」。
これがすべてだと思います。 f:id:aimstogeek:20180223170122j:plain

伝えたい事が多すぎて

もっと色々お伝えすべきポイントはあるのですが、今回は概要ということで本当にあっさりとした表面上の内容になってしまいました・・・

とにかく、採用にはものすごく時間とパワーとお金がかかります。
かなりの覚悟を持って臨まないと、何となく「いい人採用できたらいいなー」では絶対に成功しません。 詳細はまた別の機会にお伝えしたいと思います。

それではまた(_´Д`)ノ~~


\\『明日のはたらくを創る』仲間を募集中!! // 919.jp

マークアップ勉強会、はじめました

こんにちは、satopiです。

html5j マークアップ部が開催しているMarkupCafeを社内エンジニアに紹介したところ、「楽しそう」「勉強になりそう」「やってみたい!」と声が上がったので、マークアップ勉強会をはじめました!

html5j マークアップ部のMarkupCafeとは

複数のチームに分かれて、お題の最適なマークアップを話し合い、模造紙に回答を書いて発表します。

MarkupCafe

弊社では少人数スタートとなったので、業務時間内で効率的に進められるよう、以下の進め方に変更しました。

  • 日常でよく使う―身近なサイトのUIからお題を選出。
  • 大人数ではないので各自でCodePenへマークアップを投稿する。
  • CSSやJSを書いても良い。(完全に任意)

勉強会、初回の様子

初回はQiitaのコメント欄をお題に選びました。

Qiitaコメント欄

当日の流れ

  1. 各自、当日までに事前にマークアップしておく。
  2. 勉強会当日は、一人持ち時間5分で自分のマークアップについて発表する。
  3. 意見交換タイム。

マークアップの結果発表

この記事では、自分を含めたフロントエンドエンジニアの2名のマークアップを紹介させていただきます!

Aさんのマークアップ

<div class="comment-box">
    <div class="header">
        <div class="user">
            <a href="#"><div class="avatar"><img src="" alt=""></div></a>
            <div class="meta">
                <a href="#" class="username">userName</a><br>
                <span><b>99999</b>contribution</span>
            </div>
        </div>
        <div class="info">
            <time datetime="YYYY-MM-DDThh:mm+9:00">YYYY−MM−DD hh:mm</time><br>
            <button type="button" class="like-btn"><img src="" alt="👍"> いいね</button>
            <span class="like-count">0</span>
        </div>
    </div>

    <div class="content">
        <p>コメントが入ります。</p>
    </div>
</div>
Aさんの考え
  • ひとつのコメントを"comment-box"として、リスト要素の中に入れていくイメージでマークアップしました。
  • クラス名はできるだけセマンティックになるように、CSSもできるだけシンプルにかけるように心がけました。
  • 悩んだところ:ユーザーのアイコン、名前がどちらもユーザーページへのリンクになっていますが、まとめてaタグで囲んだ場合の配置の仕方に悩んだ結果、それぞれaタグで囲むマークアップをしてしまい反省しています…(´・ω・`)

※勉強会が終わった後にコメントをまとめてもらったので、Aさんの反省が入りました(´・ω・`)

私のマークアップ

<ol class="list-comment">
    <li>
        <article class="comment">
            <header class="comment_header">
                <a href="dummy">
                    <span class="comment_user">
                        <span class="comment_user-name">userName</span>
                        <span class="comment_user-contribution"><b>99999</b> contribution</span>
                    </span>
                    <span class="comment_user-img"><img src="https://dummyimage.com/36x36/888/fff&text=photo" alt=""></span>
                </a>
                <time class="comment_date" datetime="YYYY-MM-DD">YYYY-MM-DD hh:mm</time>
            </header>
            <div class="comment_body">
                <p>コメントが入ります。</p>
            </div>
            <footer class="comment_footer">
                <div class="comment_fav">
                    <button class="comment_fav-btn" type="button">いいね</button>
                    <span class="comment_fav-num">0</span>
                </div>
            </footer>
        </article>
    </li>
</ol>
私の考え
  • コメントは時系列に並んでいくことを想定し、ol要素を選択。レスがついたら、入れ子でol挿入。
  • WHATWGのarticle要素の説明に「ユーザーが投稿したコメント」も独立したコンテンツと考えられると書いてある!使ってみよう〜。(´ᴖωᴖ`)にこにこ
    • articleを使うなら、headerとfooterも使っちゃお。
  • CSSを無効にしたときに、まずはじめに表示されるのはユーザーアイコンではなく、ユーザー名にしたい。
    • 今のQiitaで、ユーザーアイコンにユーザーページへのリンクがあるけど、altが指定されておらず、Webアクセシビリティ的に良くない。
      • 現状のQiitaのマークアップだと、アイコンにaltにユーザー名を入れると、重複読み上げになってしまうのでは…。
        • そうだ!ユーザー名&貢献数&ユーザーアイコンすべてをa要素で包括しよう!!
  • FontAwesomeは便利だけど、個人的な思想で空spanを使いたくない…。
    • いいねボタンにユニークなclass名をつけておき、CSSでアイコンを背景画像として表示させちゃお。

余談ですが、参加していたアプリエンジニアに「コメント用のtextareaで『改行1つはbr要素』『改行2つはp要素』に変換されるようにしたい」という要件を話したところ「Markdown形式なら実装しやすいだろうけど…それでも面倒な要件だね〜(Ծ‸Ծ )」と教えてもらいました。
そうだったんだ〜…。

勉強会にはアプリエンジニアも参加しており、設計/実装に関する意見も聞くことができるので、とても勉強になります。

続けていこう!勉強会!

次回のお題は、社内蔵書管理システムの一部のUI。
次回もフロントエンドエンジニアだけでなくアプリエンジニアも参加予定で、楽しくUI&マークアップを考える会になりそうです。

マークアップに正解はない」と言われていて、たくさんの答えがあります。
意味付けが最適かどうか、メンテナンス性に優れているかなど、様々な意見を交換できるマークアップ勉強会。
今後も定期的に続けて、社内文化の一つにできれば……と考えています。


\\『明日のはたらくを創る』仲間を募集中!! // 919.jp

Laravel+Vue.jsでVue.jsの使い方を考えてみたNotSPA

こんにちは、みっきーです。
現在Laravel+Vue.jsで開発をしています。
当初簡単な処理を使い回すのにVue.jsを使用していたのですが、開発が進むにつれ複雑化するわ肥大化するわで全私がもやもやする状態になりました。
そこで今回はこのもやもやを改善してVue.jsを使い倒す方法を考えたので備忘として残します。

Vue.jsでごりごりしたい方はお付き合いください。

Laravel 5.4
Vue.js 2.x
*他ライブラリの話はしません


全体の構成図*汚いメモでごめんなさい
全体の構成図

Vue.js実行までの流れ

  • Laravelのconfigに出力したいJSファイル名の一覧を設定
  • ページごとに引数を使ってBladeにパラメータ出力
  • base.jsでパラメータを元に各Vueを出力

Laravelのconfigに出力したいJSファイル名の一覧を設定

<?php
    return [
        // ページ名とかURLとか一意のもの => JSファイル名
        'hoge' => 'hoge'
        'hogehoge' => 'hoge'
        'piyo' => 'piyo'
    ];

ページごとに引数を使ってBladeにパラメータ出力

<!DOCTYPE html>
<html lang="ja">
<head>
    @include("head")
</head>
<body>
<div id="app">
        @include("header")
        @yield("contents")
        @include("footer")
</div>
<!-- JS読み込み -->
<script id="js-file-name" src="js/base.js" data-pageInfo={{ $JSFileName }}></script>
</body>
</html>

base.jsでパラメータを元に各Vueを出力

import * as Vue from 'vue';

// 読み込むJS名取得
var pageInfo = document.getElementById('js-file-name').dataset.pageinfo;
var jsonPageInfo = JSON.parse(pageInfo);

// ファイル名以外に受け取りたいデータを登録
import store from './store';
store.setStoreData('hoge', jsonPageInfo.hoge);

// ファイル読み込み
if (jsonPageInfo.jsFileName !== undefined) {
  require(jsonPageInfo.name + '.js');
}

Vue.jsの構造

  • 複数ファイルで使用したい処理はcommon.jsを作ってライブラリ化
  • 複数回使い回す機能はcomponentで対応
  • 共有したいデータはstore.jsを作成し一元管理

複数ファイルで使用したい処理はcommon.jsを作ってライブラリ化

common.js

export default {
  hogehoge: function (key, value) {
    // 処理
  },
}

default.js

// ライブラリ読み込み
import Common from '../common';

let app = new Vue({
  el: '#app',
  methods: {
    hogehoge(key, value) {
      Common.hogehoge(key, value);
    },
  }
});

複数回使い回す機能はcomponentで対応

default.js

// コンポーネント読み込み
import hoge from '../components/hoge';
import hogehoge from '../components/hogehoge';

let app = new Vue({
  el: '#app',
  components: {
    hoge,
    hogehoge,
  },
});

共有したいデータはstore.jsを作成し一元管理

store.js

export default {
  state: {
    debug: false;
    hoge: hoge;
  },
  setStoreData(key, newValue) {
    if (this.state.debug) console.log('setMessageAction', key, newValue);
    this.state[key] = newValue;
  },
  getStoreData(key = null) {
    if (key === null) console.log(this.state);
    if (this.state.debug) console.log('getMessageAction', key, this.state[key]);
    return this.state[key];
  }
}

default.js

// ストア読み込み
import store from '../store';

let app = new Vue({
  el: '#app',
  data: function () {
    return {
      state: store.state
    }
  },
  methods: {
    setStoreData(key, newValue) {
      store.setStoreData(key, newValue);
    },
    getStoreData(key) {
      store.getStoreData(key);
    },
  }
});

考えてみた感想

ファイルの複雑化と肥大化の解消、データの切り分け、処理の共通化、出力ファイルはPHP側で制御できるようになりました。 ずいぶんうまく使える状態になったのですが、ものたりない感じがします。
bladeからパラメータを渡してJSでファイルを読み込む方法はもっとスマートにできないかなって思いました。

一緒に考えてくれる方を大募集中です!
ご意見ご感想お待ちしてます!!

以上備忘録でした。ヾ(。・ω・。)ばいばーい


弊社では楽しく真剣にプロダクトと向き合い、『明日のはたらくを創る』仲間を募集中です!
https://919.jp/recruit/mid-market/

Laravelを使ったレイヤードアーキテクチャ+TDD

雪が降ると窓の外をみてソワソワします。
南の島出身のフルーツパーラーです。

突然ですが、実装をする際に気にかけている事はなんでしょうか?

要件、納期、開発環境、品質担保、テスト、、、

いろいろあると思います。

そんな中でも今回はみんな大好き「依存」について話したいと思います。

こんな方に読んで欲しい

  • Fat Controller / Fat Modelを回避したい(責務の分離)
  • テストしやすいコードとは?と考えている
  • Laravel(PHP)を利用している

世にある数々のアーキテクチャに乗って依存と戦おう

時間と共に依存度の高くなるシステムを扱うチームで是非議論したいテーマです。
サンプルソースを添えていますので、是非お手元で確認してみて下さい。


github.com


弊社ではアーキテクチャを考え実装し、『明日のはたらくを創る』仲間を募集中です! 919.jp

Gmailのアイコンが突然青くなったのを元に戻す方法

yumeです。今日(2018/01/29)出社したら、大変なことが起きていました。
なんと、なんの前触れもなくGmailのアイコンが青くなっていました。

f:id:aimstogeek:20180129181632p:plain
青い…
Gmailは赤くないとしっくりこない…

原因・戻し方

どうやら、Labsの「未読メッセージ アイコン」が原因ぽい?
これを無効にしたら戻りました。

「設定」
→「Labs」
→「未読メッセージ アイコン」を「無効にする」
→赤いアイコンに戻る!

f:id:aimstogeek:20180129182506p:plain

よくわからない挙動も…

別のアカウントで、「未読メッセージ アイコン」を「有効にする」としても、一瞬青くなるだけで赤いアイコンになるケースも発見しました。
特に困らないですけど、なんか気になりますよね、アイコン。。


株式会社クイックでは、こんな日々の気づきもワイワイ楽しく共有しています!
共に楽しんで働く仲間を募集中です!
919.jp

「docomo R&D Open House 2017 in TOKYO」が楽しかったという話

こんにちは。ZAWAです。

2017年11月9、10日に日本科学未来館にて「docomo R&D Open House 2017 in TOKYO」が開催されておりました。
http://docomo-rd-openhouse.jp/

docomo R&D Open House 2017 in TOKYO」ではNTTドコモの最新の研究開発の取り組みに関する講演や展示が行われています。
今回はネットワークやAI、IoTに関する技術に加え、2020年のサービス提供開始をめざして研究開発中の第5世代移動通信システム「5G」の技術・デモなどの展示がありました。

“ちょっと先” の未来がとても楽しかったので、この場では特に印象に残った3つをご紹介いたします。

<展示内容分類まとめ ※()は展示数>
 ・未来の家 Living(11)
 ・旅行 Traveling(5)
 ・学び Learning(2)
 ・エンターテインメント Exciting(7)
 ・5G Experience(17)
 ・AIプラットフォーム(21)
 ・IoTプラットフォーム(8)
 ・デバイス&インタラクション(5)
 ・ネットワーク(23)
 ・イノベーションチャレンジ(1)
 ・ソリューション(4)

目次

1. 未来の家 Living:目が合う対面型ビデオ通話

2. デバイス&インタラクション:ぷるなび

3. 5G Experience:ニューコンセプトカート

未来の家 Living:目が合う対面型ビデオ通話

f:id:aimstogeek:20180124190352j:plain
※画像はイメージです
私がビデオ通話で感じていたもどかしさは「目線が合わないこと」です。
カメラが上部にある一方で見たい画面は中心にあることが原因ですが、この装置は課題を解決してくれています。

後ろにカメラが付いている画面が人の認識できないくらいの超高速で透明と映像を切り替えているそうです。
そのため「画面を見る」=「カメラを見る」となり、目線が合うビデオ通話を実現しています。

この技術が普及してビデオ通話やビデオ会議の活躍機会が増えることを楽しみにしています。

ビデオ会議の使用頻度が高まれば、
簡単にホワイトボードを共有できる会議の質を上げるツールとか、
話者の表情つきの議事録が簡単に作成できるツールとかも出てくるかもしれません。
夢が広がります。

バイス&インタラクション:私を「ぷるなび」で連れてって~力覚呈示デバイスの応用に向けて~

f:id:aimstogeek:20180126132015j:plain
※画像はイメージです
「ぷるなび」とは「まるで、親が子の手をひくような感覚で、目的地に誘導することはできないか」という発想で開発された装置とのことです。
見知らぬ駅に着いた時に目的地が「ざっくりあっち」とすぐ理解できれば、逆の出口に向かうことも無いでしょう。

「速い動きには敏感である一方、遅い動きは知覚しにくい」という人間の感覚特性を利用し、一方向にすばやくその反対方向にゆっくりと振動させることによって、すばやく動かす方向に連続的に引っ張られるように感じさせる装置とのことでした。

片手で持てるメガネケース程度の装置を持って実際に体験したところ、確かにひっぱられているように感じます!
その場でこの技術を応用した釣りゲームもプレイしましたが、引っ張られる感覚が楽しかったです。

色々な場面での応用ができそうで印象に残っています。
目印になるものの少ない自然の中、たとえば山の散策後に自分たちのキャンプ場に戻る場合とかも便利かもしれません。

5G Experience:ニューコンセプトカート

※外見や仕様はNTTドコモ様の報道発表資料をご確認ください
https://www.nttdocomo.co.jp/info/news_release/2017/11/02_00.html

ハンドルもアクセルもブレーキもなく、前面はガラスではなく液晶画面の車です。
運転ができない人だけでも車で移動できるようにするには、誰かが “遠隔操作” をすれば良いという考えから、
“遠隔なのだからハンドルは要らない → それなら前が透けて見えるガラスである必要もない” という結論に至ったそうです。
案内の方に「これで運転します」と渡されたものは「プレイステーションのコントローラー」でした。

車内の液晶は映す内容を切り替えることができます。
車体前方についているカメラ映像を表示すれば前を見ることもできますし、ビデオ映像を表示すれば大スクリーンでコンテンツを楽しめます。

目的地までの過ごし方が自由になるので、車での長距離移動も今よりハードルが下がりそうです。
個人的にはスキーやスノーボードに車内でワイワイ行くのを妄想していました。



他にも【ネットワーク:病院内での携帯電話利用】など興味深いものが溢れたイベントでした。
株式会社クイックでは皆が新しい技術に触れる機会を大事にしながら働いています。

919.jp
この記事のトップへ戻る

【Webサイト高速化】ブラウザキャッシュについてまとめてみた

脳内キャッシュが全然足りてません✧(・ㅂ・)و
こんにちは。クイックSREチームのみっちーです。

今回は、弊社のWebサイトにブラウザキャッシュ設定を実装したときに悩んだ箇所を簡単にまとめてみました。

今回の記事は、こんな人向けです。

  • そもそも「ブラウザキャッシュ」ってなんだろう?
  • ブラウザキャッシュは知ってるけど、設定方法がよくわからない。
  • とりあえず「Webサイトの表示を速くしたい」と思っている。

目次

1. キャッシュとは

2. ブラウザキャッシュの基礎

3. ブラウザキャッシュの設定方法

4. ブラウザキャッシュ設定時のCDNの挙動

1. キャッシュとは

最初に、キャッシュについての説明をします。

キャッシュ

プログラム処理結果をディスクやメモリ上に保存し、次回以降に同じ要求が来た場合には計算を省略することで、応答速度の向上を図る仕組みです。

キャッシュと一括りにしても、大きく分けて2種類あります。

  • サーバー側で設定するキャッシュ
    ※ このページでは便宜上、以降は「サーバーキャッシュ」と記します。
  • ブラウザキャッシュ

どちらのキャッシュを利用する場合も、
設定自体は基本的にコンテンツを配信するサーバー側で行う必要があります。

サーバーキャッシュ

その名の通り、「サーバー側」に保存されるキャッシュのことです。
DBサーバーやリバースプロキシ等でそれぞれ設定して、応答速度を向上させます。
※ なお今回ここについては詳しく触れません。

メリット

  • サーバーの負荷を軽減できる。
  • サーバー管理者がキャッシュの動作(有効期間や、キャッシュ対象、ゴミ掃除など)を管理できる。
  • Webサイトの表示が速くなる。

デメリット

  • 場合により、更新したコンテンツが即時反映されない。これにより、正しい表示がされない事がある。
  • AWS CloudFront等のキャッシュ専用サーバーを利用すると、費用が発生する。

ブラウザキャッシュ

一方こちらは「ユーザー端末(PCやスマホ等)」に保存します。ブラウザで見える内容をキャッシュして表示を速くします。
ただし設定自体は基本的に、コンテンツを配信するWebサーバー側で行う必要があります。
開発現場で、「キャッシュが残っている」という表現は、大体がこれを指しています。

メリット

  • サーバーの負荷を軽減できる。
  • パケット通信量の削減ができる。
  • サイトの表示が速くなる。

デメリット

  • サーバー管理者がキャッシュの管理ができない。 (有効期間が長いと、ずっと古い表示のまま。)
  • キャッシュを溜めすぎると、ブラウザの動作が遅くなる事がある。

2.ブラウザキャッシュの基礎

次はブラウザキャッシュについての基本事項です。
先程も少し触れましたが、ブラウザキャッシュはブラウザで見える内容をキャッシュするものです。つまりHTTPプロトコルで処理できる内容をキャッシュするということになります。

HTTP Respons Headers内の指示に従って動く

  • HTTPでの要求に対する応答には、必ずHTTP Respons Headersが付いています。Respons Headers とは、Webサーバーからの応答ヘッダー情報の集まりです。
  • キャッシュの動作(有効期間、キャッシュするか否か)は、HTTP Respons Headers内に「キャッシュ動作に関するヘッダー」が含まれていて、それに従います。
  • この「キャッシュ動作に関するヘッダー」設定はWebサーバー側で行います。

「キャッシュ動作に関するヘッダー」の主な種類

ここがブラウザキャッシュのキモだと思っています。
割りと細かいので、最初は以下だけ抑えておけば良いのでは?と個人的には思います。

最低限覚えておきたいヘッダー。
  • Cache-Control ヘッダー
  • Expires ヘッダー
余裕があれば覚えておきたいヘッダー。
  • ETag ヘッダー
  • Last-Modified ヘッダー

Cache-Control ヘッダー

キャッシュをするか否か、する場合には何秒程度キャッシュするかと言った「振る舞い」を定義します。
定義できる「振る舞い」の種類は多いですが、頻出する以下は抑えましょう。

  • no-store :
    「レスポンス結果の一切をキャッシュしてはいけない」という強い指示です。毎回違う結果を生成するphp処理などは、これを指定する事が多い印象があります。
  • no-cache :
    「WebサーバーがOKって言わない限りは、キャッシュしないでね」という指示です。基本的にno-storeと同じ内容だと考えれば良いと思います。
  • max-age :
    有効期間(TTL)の指定を行います。指定は秒です。
    ここで指定した秒数以内であれば、次回はサーバーへ確認せずに(ブラウザ)キャッシュを利用し続けます。
    0を指定すると、毎回有効期限切れ=データ再取得 となり、実質的にキャッシュをしないという意味合いになります。

Expires ヘッダー

有効期間(TTL)を意味するヘッダーで、 Cache-Control: max-age と同じです。
違いは、HTTPへの実装時期が古いため、より広く(旧ブラウザ)サポートされているという点です。
ちなみにGoogleは、こちらの使用を推奨しています。

ETag ヘッダー

ファイルに更新が有るか否かを確認するためのヘッダーです。
値には、ファイルのバージョンやハッシュが入ります。

毎回Webサーバーへ確認を行いますが、「欲しいファイルが更新されていなければ再ダウンロードは不要」と判断し(ブラウザ)キャッシュを利用します。
※ ファイルダウンロード分のパケットと、時間が節約出来ます。

なお、Cache-Control ヘッダーやExpiresヘッダーと一緒に配信された場合は、Cache-Control ヘッダーやExpiresヘッダーが優先されます。

Last-Modified ヘッダー

ETag ヘッダーと同様の内容ですが、こちらは値に「最後に更新した日付」が入ります。

3. ブラウザキャッシュの設定方法

ここでは、Respons Headers 内に上記のヘッダーを設定する方法について書いていきます。
設定は、Webサーバー上で行います。

「Cache-Control : max-age」または、「Expires」 ヘッダー に値を設定する。

Apache

expires_module(mod_expires.c) を利用します。
特にヘッダーの指定はなく、自動で両方のヘッダー項目に値が挿入されます。

以下は、画像ファイルとcss,pdfファイルに対して、アクセス日から1週間ブラウザキャッシュを有効にする設定の例です。

httpd.conf
<IfModule mod_expires.c>
    ExpiresActive On

    <FilesMatch "\.(jpg|jpeg|gif|png|ico|tiff|bmp)$">
       ExpiresDefault "access plus 7 days"
    </FilesMatch>

    <FilesMatch "\.(css|pdf)$">
       ExpiresDefault "access plus 7 days"
    </FilesMatch>

</IfModule>

AWS S3

Webサーバーでは無いですが、基本的な考え方はWebサーバーと同じです。
画像配信にS3を使う構成は、最近だと多いと思います。

オブジェクト のメタデータ

以下のいずれかで設定できます。両方書いても良いですし、片方だけでもいいです。

  • [Cache-Control]
    キー項目で上記を選択して、「max-age = 秒数」や、「no-cache」と言った形で値を入力します。

  • [有効期間]
    キー項目で上記を選択して、「曜日, 日付 月 年 時間 GMT」で値を入力します。
    ※ 前述のExpiresヘッダーに該当します。

以下は、Cache-Controlと有効期間の項目にそれぞれ1年で設定した例です。 f:id:aimstogeek:20180116172130p:plain

4. ブラウザキャッシュ設定時のCDNの挙動

ここでは、先程説明したブラウザキャッシュ設定をした状況で、AWS S3やCloudFrontを利用した場合のTTLについて記載します。

構成イメージ

f:id:aimstogeek:20180117120118p:plain かなり大雑把な図で恐縮ですが、
「WebサーバーとしてEC2を利用し、DBサーバーはRDSを利用。画像はS3に設置して、その上段にCloudFrontがある」という構成で話を進めます。

キャッシュの設定状況

以下のようになっているとします。

S3

今回、画像の配信元(オリジン)サーバーは、S3となりますので、ここでブラウザキャッシュの設定を行います。

  • Cache-Control max-age=86400

CloudFront

CloudFrontは、あくまでも「キャッシュサーバー」であるため、サーバーキャッシュの設定となります。

  • Minimum TTL 0
  • MuximumTTL 43200
  • Default TTL 60

このときの挙動は?

ユーザーが、S3上にあるA.jpgを取得したとします。
このとき、以下のような動きになります。

A.jpg の

  • ブラウザキャッシュ期間は、86400秒。
  • CloudFrontサーバー上のキャッシュ期間は、43200秒。

つまりユーザーは「S3で設定した値」でキャッシュし、
CloudFrontはCache-Controlの値と比較して短い方を、自身のキャッシュ保持期間として適用します。
※ この場合だと、CloudFrontは43200秒経過したら、S3へ再度A.jpgを取得しに行くという動きになります。DefaultTTLは、Cache-Control(またはExpires)ヘッダーが無いときに使われます。

この辺りがちゃんと理解できていないと、設定中に混乱することがあると思いますので、事前に整理しておくと良さそうです。



いかがでしたか。

ブラウザキャッシュ設定がよくわからないと悩んでいる方の助力になれば幸いです。
それではまた~ (・о・) /

919.jp

この記事のトップへ戻る