一休.com Developers Blog

一休のエンジニア、デザイナー、ディレクターが情報を発信していきます

エンジニア/デザイナー向けの会社紹介資料を公開しました

一休で働くことに興味を持っていただくために、一休のサービスやビジネス、開発組織がどうなっているかを紹介する資料を公開しました。

もし興味を持っていただけたら、いつでも話を聞きに来てください!

www.ikyu.co.jp

おまけ

先日、弊社のCSSエンジニアの募集がちょっとだけ話題になっていました。

一休レストランPython移行の進捗

レストラン事業部エンジニアの id:ninjinkun です。

一休レストランでは10年以上動いているシステムをPython 3で書かれた新システム(以下restaurant2)に順次移行する作業を進めています。現在ではPC用のレストランページ や主要な API を含め、いくつかのページがrestaurant2で提供されるようになっている状態です。本記事ではこの移行の経緯と、restaurant2システムの詳細、Pythonを選んだ理由、現在の進捗状況をお伝えします。

経緯

一休レストランはサービスローンチ時よりClassic ASP(言語はVBScript)でシステムが構築されてきました(こちらに驚かれる方も多いと思いますが、歴史的経緯という言葉で強引にまとめて話を先に進めます)。このシステムは現在も一休レストランを支えているのですが、長年の改修による複雑性の増加、言語の古さ、言語機能の貧弱さなどにより、事業成長の足かせになっているという事実がありました。

そこで昨年よりrestaurant2プロジェクトが立ち上がり、昨年末にAMPページを切り替えたのを皮切りに、移行が進んでいます。

restaurant2の概要

restaurant2の概要は以下の通りです。

  • Python 3.7 ・・・ typing, enum, data_classes, async/await (asyncio) など Python 3 の新機能を積極利用
  • フレームワークは Flask
  • DDD の Layered Architecture / Clean Architecture を参考にした薄い階層アーキテクチャ
  • Python 3 + mypy による type-hinting を使った型定義
  • flake8 + autopep8 によるコード規約 & 自動フォーマット
  • nosetests w/ factory_boy でのユニットテスト + Circle CI
  • API Blueprint による RESTful API の管理 / dredd によるドキュメントの自動テスト
  • フロントエンドは BFF + nuxt + vue.js による Universal JavaScript + コンポーネント指向設計
  • 実行環境は Docker + AWS Elastic Beanstalk (開発環境も Docker コンテナとして提供される)

構成図 f:id:ninjinkun:20180810165928p:plain

Pythonの選定理由

なぜPythonを選択したのかという質問をよくいただくので、こちらで選定理由を説明します。

一休レストランではこれまでデザイナーがマークアップも行うスタイルで開発が行われてきました。また、一部開発が得意なデザイナーはサーバーサイドロジックにまで踏み込み実装を行っていました。デザイナーがエンジニアと一緒になって開発を行うフローは、コミュニケーションロスを大幅に減らしてくれます。これを維持し、デザイナーが引き続き開発に参加しやすいように、開発環境のセットアップ、実装、プレビューまでを簡単に行える環境が求められました。既存システムのClassic ASP環境ではVBScriptをHTMLに埋め込み可能な動的なテンプレートエンジンとして利用していたので*1、同じようにテンプレートエンジンが使える動的言語がターゲットになりました。

この要件に当てはまり、かつ広く使われている言語としてはPerl, Ruby, PHP, Python, JS (node.js)あたりがターゲットになると思いますが、Pythonを選んだ理由としては以下の通りです。

  • 言語仕様がシンプルで学習コストが低い
  • 文法の自由度が低く、メンバー間の書き方の差異が減らせる
    • flake8 や autopep8 によりコーディングをスタイルを自動的に統一することも可能
  • 機械学習/AIのデファクトになっているため利用者が拡大しており、今後も言語の進化とエコシステムの高活性が見込める
  • 動的言語でありながら型定義による事前検査が可能

他にも社内にPython経験者が複数人居たり、選定の当時機械学習の勉強会が社内で流行っていた影響でPython熱が高まっていた…などの背景もあります。

マイクロフレームワークの選定理由

また、一休レストランではPythonで書かれたマイクロフレームワークであるFlaskを選択しました。この背景としては、ビッグリライトは行わず、既存のシステムと並行運用しながら段階的に移行して行くという意志決定があり、これにより以下の要求が発生したためです。

  • DBは既存システムと共用し、スキーマも既存のまま運用する
    • しかし既存DBスキーマのテーブル名、カラム名がユビキタス言語的な命名になっていなかった
    • 複数テーブルをjoinしないとEntityを表現できない場合があった
    • このためrestaurant2ではDataMapperパターンを使い、DBのテーブル構造から独立したオブジェクトを作成、オブジェクト名とカラム名をユビキタス言語に変換している
      • テーブル構造とオブジェクト構造が一致してしまうActiveRecordパターンのORMはこの方針には適さない
      • ORMを自分たちで選べる薄いWAFが望ましかった
  • 既存システムを開発しているメンバーも徐々にrestaurant2での開発に移行する
    • キャッチアップのし易さを考えると、マジカルな部分が多い厚いWAFよりは単純なWAFの方が好ましい

このような背景から、現在のrestaurant2の構成が決定されました。

現在の進捗

現在restaurant2により提供されているページは以下の通りです。

restaurant2プロジェクトは一気に新システムに移行することは行わず、ビジネス上の優先順位に基づいて手を入れる必要が出たページから順次移行するスタイルを取っているため、入れ替わりにはそれなりに時間がかかる想定です。

とはいえもっと移行を加速したいので、Pythonでアプリケーションを書きたい方はぜひご応募お待ちしております!

まとめ

  • 一休レストランはClassic ASP + VBScriptからFlask + Pythonに移行しています
  • 動的なスクリプト言語かつ人気がある言語としてPythonを選択しました
  • 一緒にPythonで一休レストランを作りましょう!

*1:個人的にはフレームワークを使わない素のPHPに似た印象を持っています

一休のSQL Server AWS移行事例(後編)

一休のSQL Server AWS移行事例(前編)からの続きです。

f:id:ninjinkun:20180718110152j:plain

実施当日

kudoy:
準備段階で色々踏んだので、だいぶリハーサルもしましたし、当日は作業スケジュール組んで、その通りにやった感じです。バッチを事前に流したり…

ninjinkun:
バッチというのは何ですか?

kudoy
宿泊とかレストランシステムなどで夜間に定時実行されているバッチ処理ですね。

ninjinkun:
ああ集計系の。

kudoy
そうですね。それを事前に流せるものは流して。

今回移行対象のDBが十数個あったんですが、各DBの環境上の都合で2通りの方法で移行する必要がありました。

1つ目の方法は、事前にオンプレ側で取得したフルバックアップを移行先となるAWS側にリストアしておく。切替までの間の差分データはトランザクションログを定期的バックアップして、それを使って移行先のAWS側も更新していく。大半のDBはこちらで対応できました。

2つ目の方法は、当日オンプレ側で完全にDBのデータ更新が終わってからフルバックアップを取得して、移行先のAWS側でリストアする。

いくつかのDBでこの対応が必要となりました。

f:id:ninjinkun:20180718110202j:plain

リストア処理について

ninjinkun:
ちょっとそのリストアとそうではないものの違いがよく分からなかったんですが。

kudoy:
データベースの復旧モデルと呼ばれる設定の違いによってDBリストアとなるのかトランザクションログのリストアになるのかが分かれました。復旧モデルの詳細については、こちら 一休では移行前は、完全復旧モデルというトランザクションログが取得できるモデルと単純復旧モデルというトランザクションログが取得できないモデルの2つを用途別に採用していました。 多くのデータベースは、一般的に使用される完全復旧モデルを採用していましたが、1日に数回だけデータが更新される集計系テーブルなどやアプリケーションからは使用されない運用上で必要だけどフルバックアップを取得した時点まで戻せればよい性質のデータを扱っているデータベースについては、単純復旧モデルを採用していました。 こういった事情で事前にある程度移行できるものと当日に移行リストアしなければならないもの2パターンの移行手段が必要だったんです。

移行先のAWSにAlwaysOnをあらかじめ構築しておいて、データのみ差分更新でオンプレの環境と同期させておくといったことが出来れば、作業時間も短くて済むんですが、先ほど話した単純復旧モデルのデータベースがある、また今回移行するにあたって、SQLServerのバージョンも2012->2017へバージョンアップしたので、バージョンアップに対応する設定変更の必要もあったので、AlwaysOnの構築は当日行いました。 DBデータファイルの分割とインデックス再構築といった作業も移行時にやっておきたかったので、こういった作業も同日おこないました。

ninjinkun:
めっちゃ大変そうですね

kudoy:
細かい話をするとWindowsのクラスタ(WSFC)はDBリストアの影響を受けないので先に構築してあって、SQL ServerのAlwaysOnも全て当日構築すると時間が掛かるので、一度リハーサル時に構築して、移行直前に一部の設定だけ削除しておいて、DBリストア後から必要な設定だけしていくといった手順で作業時間を短縮しています。部分的な再構築するといったイメージですかね。

ninjinkun:
リストアというのはMySQLで言うmysqldumpしたものを食わせるのと同じようなものなんでしょうか

kudoy:
うーん、まあそんなイメージ?バイナリなんですが。

ninjinkun:
あーでは後から順次食わせるのではなく、テーブルごと一気に復旧してしまう感じなんですね。

kudoy:
そうですね。DBのオプション設定やユーザ、テーブルとかDBに格納されているものを一気に復旧する感じです。 あとは、アプリケーション側でクラウド移行後の設定とかを変えてもらった部分があるので、それをリリースしてもらって、定期実行のスケジュールを止めていたバッチを裏側で動かして、問題無かったのでサイトオープン。 でもオープンしたらCPU使用率高いね、という(笑)

akasakas:
バッチを走らせまくってちょっと焦ってましたね。

kudoy:
あーそれがありましたね。深夜作業対で走っていた破壊力のあるバッチを一度に動かしたので。

ninjinkun:
じゃあCPU使用率はそのバッチが原因だったんですか?

kudoy:
それもあったんですが、バッチが終わってもまだCPU使用率は高かったです。

ninjinkun:
そこからパフォーマンスチューニングみたいな話になったんですよね。

kudoy:
インデックスいっぱい作ってもらったりとか、アプリケーション側も直してもらったりとか。

akasakas:
当日朝からお祭り感がありましたね。

kudoy:
でも思ったほどではなかった。それまでもオンプレで運用している段階でDBの負荷は高くなってきていて、ちょくちょくアラートが鳴る頻度は高くなってきていたので。

そういう意味では結構移行はギリギリのタイミングでした。あのままオンプレ続けてたら、手の打ちようがなくなっていた。

akasakas:
あのままオンプレ続けていたら、この夏はもう…

kudoy:
ちょっと厳しかった。

f:id:ninjinkun:20180718110224j:plain

当日起こったとしたら嫌だったこと

ninjinkun:
こういうのが起こってら嫌だったなというのありますか?

kudoy:
やっぱりリリースした後にあれこれ動かない系。事前に検証して貰っているので、そんなに心配しては居なかったです。でもフルの構成では検証し切れていないので、そこで何か出るのは嫌だなと思っていました。

あとは想定していてちょっと嫌だなと思ったことは、結局ハードを自分たちで管理できていないところなので、AWSに移行してしまった後は、転送速度が遅いとかあると今までと勝手が違うので嫌だなと思っていました。

ninjinkun:
それは今回は杞憂だったということですか。

kudoy:
そうですね。思ってたより逆に早かった。日中に試験しているときは転送速度遅いなと思っていたんですが、夜中に試したことはそれまでなくて。当日は夜中で帯域が空いている?のか早かったですね。

akasakas:
当日もちょっと巻いてましたよね。

kudoy:
EBS(io1)のIOPS上げたおかげもあると思うんですが、深夜という時間帯による影響もあったのかなと感じています。

準備段階で重点的にやったこと

ninjinkun:
この辺が怖かったので重点的にやった、みたいなのはありますか?

kudoy:
それはやっぱり時間の部分ですね。これはリハーサルを相当やったので。10回くらいはやってます。

ninjinkun:
リハーサルも最初はうまく行かなかったりしたんですか?

kudoy:
作業時間以外はうまくいかないといったことはないですね。1番の問題が作業時間が長いといったところだったので、各手順単位で作業(処理)時間を計測して、時間が掛かる作業を抽出して短縮する方法を検討/検証して時間を計測するといった繰り返して少しづつ時間を短縮していきました。

あとは机上で手順や作業を上手く組み替えて、何度もリハして何とか収まるようにした部分が一番苦労した部分ですかね。12時間かかりますとか言えないので(笑)

ninjinkun:
今回の作業はある程度リスクを取って実行していると思うのですが、やはり何度もリハーサルしたのが良かったんですかね。

kudoy:
いろんな検証を繰り返しやったので、その辺りは安心感に繋がってます。何か起きてもなんとかなるだろうと。アプリケーション で何か起きても心強いアプリの担当エンジニアがなんとかしてくるだろうと(笑)負荷試験も元々は1週間の予定だったんですが、見直して3週間くらいやっていました。

akasakas:
負荷試験はJMeterを使ってコントローラーを1台置いて、slaveを15台置いて一度に投げたりしていました。実際のリクエストからパラメータを生成して主要な導線や負荷が高いAPIをピックアップして夏のピークの負荷x1.5くらいのリクエストを投げて試験しました。

kudoy:
世の中的に同じようにAWSでやっている事例が少なかったというのがありますね。同じようなのは日本だとH.I.S.さんくらいしか居ないですね。H.I.S.さんについては事例が出ています。

ninjinkun:
やっぱりSQL Serverでクラウドだと普通はAzureになるんですかね?

kudoy:
うーん、Microsoftの製品ですし親和性は高そうですよね。でもAzureは今回は見送りました。 決めるときはAWS、GCP、Azureで比較しました。GCPはその当時まだ追いついてなかったので外して。 結局AWSとAzureともDBがSQL Serverであることが引っかかっていて、それを安定的かつお金的にも最適なと考えるとAzureはちょっとバランスが悪かったんですよね。インスタンスを立ててそれを分けるという構成だと。そもそもPaaSは制限が多すぎて両方とも使えなくて。

ninjinkun:
なるほど、フルマネージドは無理だねと。

kudoy:
そう、それは無理でインスタンスでとなったときにIOPSの調整がAWSの方が柔軟だった。そこの決定的な違いは、AWSだとディスクのサイズがある程度あれば2万IOPSまで上げられるんです。500GBでも2万IOPSとかできる。Azureの場合は容量によって性能が決まっているので、IOPSを上げると容量全然使ってないのに使っていない部分に課金が発生してしまうので、これはクラウドのうまみがないよねという話になりました。 じゃあそうなるとAWSもWebサービスの事例自体はいっぱいあるし、こっちかなと。

ninjinkun:
なるほど、サービス特性に合わせられる柔軟性と事例の豊富さでAWSになったんですね。

以上で聞きたかった話はだいたい聞けたと思います。ありがとうございました。

すごく大変な移行だったとだけ聞いていて、詳細を知らなかったので、今回は勉強になりました。

f:id:ninjinkun:20180718110146j:plain

一休のSQL Server AWS移行事例(前編)

一休.comではオンプレミスで動かしていたサーバー群をAWSに移行する作業を2016年末から2年がかりで進めてきました(以下、クラウド移行と呼びます)。2017年7月にまず全サービスのアプリケーションサーバー移行が完了、2018年2月にデータベースであるSQL Serverの移行が終わり、クラウド移行が完了しました。

一休のシステムは予約や決済などのミッションクリティカルな基幹業務を担ってるため、大規模なシステム移行は難易度が高い仕事でした。また、一休のサービスは予約を取り扱うECの一種であるため、トランザクション機能をはじめとする、DBに対する品質要求水準や機能要件も比較的厳しい環境です。

本記事ではこのクラウド移行の中でも特に難易度が高いDBの移行を牽引したkudoyに、移行計画の設計や勘所についてインタビューします。

f:id:ninjinkun:20180718110142j:plain

  • インタビュイー
    • kudoy デジタルマーケティング部エンジニア(写真右)
  • インタビュワー
    • ninjinkun レストラン事業部エンジニア(写真左)
    • akasakas 宿泊事業部エンジニア(撮影のため写真なし🙏)

ninjinkun:
私は今回のDB移行やSQL Serverについてほぼ何も知らないので、今日は私のような素人にもわかるようにお願いします。

kudoy:
わかりました(笑) 今回のAWS移行で解決したかった問題は3つあって、

  1. ハードウェアを属人的な運用で管理している
  2. ハードウェアの調達に時間がかかる
  3. ハードウェア製品のライフサイクル(製品寿命)に合わせて都度移行作業が発生する

という感じです。

ninjinkun:
クラウド移行前はハードウェア調達にどれくらい時間がかかっていたのでしょうか。

kudoy:
ハードウェアの調達は選定から始まって急いでも一ヶ月、セットアップから投入まででトータル二ヶ月くらい時間がかかっていました。

DB移行の時期がアプリケーションサーバより遅れた理由

ninjinkun:
2017年の夏にはアプリケーションサーバーの移行は完了していたわけですが、DB移行が後になったのはどういった理由だったのでしょうか?

kudoy:
一休のサービスに必要な機能がSQL Server 2017でないと使えなかったためです。具体的には、一休ではホテルやレストランの予約の際に宿泊データのDBとユーザーデータのDBで異なるDBをまたいだトランザクションを使用しているのですが、このための機能がSQL Server 2016にはありませんでした。

ninjinkun:
でも一休のサービスはこれまでも同じように異なるDBにまたがるトランザクションを利用して動いていたわけですよね。なぜ今回のクラウド移行で新たな機能が必要になったのでしょうか?

kudoy:
これまでは、DBサーバの冗長化をftServerと呼ばれる全ての部品が2重化されているハードウェア側で行っていました。このハードウェアは、OSやSQLServerからは1台のサーバとして扱えることで、複数DB間のトランザクションに関する制約に該当しなかったんです。しかしクラウド環境でも同じ可用性を確保するためには、 AlwaysOnという複数のインスタンスでMaster DBを冗長化する機能を使う必要がありました。その結果、インスタンスを超えてトランザクションを使う分散トランザクション*1が必要になったのです。

ninjinkun:
なるほど、クラウド移行でハードウェアではなくサーバー構成で冗長化する必要が出てきて、そのためにインスタンスを分けたから分散トランザクションが必要になったんですね。

kudoy:
SQL Server 2017でAlwaysOnでのDBまたぎの分散トランザクションがサポートされることがわかっていたので、アプリサーバーの移行を先に行い、SQL Server 2017のリリースを待っていました。 この時期は、AWS Direct ConnectでAWSに移行したのアプリケーションサーバとオンプレのDBサーバを専用線で繋いでいました。

ninjinkun:
他にスケジュールに影響を与えた要因はあるのでしょうか?

kudoy:
実は現在のハードウェアのキャパシティでは2018年中にはトラフィックを支えきれなくなる見込みで、かつ2017年の時点でデータセンターも2018年3月で解約することになっていたので、もうこの時期にやるしかなかったんです。

f:id:ninjinkun:20180718110214j:plain

DB移行の準備がスタート

kudoy:
2017年の10月にSQL Server 2017が出てきたので、そこからようやく本当のスタートでした。まずSQL Server 2017でできるようになったことを調べて、それから環境を作るためにAWS側のネットワークの設計や構築、DBの設定、セキュリティ、Microsoft ADの設定、運用周りの設計だとかを並行してやっていきました。

ninjinkun:
実際に動かして試したりもしたんですか?

kudoy:
動かしていましたね。でもテスト環境なので、最小構成で作ってしまっていて、いざ本番構成を作るときに見えていなかった考慮すべき点が出てきてしまったんです。

ninjinkun:
具体的にはどんな問題があったんでしょうか。

kudoy:
ネットワーク構成の問題ですね。 Multi-AZで、それぞれのAZにデータベース用のサブネットを用意して、AlwaysOnを4台構成(+1台ファイル共有マジョリティ用ノード)にして、2台+2台で配置する想定でいましたが、2台構成だと上手くいくけど、4台構成にするとうまくいかない。事例を調べても2台構成はあるけど、4台構成の情報が見当たらなくて。。。。

ninjinkun:
MySQLでの事例はあったんでしょうか?

kudoy:
選択する仕組み自体が違ってくると思いますが、MySQLだとレプリケーションを使用するかと思うので、EC2起動時に割り当てられたマスター/スレーブの1つのIPを指定するのではないでしょうか。

EC2でAlwaysOnを構成する場合、まずWSFCというWindowsクラスタが組まれていることが前提条件になります。構築するに当たり1つのENIに

  1. EC2(OS)用
  2. WSFC用
  3. SQLServer用

の3つのIPを指定する必要があるのですが、想定していた2つのサブネットにそれぞれ2台づつ配置する4台構成にしてAlwaysOnを構築するとWSFC用IPとSQLServer用IPが上手く認識できなくてWindowsクラスタが正しく動作していませんでした。

どうにも解決策が見当たらなくて、AWSサポートと相談してたところ、WSFCで1 つのサブネットに複数のクラスターノードを配置する方法は、 AWS ではサポートされていないことがわかったんです。そこで、以下の図のようにサブネットを分ければ解決することがわかりました。

f:id:ninjinkun:20180701165921p:plain
一休.comのAlwaysOn構成

この構成がオープンになっている事例は探してみたところ見当たらないので、図だけでも面白いかもしれません。そもそもAWSでSQL Serverを運用している事例自体が少ないですが。

ninjinkun:
元々は2つのサブネットで行けるはずが、4つのサブネットが誕生してしまったんですね

機能検証

kudoy:
その後、まずは機能検証用に今のアプリケーションをSQL Server 2017に変えただけで動くのかを検証しました。小さな環境を作って、E2Eテストや直接アクセスしてもらって検証したのですが、その時点でアプリケーションはほとんど問題無く動いていました。 それが終わった後に負荷試験用に本番と同じくらいのサイズのインスタンスを立ててパフォーマンスの検証をやってもらった感じです。

機能検証、パフォーマンス検証がクリア出来たところで、DB移行のリハーサルを始めました。 データ転送の時間や、AlwaysOnを構築したあとのプライマリインスタンスからレプリカインスタンスへのDB同期させる時間はすごく長くかつブレがありました。サイトのメンテナンス時間をユーザーやパートナーに告知しないといけないので、短くする方法を模索していました。

24:30から8:00で止めると告知しました。まあ告知した時点ではリハーサル時間は全然収まりそうもなかったんですが(笑)

一同:
(苦笑)

ninjinkun:
でもビジネス的にはそれ以上止められないということで、時間が決まったんですよね。

kudoy:
10時間超えるとか言ったらさすがに…となりますからね(笑)

akasakas:
でも最初のリハーサル12時間とかじゃなかったでしたっけ?

kudoy:
最初はそんな感じでしたね。でも色々、検討&検証してみたら何とかなりました。

ninjinkun:
それは事前にできるところをやっておくとかでしょうか。

kudoy:
そこも含めて、手順を簡略化したりとか、順番を入れ替えたりして。

EC2インスタンスタイプの選択も重要でした。当初R4系を使おうと思っていましたが、最終的にはI3系を選択しました。選択した大きな理由としては、I3系には永続的なデータの保存には使えませんが、インスタンスストレージとして、NVMeのストレージが付いていることでした。

インスタンスストレージは、インスタンスを完全に停止してしまうとデータが消えてしまいますが、起動している間は追加料金なしで使用できるすごく高速なストレージです。移行時には、データ転送やDB再構成、移行後もスナップショット的なDBバックアップなど、一時的な作業で且つ出来るだけ処理時間を短くしたいという用途に向いていました。このNVMeのストレージを上手く活用することで、AlwaysOnを構築する際に必要なDBバックアップとリストアの処理時間を短縮することができました。

あとはストレージ(EBS)のパフォーマンスを調整できるので、IOPS(ストレージのパフォーマンス)を一時的に上げて、そこでパフォーマンスが出たので机上の計算よりは速く収まりました。

ninjinkun:
結局一番効いたのは何でしたか?

kudoy:
NVMeストレージの活用と、IOPSの調整は効いてる思います。

ninjinkun:
環境構築してリハーサルも行って、見積もりも出ましたと。後は…

kudoy:
それと並行して開発系DBの移行の準備も進めていました。

移行に関しては丸々サイト止めてバックアップ取ってからデータをそのまま持っていくとすごく時間がかかるので、事前にフルバックアップを前日に取ってそれを先に送って、当日は差分のログだけを流すようにしました。できるところは事前にと言うのは、そういうところですね。

ninjinkun:
移行が11月からスタートして2月の中旬でしたっけ、実質4ヶ月弱でここまで来ているわけですね。

akasakas:
結構調整が大変でしたよね、移行当日のスケジューリングとか。

kudoy:
(ユーザー向けの)キャンペーンがちょくちょくあって実施可能な日が割と少なかったりとか…。

f:id:ninjinkun:20180718110210j:plain

準備で一番時間がかかったところ

ninjinkun:
ちなみに、一番準備段階で時間がかかったのはどこですか?

kudoy:
負荷試験じゃないですかね。負荷試験の環境を作るのもそうだし、さっき言った通り最小構成でテスト環境作っていたので、ネットワーク環境の作り方を調べるのに時間がかかって。

あとはすぐにインスタンスが確保できなかったという問題がありました。AWSの制約上、大きいインスタンスだと最初は2個しか確保できず、4台使いたいと思って制限解除の申請したら2日3日掛かって、IOPSの上限を上げるのも申請制でした。

ninjinkun:
それは間違って契約するのを防ぐためなんですかね。

kudoy:
たぶん意図しない課金を防ぐためと、あと制限なしにいきなり大きなリソースを沢山のアカウントから確保されるとAWS側もリソース供給出来なくなってしまいますよね。EBS(io1)のIOPS上限の緩和は時間が掛かるケースもあって一週間くらいかかったりすることもありました。

ninjinkun:
クラウドとは…という気持ちになりますね。

kudoy:
本番用のインスタンスも料金節約のため直前に用意するつもりだったのですが、AWS側のリソース提供状況によってはインスタンスが起動できないことがあることがわかったので、早いけど一週間前にはインスタンスを確保する為に起動しておこうと。 クラウドの怖いところを直前になって思い知らされました。

ninjinkun:
直前だと意外と調達できないという…

後編に続く

後編はDB移行の当日作業や、なぜAWSを選んだのかなどについて話します!

*1:複数のデータソース間でトランザクションの整合性を保証する技術。分散トランザクション自体はSQL Server 2016でも利用できたが、AlwaysOn可用性グループで利用できない制限があった。 参考

imgix導入で画像最適化とサイトスピードを改善した話

こんにちは。 一休.comの開発基盤を担当しています、akasakas です。

今回は、画像最適化配信サービスであるimgix宿泊レストランサイトに導入して、 画像最適化・サイトスピード改善につなげたお話をしたいと思います。

ここでお話しする内容

  • サイトスピードという観点での一休が抱えていた課題(の一部)
  • imgixの特徴とそこでできる解決策
  • imgix導入の効果
  • imgix導入をする上で大変だったこと
  • これから画像最適化を考える人たちへ
  • まとめと感想
  • おまけ(与太話)

諸注意

imgixを導入して、画像最適化という面でサイトスピード改善につながりましたが、 サイトスピードという観点で一休が抱えている課題はまだまだあります。 imgixを導入すれば、サイトスピードは万事解決!!!という話ではありませんので、悪しからず。

サイトスピードという観点での一休が抱えていた課題(の一部)

画像最適化ができてなかったため、サイトスピードが遅かった

画像の最適化ができてなかったことが大きな原因の一つでした。

一休というサイトの特性上、ホテル・旅館・レストランの画像を綺麗に見せたいというのが前提としてあります。 ホテル・旅館・レストランの高級感をユーザーに伝えたいため、画像サイズが大きい&高品質な画像を取り扱っています。

サイズ・転送量の大きい画像を扱っていることがサイトスピード低下の大きな原因となっていました。 無駄なリソースの削除をして、画像最適化・サイトスピード改善につなげたかったというのが背景としてあります。

今まで画像最適化ができてなかった結果、サイトスピードが遅くなり、ユーザーにご不便をかけていたのを改善して、より快適に使ってもらいたいという思いから画像最適化プロジェクトが始まりました。

imgix導入前のPageSpeed Insightsのスコア

imgix導入前のPageSpeed Insightsのスコアが以下になります。

宿泊

f:id:akasakas:20180624205650p:plain

[補足] 宿泊のキャプチャについては2018/01/19時点のもので、厳密にいうと画像最適化だけでなく、他にもいろんなボトルネックがありましたが、スコアが低い大きな原因は画像最適化の部分でした。

レストラン

f:id:akasakas:20180624205710p:plain

imgixの特徴とそこでできる解決策

上記の課題を解決するためにimgixを導入しました。 imgixは画像最適化に特化したCDNサービスというイメージです。 国内でimgixの導入企業として有名なのが、日本経済新聞社さんですね。

個人的に思うimgixの特徴としては以下が大きいと思います。

  • 拡張子自動判別
  • 自動圧縮設定
  • ロスレス圧縮
  • ストレージサービス連携(Amazon S3/Google Cloud Storage)

imgixのAPI Referenceなどに詳細が記載されていますが、こちらでも簡単に触れてみます。

拡張子自動判別

imgixの画像URLパラメータに auto=format でjpegの画像もブラウザによってwebpなどに変換してくれます

自動圧縮設定

imgixの画像URLパラメータに auto=compress で自動で圧縮してくれます

ロスレス圧縮

ロスレス圧縮が可能な画像に対して、 lossless=0 とすれば、ロスレス圧縮されます lossless=1 ではないというのが地味に罠でした。

ストレージサービス連携(Amazon S3/Google Cloud Storage)

画像の移行をする上で、これが大変ありがたかったです。 imgixではストレージサービスを指定するだけで画像のの最適化・配信ができます。

一休ではAmazon S3に画像コンテンツを格納しているので、 S3バケットの指定と、Access Key ID/Secret Access Keyを設定すれば、 imgixから画像が参照できるというところまでできました。

既存資産(一休でいうS3)をそのまま使えたので画像移行がスムーズに行うことができました。

before:S3バケットをみるCDNサービスを

f:id:akasakas:20180624210008p:plain

after:imgixに切り替えたというイメージです

f:id:akasakas:20180624210107p:plain

URLの変更はありましたが、そこは気合いと根性でアプリケーション側を直しました。

ストレージサービス連携はS3だけでなく、Google Cloud Storageにも対応しています。

imgix導入の効果

PageSpeed Insightsのスコアは宿泊・レストラン共に大きく改善されたのがわかります。

宿泊

before(再掲)

f:id:akasakas:20180624205650p:plain

after

f:id:akasakas:20180624210352p:plain

beforeのキャプチャについては2018/01/19時点のもので、厳密にいうと画像最適化だけでなく、他にもいろんなボトルネックがありましたが、スコアが低い大きな原因は画像最適化の部分でした。imgixの導入で20〜30点ほどスコアが改善されました。

レストラン

before(再掲)

f:id:akasakas:20180624205710p:plain

after

f:id:akasakas:20180624210737p:plain

画像転送量

ある画面でimgix導入前後の画像転送量比較が以下になります。

before(imgix導入前)

f:id:akasakas:20180625101726p:plain

after(imgix導入後)

f:id:akasakas:20180625101748p:plain

10MB近く削減されています。 imgix導入前は全く最適化できてませんでした。 そもそも10MB越えの画面はどうなんだ?という疑問もありますが。

上記の例が最も画像転送量を削減できましたが、他の画面でも軒並み画像転送量を抑えることができたので、導入の効果は非常に高かったと感じます。

imgix導入をする上で大変だったこと

技術面

  • 既存の資材(S3)を使えたので、切り替えが楽だった
  • URLの変更はあったけど、そこは気合いと根性で直した

ので、正直なところ技術面で大変だったことはなかったです。

技術面以外

実はこっちの方が大変でした

英語でサポートと契約などのやり取り

imgixは日本の代理店がありません。 最初にサポートに確認したら「ない!!!」って言われて、そのメールをそっ閉じしたのは今思えば、懐かしいです。 英語が堪能な帰国子女さんのお力を借りて、なんとかVolume Pricingの交渉ができました。

料金見積もり

見積もりが難しかったです。 実際に1ヶ月フリーで使わせてくれて、そこで正確な見積もりをとりました。 imgixのサポートは丁寧で優しかったです。

導入の承認

CDN費用のコストカット+技術的な改善ができることを説明して、導入の承認を得ることができました。

これから画像最適化を考える人たちへ

実際にimgixを導入しましたが、自分の進め方がよくなかったと思う部分が多々ありました。 反省/振り返りを含めて、どうやって導入を進めればよかったかというのを考えてみました。 あまりテッキーな話ではないです。仕事の進め方的な話がメインです。

ポイント

imgixに限らず、導入効果とコストカットの説明ができれば、導入できると思いました。

技術選定

候補としては以下の3つがあげられるのかなと思います

  1. imgix
  2. Cloudinary
  3. Fastly Image Optimizer

技術比較

一休の場合、画像最適化をポイントとしました。 imgix や Cloudinary の方が画像最適化を主眼に置いたサービスであるため使いやすい印象を覚えました。

移行難度

既存資産(S3や一休の画像配信構成)をそのまま使えるという点でimgixの方が移行をスムーズに進めることができるという印象がありました。 なので、この時点でほぼほぼimgixに決めてました

移行コスト

正直、imgixの見積もりしかとってなかったです。 理想は3つそれぞれ見積もりを出して、比較するべきだったというのが反省点です。

imgixの導入コストが既存より安いor同等であればOKかなと思ってました。 技術的改善が見込めれば、導入前後のコストが同等でも問題ないだろうという考えがあったからです。

その他細かい話

あと細かいところで 特定画面だけimgixを部分導入して、詳細なBefore/Afterの効果比較を実施し CDN費用のコストカット+技術的な改善(画像最適化)ができることを説明して、導入の承認を得ることができました。

まとめと感想

imgixを導入して、画像最適化&サイトスピードを改善ができました。

モダンなインフラに乗っかると、割と簡単にベストプラクティスを実践できるのだなというのが正直な感想です。 既存の環境でチューニングをがんばっていた諸氏には感謝をしつつ、未来のためにはどんどん新システムに塗り替えていきたいですね。

これから画像最適化を考えているエンジニアの方々にとって、この記事が少しでも参考になれば幸いです。

諸注意をもう一度

imgixを導入して、画像最適化という面でサイトスピード改善につながりましたが、 サイトスピードという観点で一休が抱えている課題はまだまだあります。 imgixを導入すれば、サイトスピードは万事解決!!!という話ではありませんので、悪しからず。

おまけ

レストランの導入が想定よりも遥かに速かった

当初は宿泊のみ先行して対応するという話で進めてて、レストランの対応は冬ぐらいになる

はずでしたが、

やっぱりモダンな技術とその効果が気になって、飛びついたご様子です。

技術調査・契約・見積もりなどのめんどくさいことをやって、慎重に事を進めてきた自分としては おいしいところだけをかっさらったレストランのエンジニアをみて、イラッときたのは内緒です。

でも、レストランのサービスが改善できたのでOKです

最後に

imgix移行に協力して頂いた方々に感謝

  • インフラ面で協力してくれたエンジニア
  • 英語のやりとりが発生した中で、サポートして頂いた帰国子女の営業さん
  • 画像の変更で作業ワークフロー上、大きな影響を受けたにも関わらず、ご理解とご協力を頂いたデザインさん

この場を借りて、御礼申し上げます。

参考

一休のETL処理をAirflowで再構築しました

一休のデータサイエンス部に所属しています小島です。

以前データ分析基盤の構築で記事を上げていましたが、今回はETL*1周りの話をしようと思います。 user-first.ikyu.co.jp

今回ETLのツールとして導入したのはAirflowというツールです。 2017年のアドベントカレンダーでも紹介させていただきました。 一休のデータフローをAirflowを使って実行してみる

一休のETLの現状について

一休のETL周りは以下の画像のようになっていました。 f:id:kojimakohei2:20180607154849p:plain

課題

  • ETLの処理時間が伸びた(出社後も処理が続いていた)
  • エラーのリカバリ作業に時間がかかる(ログが確認しにくい, サーバーに入って作業しなければいけない)
  • 複雑な依存関係の定義がしにくい(どれとどれが依存しているかわからない)
  • リソース負荷(全て並列で実行していた)
  • 処理毎のボトルネックが把握できない

ツールの問題というよりは正しくツールを使えていないことが原因でしたが、上記の理由でDigdagをAirflowに移行することを決定しました。

なぜAirflowにしたか

  • 処理が複雑にかける
  • 管理画面の依存関係のグラフと、tree形式での表示がわかりやすい
  • Pythonで記述できるので、機械学習などと相性がいい
  • 管理画面から簡単に再実行できる

Airflowの導入した結果できるようになったこと

  • ETLの時間が把握できるので、ボトルネックとなる処理に関して対処が可能になった
  • 再実行が管理画面から簡単にできるので、リカバリーが簡単になり運用に時間がかからなくなった
  • 並列数などを簡単に設定できるので、負荷が少なくなった
  • 依存関係がとても簡単に定義できる

ボトルネックとなる処理の検知

f:id:kojimakohei2:20180601122438p:plain めちゃめちゃ簡単にわかるようになりました。

対象日付のリカバリーが管理画面から簡単にできる

f:id:kojimakohei2:20180601122649p:plain

導入に当たって苦労したこと

  • ETL処理は意外と追加や変更が多いので開発しやすい環境を作ること
  • Airflowの制約があったこと

ETL処理は意外と追加や変更が多いので開発しやすい環境を作ること

ETLで肝なのはデータが正確にDWHに格納できるかというところです。 なので開発環境を作る必要があります。 ただ、開発環境を作成する際に考えなければいけないのがアウトプットするDBについてです。 インプットに関しては参照するだけなので、あまり考慮しなくていいのですが アウトプット用のDBをどのように用意するかを考え、結果的に開発環境ではdockerコンテナを立て件数を自動的に絞ることで開発ができるようにしています。 BigQueryや、S3などについてはコンテナを作れないので環境ごとにbucketを変更することで開発できるようにしています。

Airflowの制約があったこと

Airflowは1.9.0を使用していますが、内部的に全てUTCで動いています。 利用としてはこちら記事にわかりやすく書いてありましたが、タイムゾーンに依存しないように内部のシステムは全てUTCを使用するようにしているらしいです。 [AIRFLOW-289] Use datetime.utcnow() to keep airflow system independent - ASF JIRA

なので一休では表示側はUTCで処理部分は明示的にJST => UTCに変換しています。

今後ETLツールの利用方法

レコメンドなどに使用しているモデルなどについて機械学習の学習部分を自動で行なっていくことと、 ボトルネックになっている処理を改善し、ETLの最適化を図るようにしていこうと思います。

*1:Extract、Transform、Loadの略で、企業内に存在する複数のシステムからデータを抽出し、抽出したデータを変換/加工した上でデータウェアハウス等へ渡す処理

Send With Confidence Tour で一休.com の事例を話してきました

こんにちは。一休.com の宿泊開発基盤のお手伝いをしている id:shiba-yanです。

先週の話になるのですが、SendGrid の日本国内代理店の方から「一休.com での SendGrid を利用した運用について事例を話してほしい」とお願いされたので、簡単に出したが話してきました。

当日は SendGrid 本社から技術系の偉い人が来日して、世界初の話を含めていろいろと話してくれました。

様々な話がありましたが、中でも Geopod*1 が東京にもあることは知りませんでした。昔はシンガポールを使っていたみたいですが、今は日本からでも低レイテンシーで送信できるようになっているようでした。

おそらく開催ブログは公開されると思いますが、先にスライドを公開しておきます。メインは 2017 年に発生した、割と大規模な SendGrid 障害時の話です。

一休.com ではメールが非常に重要な役割を果たしているので、多少の送信エラーが発生したとしても、出来るだけリカバリーが行えるように実装しています。

しかし、障害発生時にはいろいろな問題が発覚したため、反省しつつ対応を行っていきました。

割と当たり前のことが出来てなかったと今では思いますが、当たり前を当たり前に行うのは非常に難しいことだと再認識し、今後の開発にも繋げていきたいと考えています。

参考

*1:SendGrid が世界中に用意しているエンドポイントの一つ

Renovateによるnpmパッケージ定期更新

一休.com・フロントエンドエンジニアの宇都宮です。

JavaScriptを使ったWeb開発では、様々なライブラリを使います。開発の活発なライブラリであれば、毎週のようにバージョンアップが行われます。ライブラリのバージョン更新は、それを行ったからといって価値に直結するわけではありません。しかし、以下のような理由から、一定の頻度での定期更新が必要です。

  1. バージョンアップに追従しないと、古いバージョンにロックインされる
  2. 差分が大きいバージョンアップはリスクが高い
  3. ライブラリに脆弱性が見つかった際は速やかにバージョンアップが必要

本記事では、JavaScriptライブラリ管理の標準的ツールであるnpmと、GitHub AppのRenovateを使用した、ライブラリを定期的に更新する仕組みの作り方について解説します。

npmによるパッケージ管理

npmは、JavaScriptライブラリの管理ツールです。

npmでは、使用するライブラリの名前とバージョンをpackage.jsonというファイルで管理します。

以下のように、dependencies(本番環境で使用するライブラリ)と、devDependencies(開発環境でのみ使用するライブラリ)を分け、それぞれのライブラリ名と、バージョン番号を指定します。

{
  "dependencies": {
    "vue": "2.5.16"
  },
  "devDependencies": {
    "vue-loader": "14.2.2"
  }
}

npmパッケージのバージョン番号は原則としてsemverという仕様に基づいており、x.y.zのxがメジャー、yがマイナー、zがパッチバージョンを意味します。メジャーバージョンアップは後方互換性のない変更、マイナーは後方互換性のある新機能追加、パッチは後方互換性のあるバグフィックスです。

依存ライブラリのバージョンには、幅を持たせることが可能です。たとえば「^2.5.16」は、「2.5.16以上のバージョンで、3.0未満」という意味になります。この指定方法はライブラリ向きです。他からライブラリとして参照されることのないアプリケーションでは、バージョン番号を固定しても良いでしょう。

実は、package.jsonだけでは、インストールされるライブラリのバージョンが毎回同じになることは保証されません。vue 2.5.16をインストールしたとしても、vueが依存しているライブラリのバージョンまで、全て同じになるとは限らないからです。

そのため、全ての依存ライブラリのバージョンを記録したファイルを別途用意する必要があります。npm 5以上では、npm installの実行時にpackage-lock.jsonというファイルが生成されます。このファイルには、全ての依存ライブラリのバージョンが記録されます。package.jsonpackage-lock.jsonが存在する状態でnpm installを実行すれば、正確に同じバージョンのライブラリがインストールされます。

これはnpmコマンドの代替であるyarnでも同様で、yarn.lockに全ての依存ライブラリのバージョンが記録されています。

npmパッケージの更新手順

更新の必要なnpmパッケージを探すには、npm outdatedまたはyarn outdatedコマンドを使用します。

yarn outdatedは、semverに基づいてメジャー/マイナー/パッチの分類を行う等、よりリッチな機能を備えています。個人的にはyarn outdatedを好んで使っています。

バージョンアップの必要なライブラリを見つけたら、yarn upgrade vue 等で、ライブラリのバージョンを更新します。あとは動作確認して、リリースするだけです。

簡単そうに書きましたが、実際のところ、npmパッケージの更新は面倒です。バージョンアップしても問題ないか確認するには、CHANGELOGの確認が欠かせません。

一休の宿泊予約サービス開発チームでは、週に1回程度の頻度で、以下のようなプルリクエストを作成して、npmパッケージの定期更新を行っていました。

f:id:ryo-utsunomiya:20180427145703p:plain:w320

この作業は地味に面倒で、毎週1時間程度の時間をアップデートに割いていました。そこで、パッケージ更新作業の省力化を図るための手段をいくつか検討した結果、Renovateの導入を決めました。

Renovateとは

Renovateは、GitHub上で動作するアプリ(bot)です。Renovateを導入したリポジトリでは、以下のようなプルリクエストが自動的に作成されます。

f:id:ryo-utsunomiya:20180501110718p:plain:w320

github.com

↑のスクリーンショットは、yarnのリポジトリから取得しています。Renovateは、yarnをはじめとした様々なオープンソースプロジェクトや、Uber等の企業まで、幅広く利用されています。

publicリポジトリでは無料、organization + privateリポジトリでは$15/month~で使えます。更新の必要なライブラリの確認 => CHANGELOGまとめ => プルリク作成という定型作業を代わりに行ってくれるので、十分に元が取れる価格だと思います。

また、Renovate本体はOSSであり、自前でホスティングすることも可能です。

Renovateを導入する

RenovateはGitHub Marketplaceから導入できます。Marketplaceからrenovateを探したら、まずはプランを選びましょう。ここでは、私が個人で開発しているオープンソースプロジェクトにRenovateを導入するため、「Open Source」を選んでいます。

f:id:ryo-utsunomiya:20180501112032p:plain:w320

次の画面では、有料プランの場合、支払い情報の入力が必要になります。会社のGitHubリポジトリに導入する際は、個人アカウントの請求情報を入力しないよう注意しましょう。

f:id:ryo-utsunomiya:20180501112420p:plain:w320

最後に、インストールするリポジトリを選択します。ここでは、「ryo-utsunomiya/amazon-block」を選択しています。

f:id:ryo-utsunomiya:20180501112659p:plain:w320

1時間ほど待つと、以下のようなOnboading Pull Requestが作成されます。

f:id:ryo-utsunomiya:20180501120900p:plain:w320

github.com

このプルリクでは、renovate.jsonというRenovateの設定ファイルをリポジトリに追加します。デフォルトでは以下のような動作をします。

  • メジャーバージョンアップは別々のプルリクに分割する
  • パッチ・マイナーアップデートを区別しない
  • アップデート用ブランチの名前には renovate/ プレフィックスをつける
  • マージは自動では行わない(人間が手動でマージする)
  • package.json が更新された場合にのみlockファイルを更新する
  • プルリクの作成は2時間毎に1回を上限とする
  • Renovateが作成したプルリクで、マージ/クローズされていないものの数が20を超えないようにする

このプルリクエストをマージすると、Renovate Botが動き始めます。

Pin Dependencies

パッケージのバージョンが固定されていない場合、以下のようなプルリクエストが作成されます。

f:id:ryo-utsunomiya:20180507191951p:plain:w320

github.com

ここでは、各パッケージのバージョン番号を固定(Pin)します。Pin Dependenciesプルリクをマージすると、パッケージの更新用のプルリク作成が始まります。

なお、依存ライブラリのバージョンをに幅をもたせたい場合は、以下のようにrenovate.jsonを変更すればOKです。

{
  "extends": [
    "config:base",
    ":preserveSemverRanges"
  ]
}

renovatebot.com

パッケージ更新の運用

Renovateによって、プルリクを作成するところまでは自動化されました。残りの作業は、安全を期して人間が行うようにしています。

具体的な手順は以下の通りです。毎週月曜の当番制にしています。

  1. 毎週月曜に、担当者がパッケージ更新の有無を確認する
  2. 更新があれば、パッケージ更新用ブランチを手元にpullして動作確認する
  3. 問題なければ、プルリクをマージする

マイナー/パッチアップデートはすぐに適用しています。一方、メジャーアップデートはソースコードの改修が必要なことが多いので、担当者をアサインして、一定の時間を確保してアップデートしています。

導入の効果

Renovateによる自動化によって、プルリク作成までの手順が標準化されたため、誰でもパッケージ更新作業が行えるようになったのが大きいです(従来は、有識者が気づいたときに更新するという体制でした)。

副次的な効果として、どのようなライブラリを使用しているか棚卸しできる、ライブラリの導入時に運用コストまで考慮できる、といったものもあります。

今後の課題

更新時の動作確認に属人性があるため、バグのある状態でリリースしてしまう危険性があります。

  • 自動テストの拡充
  • 動作確認手順の標準化

などによって、バグのある状態でのリリースを防ぐ対策を強化していきたいと考えています。

おまけ:マイナー/パッチアップデートをまとめる

デフォルト設定の場合、各パッケージ毎に更新プルリクが作成されます。しかし、週1回程度の更新頻度だと、マイナー/パッチアップデートは複数存在する状態になります。そこで、宿泊予約サービス開発チームでは、以下のようなrenovate.jsonの設定を行って、マイナー/パッチアップデートを1つのプルリクエストにまとめています。

{
  "extends": [
    "config:base"
  ],
  "minor": {
    "groupName": "all dependencies"
  }
}

↑のrenovate.jsonを使用すると、以下のようなプルリクエストが作成されます。

f:id:ryo-utsunomiya:20180507185539p:plain:w320

github.com