一休.com Developers Blog

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

一休.comにおけるAMP導入と今について

本記事は、一休.com Advent Calendar 2018の16日目の記事です。

qiita.com


デジタルマーケティング部で主に宿泊サイトを担当している田中(id:yakisoba6318)です。

今回は今年2月に導入したAMPについて導入時と今について紹介したいと思います。 内容に関しては主に宿泊サイトの話となります。

AMPとは?

AMPとは、Accelerated Mobile Pages (アクセラレイティッド・モバイル・ページ)と言い、主にGoogleが提唱しているモバイルウェブ高速化を目的としたプロジェクトです。

ここではそのAMPプロジェクトのオープンソースフレームワーク(AMP HTML)を指してAMPと言います。

www.ampproject.org

一休.comにおけるAMP導入動機

  • ランディングページのスピード改善
  • 速度改善による流入の増加
  • ユーザ体験向上

などいろいろありますが

簡潔に言うと ランディングページの最適化 です。

Choose a structured data feature  |  Search  |  Google Developers

一休.comのAMPページの紹介

現在一休で公開しているAMP対応ページとポイントを紹介します。

f:id:yakisoba6318:20181213171024p:plain
左からレストラン店舗ページ、宿泊施設ページ、宿泊観光ページ、宿泊特集ページ

レストラン店舗ページAMP

  • リッチカード x AMPで流入増
  • amp-list でプラン情報、割引率を動的表示

ランチ - ザ・ロビー - ザ・ペニンシュラ東京/コンチネンタルダイニング [一休.comレストラン]
*スマホからのみ確認できます

宿泊施設ページAMP

  • amp-list プラン情報・クチコミ表示
  • amp-access でクーポン表示

ザ・プリンス パークタワー東京 - 宿泊予約は[一休.com]
*スマホからのみ確認できます

宿泊観光ページAMP

  • レスポンシブ(Canonical / Responsive AMP )
  • amp-toolbox-optimizerを使ったページの最適化
  • まだまだ試験的なページで今は京都、金沢、箱根や一部温泉地のみですがこれからエリアを拡大予定

京都観光で行きたい名所!京都旅行におすすめ人気スポットランキング 19選【2018年】 - [一休.com]
*レスポンシブ対応のためPC・スマホどちらでも確認できます

宿泊特集ページ

  • amp-story実験的に公開中
  • 高級宿の素材をリッチに表示できるページ
  • ただ、日本では検索でAMPViewとしての表示はまだ出来てない
  • 今後の展開を期待

ジ・ウザテラス ビーチクラブヴィラズ 新規開業[一休.com]
*amp-storyがPC表示に対応しているためPC・スマホどちらでも確認できます

AMPコンポーネントの活用例

AMPページを作るにあたって欠かせないのが公開されてるコンポーネントです。 日々いろんなコンポートの開発が進んでいてAMPで出来ることが増えてきています。 今回はその一部を使った利用例を紹介します。

www.ampproject.org

動的なカルーセルを出したい

<amp-list 
    src="フォトギャラリーAPI"
    height=”254”
    layout=”fixed-height”
    single-item>
    <template type="amp-mustache">
      <amp-carousel
                    controls
                    loop
                    height="254"
                    layout="fixed-height"
                    type="slides">
        {{#photos}}
        <div>
          <amp-img src="{{ImageUrl}}"
                   layout="fill"
                   alt="{{ImageAlt}}"></amp-img>
        </div> 
        {{/photos}}
      </amp-carousel>
    </template>
</amp-list>

amp-listはamp上でxhrできるのでAPIからリスト取得、受け取ったリストをmustache形式でループさせて画像を動的に生成することができます。

https://www.ampproject.org/docs/reference/components/amp-list https://www.ampproject.org/docs/reference/components/amp-mustache

カルーセルの画像枚数を表示させたい

<amp-state id="GuideTopPhotoUrl" src="フォトギャラリーAPI"></amp-state>
<amp-carousel
    controls
    loop
    height="254"
    layout="fixed-height"
    type="slides"
    on="slideChange:AMP.setState({
      GuideState: {
        header: {
          selectedSlide:event.index,
          Is_slide:true
        }
      }
    })">
<!-- 略 -->
</amp-carousel>
<div>
    <span
    [text]="!GuideState.header.is_slide ? '' : 
(GuideState.header.selectedSlide + 1)  +
 '/' +
(GuideTopPhotoUrl.items.photos.length + 1) ">
    </span>
</div>

on属性にslideChangeをトリガーを設定してsetStateを更新、更新した情報をspanにbindすることで動的に枚数を更新することができます。

https://www.ampproject.org/docs/reference/components/amp-carousel https://github.com/ampproject/amphtml/blob/14153fe212a80aeb6f1e7a7f14e4849cc228eba9/spec/amp-actions-and-events.md#L177

結局AMPはどうなの?

紹介したように既に多くのページをAMP化してきましたが効果どうだったのか、 いろいろ工夫が必要な点もあったのでそれを踏まえて紹介したいと思います。

サイトパフォーマンス

f:id:yakisoba6318:20181213183156p:plain
Before: AMP Visually Complete 4G
上から順に通常のスマホページ、AMPページ、Googleキャッシュ上のAMPページ

こちらは少し古いレポートですが、始めはAMPコンポーネントの使い過ぎやamp-stateの不要な更新が多かったため AMPの要件は満たしていたがユーザ体験が悪化していました。

なので対応として

  • Googleキャッシュからの配信の恩恵を受けるためリアルタイム性の不要なコンテンツはxhrで取得しない
  • amp-stateの更新をbindするのに時間がかかるためUI上での不要なstate管理を辞める

具体的な例として

  • フォトギャラリーのamp-listを辞める
  • 開閉のUIをamp-state + hidden属性からamp-accordionに変更

結果として

変更
After: AMP Visually Complete 4G
上から順に通常のスマホページ、AMPページ、Googleキャッシュ上のAMPページ

AMPがoriginのスマホページよりVisually Completeの面では早い状態まで持っていくことができました。

ちなみにTTFBはoriginもほぼ同じでしたがDOMContentLoadで大きく遅れを取った結果このような数字になっていました(web page test)

ユーザ体験

amp-listで施設のプランを取得していますが、導入当初は読み込み後に展開するようにしていました。

変更前

ただ、これだとページ下部のコンテンツ(アクセスや施設情報など)閲覧中にリフローによって画面がずれ込み体験が悪くなっていました。

そこであらかじめスケルトンスクリーンを用意して高さを確保することでユーザ体験を損なわないように対応しました。(AMP開発者の方からのアドバイス)

変更後

これで表示が遅れてもユーザ体験に影響が少ないページになりました。

今後の方針

一休.comでの今後のAMP展開については以下の検討をしています。

  • amp-storyの活用事例を増やす
  • AMP上でjsが動くamp-scriptを使ったコンテンツのリッチ化
  • ITP2.0によるトラッキング対策(Safari対応)としてamppackagerやamp-toolbox-optimizerなどの使用による対応

digitalidentity.co.jp

まとめ

AMPのコンポーネントは容量用法を意識して使うのが良さそうだと感じました。

AMPページとしてのサイトの役割を考えつつ良質なユーザ体験と流入を獲得できるサービスをこれからも作っていきたいと思います。

AMPと聞くとスマホだけの印象のありますが、 PCページとしてのAMPやコンポーネントを個別にshadowDOMとしてページに取り入れることができたりと活用の幅はとても広いです。 今後の進化からも目が離せないですね!

明日は id:IganinTea さんの「 C#とWindows」です!

Ikyu Frontend Meetupを開催しました

この記事は一休.com アドベントカレンダーの14日目の記事です。

qiita.com


こんにちは。 id:kentana20 です。一休で宿泊サービスの開発をしています。 今日は一昨日の夜に実施したイベント「Ikyu Frontend Meetup」の様子をレポートしたいと思います。イベントページはこちら。

ikyu.connpass.com

年末の忙しい時期にもかかわらず、多くの方にご応募・ご参加いただきました。

f:id:kentana20:20181212191052j:plain

今回のイベントのきっかけ

過去に2度、一休ではテック系イベントをやっていましたが、「ぼちぼちまたイベントやりたいな〜」と思っていたときに、 id:supercalifragilisticexpiali が書いた

user-first.ikyu.co.jp

を見た他社のエンジニアの方から「情報交換しましょう」と複数問い合わせや依頼があったので、イベントにしてしまおう、と思って今回のMeetup開催に至りました。

セッションの内容

今回のイベントでは「一休.com / 一休レストランでのフロントエンド開発」をテーマに3本のセッションを行いました。

セッション1: 「JavaScript/Vue.js アプリケーションのパフォーマンスチューニング」

1本目のセッションは宿泊サービスのエンジニアである id:ryo-utsunomiya がJavaScript/Vue.jsのパフォーマンスチューニングについてお話しました。

f:id:kentana20:20181212191727j:plain
ryo-utsunomiya によるJavaScript/Vue.jsのパフォーマンスチューニングの事例

一休.comモバイルWebのホテルページを高速化した事例をもとに

  • Lighthouse, Calibreを使ったパフォーマンスの計測
  • 同業他社のページを参考にした目標設定
  • JavaScriptのチューニングのポイント

などをお話しました。

f:id:kentana20:20181212193014j:plain

セッション2: 「imgix導入で画像最適化とサイトスピード改善」

2本目のセッションでは、画像の最適化によるチューニングの事例を id:akasakas がお話しました。

f:id:kentana20:20181212194216j:plain
akasakasによる画像最適化事例のセッション

もともと自社(Image Magick)で画像のリサイズや切り抜きなどの加工処理をしていたところを、画像最適化/配信のSaaSであるimgixを導入して最適化したお話を

  • 導入前の課題整理
  • 技術選定の観点とimgixに意思決定したポイント
  • imgixの優れている点
  • 導入後の効果

などの流れでお話しました。課題 → 解決のための手法 → 解決後の成果が順を追って語られていて、とてもわかりやすいセッションでした。セッションの中でも語られていましたが、一休が提供するサービスにおいて画像はとても重要で、ユーザ体験に大きな影響を与える部分なので、最適化によってユーザ体験を向上できたことは本当に良い成果につながったと思っています。

f:id:kentana20:20181212195231j:plain

セッション3: 「一休.comレストランのスマートフォン検索ページがSPAになりました」

3本目のセッションでは、イベントのきっかけになったブログエントリをもとに id:supercalifragilisticexpiali が一休レストランモバイルWebのレストラン検索ページをSPAにした事例ををお話しました。

  • 「もっと使いやすく」「サクサク動くように」というプロダクトのニーズ
  • SEOを落とさないようにという集客面のニーズ
  • Atomic Designを意識したコンポーネント指向設計を取り入れて生産性を上げたいという技術面でのニーズ

といった多角的な観点から Vue.js, Nuxt.js, ITCSS を導入した事例を細部まで丁寧にお話しました。

f:id:kentana20:20181212195441j:plain

内容が濃く、参加者のみなさんも真剣に聞いてくださっていて、とても良いセッションでした。

パネルディスカッション / 懇親会

3本のセッション終了後はビール片手にパネルディスカッションと懇親会を行いました。

事前に参加者の方からいただいていた質問をベースにスピーカーと話したり、参加者から当日出た質問に答えるQ&A形式で進行しました。

f:id:kentana20:20181212204134j:plain f:id:kentana20:20181212205745j:plain f:id:kentana20:20181212215210j:plain

編集後記

過去2回は他社と合同で実施したMeetupイベントでしたが、今回は一休単独での開催だったので、参加者が集まるか、イベントに満足いただけるか、など不安な点はありましたが、まずまず満足いただけたようで、開催してよかったです。

一休.com / 一休レストランともに、まだまだフロントエンド開発でやりたいことはたくさんあるので、継続的にコツコツ改善を続けてユーザにとって使いやすいサービスを提供していきたいと思います。

明日はアドベントカレンダーはお休み、明後日16日は id:yakisoba6318 による「 一休.comにおけるAMP導入について」です。お楽しみに!

一休レストランの店舗ページをSPA化して Fastly で段階的リリースした話

この記事は一休.comアドベントカレンダー2018の13日目の記事です。

qiita.com


こんにちは。
今年の7月に入社したレストラン事業部の渥美です。
一休.com レストランにてフロントエンドとバックエンドの開発を行なっております。

この記事の概要

  • 店舗ページをSPA化した背景
    • 店舗ページリニューアル
    • プラン詳細ページのSPA化
  • Vue.js によるモーダルの実装方針
    • 事前ロード
    • モーダルの開閉
    • URLを動的に生成する
  • Fastly での段階的リリース
    • Fastly について
    • VCL の設定
    • Fastly Fiddle によるテスト
  • 今後の展望

店舗ページをSPA化した背景

店舗ページリニューアル

私が一休に入社する1ヶ月前、店舗の情報は複数のページにまたがっていました。 具体的には、店舗のページはプラン一覧や店舗情報、アクセスマップなどの情報が別々のタブに分かれており、ページ遷移が必要でした。
しかし、restaurant2*1へのリニューアルと共に、SPAとして生まれ変わったのでした。

f:id:atsumim:20181213113140p:plain
Before: 旧店舗ページ

f:id:atsumim:20181213113445p:plain
After: 新店舗ページ

プラン詳細ページのSPA化

その後入社した私はユーザ体験をさらに向上させるために、 独立していたプランの詳細ページのモーダル化を担当しました。 これにより、店舗ページ内で完結できる範囲が広がりより予約がしやすいUIになりました。

※現在も下記のプラン詳細ページは動いていますが、後述するようにモーダルへ移行予定です。

f:id:atsumim:20181211173120p:plain
Before: プラン詳細ページ
f:id:atsumim:20181211172225p:plain
After: プラン詳細モーダル

今回はこのモーダル化の実装について簡単にお話するとともに、
段階的リリースの取り組みについてご紹介します。

Vue.js によるモーダルの実装方針

一休レストランのフロントエンド開発では Vue.js が使われています。 モーダルの実装もVue.jsで制御しており、今回はその基本的な実装方針を紹介します。

大まかな処理は以下のとおりです。

// 店舗ページの Vue ファイル
<template>
  <!-- 略 -->
  <object-plan-item
    @mouseenter="preloadPlanDetail"
    @click:plan="openPlanDetail"
  />
  <composition-plan-detail-modal
    :activated="showPlanDetail"
    @update:activated="onClose"
  />
</template>
export default {
  // 略
  data() {
    return { showPlanDetail: false };
  },
  methods: {
    openPlanDetail(plan) {
      // ①モーダルの開閉処理
      this.showPlanDetail = true;
      // ②URL の動的生成
      global.history.pushState(null, '', this.planDetailUrl(plan));
    },
    onClose() {
      // ①モーダルの開閉処理
      this.showPlanDetail = false;
      // ②URL の動的生成
      global.history.pushState(null, '', `/${this.restaurant.id}/`);
    },
    preloadPlanDetail(plan) {
     // ③プラン詳細情報の先読み
    },
    planDetailUrl(plan) {
      // URL を生成し返却する
    },
  }
}

<object-plan-item> が各プラン項目のコンポーネント、
<composition-plan-detail-modal> がモーダルのコンポーネントです。

①モーダルの開閉処理

モーダルの表示状態は showPlanDetail を定義して、この boolean 値で管理しています。 単純ですが、プランの「詳細・予約」ボタンがクリックされたときに showPlanDetail = true,
モーダルの背景がクリックされたときに showPlanDetail = false となり、表示が切り替わります。

②URL の動的生成

モーダルを開いたときに URL を動的に生成しています。
History API を利用して、

global.history.pushState(null, '', this.planDetailUrl({ plan, time }));

とすることでモーダルを開いたときに URL が更新され、履歴にも追加するようにしています。
モーダルを閉じるときは、

global.history.pushState(null, '', `/${this.restaurant.id}/`);

として店舗ページの URL に戻します。

③プランの詳細情報を先読み

プランの詳細情報を先に読み込む処理を書いています。 実際には <object-plan-item> から mouseenter のイベントが emit され、preloadPlanDetail が発火されます。
ユーザからすると、プラン一覧の項目をマウスオーバーすると同時にプリロードが走るので モーダルを開いたときにはシームレスにプラン詳細が表示される体験を提供できます。 (※ローディングが完了していない場合はローディング画像が表示されます)


勿論他にも処理はありますが、以上がモーダル化の大まかな実装となります。
Vue.js を使うことでモーダルの開閉状態や、先読みしたプランの情報をモーダルに受け渡せるのは便利な点でした。

Fastly での段階的リリース

今回モーダル化したページは予約導線に直結しているということもあり、 リリース当初は特定の店舗のみに限定公開しました。 この限定公開に Fastly を使いました。

Fastly について

一休ではリバースプロキシとして Fastly を利用しています。 Fastly は設定言語の VCL を書くことでリダイレクトの処理や、レスポンスの制御を簡単に行うことができます。

今回は、特定の店舗ページにアクセスしたときのみ、?modal_enabled=1というクエリパラメータを付与するように VCLの設定を行いました。 そして、このパラメータが付与されている場合はモーダルを開くようにアプリケーション側でハンドリングしました。

流れとしては下記のようなイメージです。

  • 特定店舗のURLにアクセスする
  • Fastly で ?modal_enabled=1 が付与されたURLにリダイレクトさせる
  • ?modal_enabled=1 が存在するときのみアプリケーション側でモーダルを開く

メリット

Fastly での段階的リリースのメリットとしては下記のようなものがあります。

  • 限定リリースができる
    • 今回の主目的である、URLに応じた限定リリースが可能です。
  • Fastly のデプロイが高速
    • VCL の本番環境への反映が、CIのテストを含めて2分ほどで完了します。
  • 切り戻しが容易
    • 上記のデプロイが高速であることと関係するのですが、なにかトラブルがあった場合の切り戻しが容易です。 アプリケーションのリリースは20分程かかるのでこれはありがたいポイントです。

それまで他の手法での段階的リリースは行われていたものの、
Fastly を使った段階的リリースは初めての試みでした。

VCL の設定

さて、実際に VCL で設定した内容をご紹介します。(必要に応じて変数名等を変えています)

table test_ids {
  "100000": "true",
}

sub vcl_recv {
#FASTLY recv
  if (req.url.qs !~ "modal_enabled=" &&
    req.url.path ~ "^\/(\d{6})" &&
    table.lookup(test_ids, re.group.1)
  ) {
    error 700; # 内部的にerror statusを飛ばすことでリダイレクトを行う
  }
}

sub vcl_error {
#FASTLY error
  if (obj.status == 700) {
    set obj.http.Location  = "https://" req.http.host req.url.path "?modal_enabled=1" if(req.url.qs == "", "", "&" req.url.qs);
    set obj.status = 307;
    set obj.response = "Temporary Redirect";
    return (deliver);
  }
}

table の項目で特定店舗のIDを指定しています。

ここで気になった方もいるかも知れませんが、 VCL の設定では内部的に error status を飛ばすことでリダイレクトを行います。

Fastly Fiddle によるテスト

この VCL の設定を行うにあたり、Fastly Fiddle というサービスを利用しました。これは VCL のコードをオンラインで簡単にテストできるサービスです。 Fastly Labsが実験的に運用しているようです。

f:id:atsumim:20181213141040p:plain
Fastly Fiddle の画面

Fastly Fiddle は実行したコードを他のユーザと共有することもできます。 上記のコードをサンプルとして用意しました。 ページにアクセスしたら、右上の[RUN]ボタンを押してみてください。 table test_ids に含まれるIDに対応するURL(/100000)に対してリクエストを送ると、リダイレクトが発生することがわかります。

f:id:atsumim:20181213140910p:plain
リダイレクト結果

反対に、test_idsに含まれないIDに対応するURL(例えば /200000)にリクエストを送ってもリダイレクトされません。 是非Send a requestの項目を/100000から変更して遊んでみてください。

このようにして、Fastly による特定店舗のみの段階的リリースができるようになりました。

今後の展望

今回のモーダル実装で店舗のページのSPA化が進みました。 しかし、モーダルのURLに直接アクセスしたときは、未だ以前のプラン詳細ページに遷移するようになっています。
今後はこのような導線にもモーダルが開くように対応していきます。

また、今回 Fastly での段階的リリースの仕組みも整ったので、今後の機能追加でどんどん活用していきたいと思います。

明日は id:kentana20 さんの 12/12 Ikyu Frontend Meetup 開催レポート です! お楽しみに!

*1:新しいアーキテクチャによる一休.com レストランのWebアプリケーションを指す。

一休のUI/UXデザイナーとして私がやっている4つのこと

この記事は一休.comアドベントカレンダー2018の11日目です。

qiita.com


はじめに

デザイナーと聞いて、皆さんはどのような人を想像しますか?
「見た目を美しくかっこよく作れる人」、「ビジュアルデザインの専門家」というイメージを持たれている方も多いのではないでしょうか?
デザイナーのアウトプットだけを見ればその通りですが、アウトプットに至るまでのデザインの考え方 や取り組み方、役割がここ数年で変わってきています。
以前は、情報を整理して色や形を使いこなすビジュアルデザインがデザイナーの中心的な業務だと考えられていました。
しかし、Webデザインの標準化が進み、類似サービスの乱立も増え、ユーザーから好まれるサービスかどうかが重視されるようになりました。
より効果的なデザインを実現するために、事業戦略やサービスの現状、マーケットニーズに目を向け、デザインに活かそうとする動きが活発になりました。

とはいえ、理屈は理解できても具体的に何をすれば良いのかがわかりにくいと感じている方も多いはず。そこで今回は、一例として私が一休のデザイナーとして取り組んでいることをご紹介したいと思います。

Index
1) トーンマナ―などのルールは最低限にして都度考える
2) 自分たちが目指すデザインの方針を明文化する
3) デザイナーが自由に発散し、少し先の将来について考える場を設ける
4) デザインに取り掛かる前に考える

トーンマナ―などのルールは最低限にして都度考える

一休のレストラン事業のトーンマナーで定義しているのは、複数のページで登場する基本的なUIパーツとタイポグラフィの基準のみです。
あまり詳細まで定義するとルールに縛られて考える機会が失われる可能性がありますし、ドキュメントの整備に手間をかけるのも得策ではありません。
敢えてルールを決めすぎず、状況に応じてデザイナーが自分の頭で考え、最良と思うものを画面に反映させて効果を見てみる。
工夫できる余地を残すことでサービス改良の提案がしやすくなり、変化の促進につながると考えました。
ただし、都度デザインすることがユーザーのためにもサービスのためにもならない要素についてはVueコンポーネントに定義して、デザイン、コーディングの効率化とサービスの使いやすさの両方の実現を目指すことにしました。

目指したいデザインの方針を明文化して共有する

デザインをしていてしばしば頭を悩ませるのは正解が複数あることです。
デザインの善し悪しは判断軸によって変わりますし、人によって異なる感想を持ちます。
チームでデザインを考える場合、個々に全く異なるコンセプトで考えてしまい、サービス全体での一貫性が損なわれることもあります。
かといって、トーンマナ―を充実させれば良いかと言うと前述の通り、そうではないと思っています。

そこで、サービスデザインコンセプトというものを明文化することにしました。
ルールではなくコンセプト、つまり方向性です。
具体的にあれをする、これをするを書くわけではなく、実現したい景色を3つ~5つの項目に落とし込み、自分たちが作っていくプロダクトの目標として据えました。
そして、この目標にマッチしていない箇所について、「こうしたらどうだろう?」「こういう方法もあるかも」「どうやって進めようか?」といったことを、次項で紹介する「UI/UXデザイン語り場」で話し合っています。

一休レストラン事業のサービスデザインコンセプト(抜粋)

  • 楽しい、予定がなくても見たくなるサービスであること
  • 最短で目的を達成できるサービスであること
  • 直感的に操作ができ、考える必要がないサービス
  • システマチックになりすぎず人間味があるサービス

デザイナーが自由に発散し、少し先の将来について考える場を設ける

目前のタスク消化であっという間に数か月が過ぎてしまった、という経験はありませんか?
また、自分の考えが正しいかわからず提案しても良いものかと躊躇してしまう、ということはありませんか?
これらの課題をチームで解消して、小さなことでも良いので実行を積み重ねていきたい、という想いから「UI/UXデザイン語り場」というミーティングを実施することにしました。
週に一時間、今抱えているタスクから離れてこれからやっていきたいこと、やってみたいことについて考え、自由に話すための時間です。

提案した内容をなんでもやれば良いわけではありませんが、答えのないことを行動前にあれこれ考えすぎても意味がありません。
そして最も避けるべきは「何もしないこと」です。
何もしなければ業績は低下していきます。
よほど的外れなことでない限り、まずやってみる、やってみた結果を踏まえて次の打ち手を考える。
この繰り返しがサービス改善には不可欠です。

デザインに取り掛かる前に考える

「こういう機能を付けたいから画面デザインをください」と言われた時に、いきなりデザインツールに向かってしまうのは適切ではありません。
その前に、まずは企画者の話に耳を傾け、実現したいこと、手段、現時点での仮説、リスクなどを書き出し、前提情報を整理します。
そして、それらの前提を元にターゲットの深掘りをしていきます。
データからわかることと、データからはわからない不確かなことに分けてまとめると良いでしょう。
ヒアリングやデータ解析、他社分析の作業は必ずしもデザイナーが担当する必要はありません。
自分でできるなら自分でやれば良いですし、できない場合には得意な人の協力を得ても良いと思います。
重要なことは事実と可能性をしっかりと把握することです。

一通りまとめ終えたら、いくつかのターゲットグループのユーザー像を想像できるようにしてから、いざデザインに取り組みます。
このプロセスを踏んだ時と踏んでいない時とで、デザインを提案する際の説得力も自信も、提案内容も変わるはずです。
多少手間でもこのプロセスを踏んで予測し、実施後にどうなったのかをノウハウとして蓄積すると、アイデアの引き出しが増やせると思います。

おわりに

いかがだったでしょうか?
「そりゃそうでしょ!」と思う内容が多かったと思いますが、13年間ECサイトのデザインに携わってきて気付いたのは、難しく考える必要はないということでした。
シンプルに考える、当たり前のことを当たり前にちゃんとやってみた結果、見えてくることも多々あります。
少しでも参考になれば幸いです。

最後までお読みいただき、ありがとうございました。
次回は@atsumim の 「プラン詳細ページのモーダル化を Fastly で段階的リリースした話」です。お楽しみに!

Storybook を自作して「フロントエンドビルドが遅い問題」に立ち向かう

この記事は一休.comアドベントカレンダー2018の10日目です。

qiita.com


こんにちは。レストラン事業部の所澤です。 WEBアプリケーションエンジニアとしてフロント/サーバー問わず機能開発を行っています。

今回は一休.com レストランの旧アプリケーションのフロントエンド開発環境改善についてお話します。

※ この記事の執筆時点では以下の内容は master に取り込まれていません。同僚のフロントエンドエンジニア(ガチ勢)から何か指摘があったら追記します。

この記事の概要

  • 一休.com レストランの旧WEBアプリケーション(以下 restaurant1 )はなぜかフロントエンドビルドが超遅い。
  • Storybook のようなアプリと切り離された、高速でビルドできる環境があればもっと快適に開発できるのではないか?
  • Storybook だと vue-devtools が使えないので Storybook (の最低限の機能を持つ)小さいアプリケーションを作ってみた。

一休.com レストランの開発環境

新アーキテクチャへの移行状況

何度かこのブログでも取り上げていますが、現在(2018年12月)、一休.com レストランは新旧ふたつのWEBアプリケーションが並行する形で運用されています。 古くから稼働している VBScript で書かれたアプリケーションは restaurant1、リニューアル後の Python で書かれた新しいアプリケーションは restaurant2 と呼ばれています。 着々と restaurant2 への移行は順調に進んでいますが、依然として restaurant1 の上に乗っている部分も多く残っています。

f:id:shozawa:20181210145104p:plain:w300

たとえばスマートフォン版の店舗トップ画面は機能追加の機会が多いページですが、まだ restaurant2 への移行が完了していません。 新アーキテクチャだけを触ればOK、という状態まではまだ少しかかりそうだというのが現状です。

restaurant1 のフロントエンドについて

さて、"レガシー"などと言ってしまいましたが、実はフロントエンドに限って言えば旧アプリケーションもそこまで古くはありません。 jQueryでゴリゴリ書かれたページもありますが、主要ページに関しては ES2015+ と Vue.js で開発できる環境が整っています。 restaurant2 のピカピカのコードに比べると若干見劣りはしますが十分モダンだと言っていいでしょう。

問題は、フロントエンドビルドが とてつもなく遅い ことです。 フルビルドに時間がかかることに関して良いとしても watch しているときの差分ビルドも 1分以上 かかります。(Core i7 の開発機で)

遅い原因は特定できていないのですが、

  • アセットの肥大化
  • そもそも Windows だとビルドが遅い( restaurant1 は ASP で書かれているので Windows 必須です)
  • セキュリティのために入れているファイル監視ソフトの相性の問題

など、いろいろな可能性が考えられます。

さて本来であれば根本原因を特定して解決するのが筋ですが、どうにも問題の切り分けがうまくいかないので別の解決方法を考えてみます。

restaurant1 に Storybook を導入してみる

いままではユニットテストを書いてブラウザを使った動作確認の回数を減らし、なるべくこの問題を意識しなくて済むように気をつけていました。 しかしやはり新規のコンポーネントを0から作るときやデザインの微調整をする際はどうしてもビルドの遅さが気になります。

restaurant2 や宿泊のサイトでは 既に Storybook を導入済みだったこともあり、"開発用の Playground として" restaurant1 にも Storybook を入れてみようと試してみました。

f:id:shozawa:20181210150410p:plain:w300

※ restaurant2 では"デザイナーとの協働をスムーズにする" という目的で Storybook が活用されています。詳細はまたいつか。

Storybook で十分、か?

さて冒頭でも書きましたが結局 Storybook を導入することは見送りました。理由は vue-devtools が使えないからです。

今回はデザインシステムとしてではなく、開発用の Playground として Storybook を使いたいので開発ツールがうまく動かない点は致命的な問題です。

(最初に気付けよ、という感じですが私自身はそれまであまりちゃんと Storybook を使ったことなかったので...)

Github の issue を見るとワークアラウンドがありそうですが...。すでに導入でだいぶ消耗していて、これ以上の yak shaving をする気は起きなかったので別の方法を検討することにしました。

(追記: 無理やりですが iframe を別タブで開くと vue-devtools が使えます)

Storybook 相当のアプリケーションを作ってみる

よく考えれば今回の用途に限って言えば Storybook の全機能が使える必要はありません。やりたいことは至ってシンプルです。

要求・仕様

  • アプリケーションと独立した環境で動作確認しながらコンポーネントを開発できる
  • vue-devtools が使える
  • LiveReload
  • Mac でも開発できる
  • コードのコピペなどせずに restaurant1 のコードがそのまま確認できる
  • Storybook 風にストーリーが書ける

この仕様を満たす小さなアプリケーションを書いてみることにしました。 ※ あくまで Playground として使い、最終確認はアプリケーションに組み込んでやる前提。

まずは使い方をご紹介

  • stories.js にStorybook 風のAPIでストーリーを追加していく
storiesOf('sample')
  .add('hello', h => h('h3', ['hello. this is my story.'], {}));

storiesOf('DatePicker')
  .add('select', h => h(CustomDatePicker, { props: { value: '2018-12-01' } }));
  • restaurant1 内で yarn play を実行してサーバーを起動

f:id:shozawa:20181210144606g:plain

UIがダサいのはご容赦ください...。

これだけですが、「アプリケーションと切り離された環境でコンポーネントを開発する」ということは実現できています。

こだわりポイント

ここからは蛇足な気がしますが、せっかくなのでこだわりポイントをご紹介します。

  • とにかくシンプルに!
  • 新しいライブラリを追加しない
  • Storybook like な API

以上の3点を心がけて実装しました。

このツールを使う人やツールの機能を拡張しようとしてコードを読む人の負担が最小限になるように気をつけています。

まずは何よりコードが小さく、シンプルになるように心がけました。また不用意に新しいライブラリを追加すると、でコードを読んだ人に負担もかけてしまうので restaurant1 に追加済みのライブラリのみを使用することにしました。

webpack-dev-server でホスト

今回はアプリケーションの中に playground ディレクトリを作りそこに関連ファイルを格納し、 webpack-dev-server でホストしています。 今回のモチベーションが「ビルドの速度改善」なので 本アプリの webpack の設定を使い回すことはせずに新しく playground 以下に数十行のシンプルな設定ファイル( playground/webpack.config.js )を追加しました。

// package.json
{
  // 略
  "scripts": {
    // 略
    "play": "webpack-dev-server --config playground/webpack.config.js"
  }
}
// playground/webpack.config.js
{
  // 略
  devServer: {
    contentBase: path.resolve(__dirname, 'dist'),
    port: 9000,
  },
}

9000番ポートで dist ディレクトリの内容をホストします。

storiesOf 関数

storiesOf 関数の実装はこれだけです。 storiesOf と add でオブジェクトにコンポーネントを登録していきます。

// playground.js
const stories = {}; // Component を格納
const tableOfContents = {}; // ナビゲーション用

// TODO: HMR
function storiesOf(title) {
  tableOfContents[title] = tableOfContents[title] || {};
  return {
    add(scenario, value) {
      const key = `${title}:${scenario}`;
      tableOfContents[title][scenario] = key;
      playground[key] = { render: value };
      return this;
    },
  };
}

const getStories = () => stories;
const getTableOfContents = () => tableOfContents;

export {
  storiesOf,
  getStories,
  getTableOfContents,
};

※ vue-play の実装を参考にさせていただきました。

github.com

プレビュー機能

import { getStories } from './playground';
export default {
  name: 'PlaygroundPreview',
  data() {
    return {
      scenario: '',
      stories: {},
    };
  },
  methods: {
    setScenario() {
      const hash = decodeURI(window.location.hash);
      this.scenario = hash.replace('#', '');
    },
  },
  computed: {
    current() {
      return this.stories[this.scenario];
    },
  },
  created() {
    this.stories = getStories();
    this.setScenario();
    window.addEventListener('hashchange', this.setScenario);
  },
  render(h) {
    return h(this.current, [], {});
  },
};

ストーリーとして登録したコンポーネントのプレビュー部分です。 URL のハッシュ部分にコンポーネントのキーを入れるようにし、ハッシュの変更によってプレビューされるコンポーネントが切り替わるようにしています。

http://localhost:9000/#{ストーリーのタイトル}:{ストーリーの小見出し}

今後の展望

webpack の設定ファイルを含め、250行程度のコードでここまでの内容が実現できました。

シンプルな実装で最低限やりたいことはできた、と思っています。

  • WEBフォントの読み込みができていない
  • Vuex と連携するコンポーネントの動作確認ができない
  • DefinePlugin の対応
  • async/await を使っているコードでエラーが出るので webpack の設定を見直し
  • UIがイケてない

などすでにいくつか課題は見つかっているのですが、プレゼンテーションだけに責任を持つシンプルなコンポーネントの開発であれば十分に活用できるかと思います。

実はデモ用にいくつか実際に使われているコンポーネントを追加しようと思ったのですが、ほとんどの主要コンポーネントが Vuex に依存していてうまく追加できませでした。

よく言われていることですが、あらためて Presentation Component と Container Component の分離が重要ですね。

もう少しブラッシュアップして、良さそうであれば master に取り込もうと思います。

Rundeck in practice [運用編]

この記事は一休.comアドベントカレンダー2018の9日目です。

qiita.com


導入編に続き、運用編です。
ここ2年間 Rundeckを運用してきて発生したトラブルとその対処について書きます。
※この記事で言及するRundeckはバージョン2.6.9です。
トラブルはふたつありました。

  • データベースが高負荷になり動作が不安定になった
  • なぜかジョブが起動しない

データベースが高負荷になり動作が不安定になった

原因は複数ありました。

データベース(AWS RDS)のインスタンスタイプが小さすぎた

完全にサイジングのミスでした。動作確認で複数のジョブを大量に動かしたときでも、t2.smallのインスタンスで十分に動作したので、t2.smallで大丈夫だろうと、そのまま本番導入したのですが、運用開始して2ヶ月くらいで、高負荷になりました。速やかにt2.mediumにスペックアップしました。

コネクションプールの設定が漏れていた。

Rundeckは、rundeck-config.propertiesというファイルにデータベースの接続情報を記述します。
デフォルトでは、H2 Databaseを使用する前提の接続情報になっています。
これをRDS(MySQL)を使うように修正したのですが、その際、コネクションプールの設定が漏れていました。そのため、接続が一切プールされない、という状態になっていました。
以下の記述をrundeck-config.propertiesに追加することで適切にプールをするように修正しました。

dataSource.pooled=true
dataSource.properties.removeAbandoned=true
dataSource.properties.removeAbandonedTimeout=60

データベースのインデックス不足

このissueで議論されていますが、RundeckのデータベースにはGUIの性能を大きく改善できるインデックスがいくつかあります。これらのインデックスはデフォルトでは付与されていないようです。運用開始後、GitHubやGoogle Groupに投稿されている情報を調査し、このissueで紹介されているインデックスや#1547で紹介されているインデックスを付与することで性能を改善できました。

実行ログがたまり続ける

これは運用を開始する前からわかっていた課題だったので、データベースの高負荷の原因にはなりませんでしたが、対処が必要な課題ではありました。
Rundeckはデータベース上の実行ログを削除しません。長期間運用して実行履歴が大量に溜まった段階で実行履歴の検索を行なった場合、データベースの負荷が高まる可能性があるので、定期的に削除する仕組みが必要だと判断しました。
削除する実行ログの条件は以下の通りです。

  • ジョブは最新の1万件の実行ログを保持する。1万件を超えたら古い順に削除される。

月次1回だけ実行されるジョブもあれば、1時間以内に複数回実行されるジョブもあります。また、調査のために古い履歴を調べることがあるかもしれません。このような前提を考慮して、上記のようなルールにしました。

そして、Rundeckのデータベースの構造を理解してどのテーブルのデータを削除すればよいのかを見つけ、定期的に削除するプログラムを作成しました。

調査したところ以下のdelete文の実行すれば良さそうです。

delete from log_file_storage_request where execution_id in ( @jobhistoryids )
delete from execution where id in ( @jobhistoryids )
delete from base_report where jc_exec_id in (@jobhistoryids )

@jobhistoryidsは、base_reportテーブルのjc_exec_id列の値です。
あとは、上記の条件に合致するジョブのIdと削除件数を特定する必要があります。
それは次のSQLで取得できました。

select
    inntable.jc_job_id as JobId,
    inntable.counts - 10000 as DeleteCount
from
    (
        SELECT
            jc_job_id,
            count(jc_exec_id) counts
        FROM
            base_report
        group by
            jc_job_id
    ) inntable
    inner join
        scheduled_execution se
    on  se.id = inntable.jc_job_id
where
    se.execution_enabled = 1
and inntable.counts > 10000
order by
    inntable.counts desc

このselect文で、実行回数が1万回を超えているジョブのIdと超過回数がわかります。
※例えば10300回実行されたいたら300回が超過回数になります。

そして、次のSQLで削除対象のjc_exec_idを特定します。

SELECT
    jc_exec_id
FROM
    base_report
where
    jc_job_id = @jobId -- 上のselect文で見つかった JobId
order by
    date_completed asc -- 完了日時で昇順でソートすることで1万件を超過した実行ログのIdを特定できる
limit @deleteCount -- 上のselect文で計算した削除対象件数 DeleteCount

あとは、このselect文で取得できたjc_exec_idをパラメータにして上述した3つのdelete文を実行すれば、削除完了です。

なぜかジョブが起動しない

データベースのトラブルが治った後はしばらく順調に動作していました。しかし、指定した時間なのにバッチに起動しない、という現象が発生するようになりました。
詳しく状況を見てみると、バッチ実行が遅延しているようでした。
いろいろと調べてみると、Rundeckが内部で使っているジョブスケジューラライブラリのQuartzのパラメータが原因でした。
Rundeckの公式ドキュメントによれば、The maximum number of threads used by Rundeck for concurrent jobs by default is set to 10と書いてある通り、デフォルトでは最大で10本のジョブの同時実行が可能です。
一方、一休では利用が促進された結果、タイミングによっては10以上のジョブが同時に実行されるような状況になっており、その結果、実行が遅延するようになっていました。この設定を変えるには、以下の記述をrundeck-config.propertiesに追加します。

quartz.props.threadPool.threadCount=30

この記述によって最大30まで同時実行できるようになり、問題が起きなくなりました。

終わりに

今回は実際に運用してきて発生したトラブルとその対処について紹介しました。参考になれば幸いです。 一休ではWindowsのタスクスケジューラからRundeckへ移行しました。Rundeckは未知のツールだったので苦労する点もありましたが、起こった問題は調査すれば解決策が見つかるものばかりだったので、移行は十分成功したと感じています。

おまけ

GUIの日本語化

管理画面が英語だとわかりにくいので、一休ではガイドラインにしたがって、主要な部分だけですが日本語にしています。 部分的なローカライゼーションではありますが、ないよりマシ、なレベルではあるので、本家の方にも導入できるようにPRを送っています。次のバージョンで取り込まれるかもしれません^ - ^

この記事の筆者について

  • システム本部CTO室所属の 徳武 です。
  • サービスの技術基盤の開発運用、宿泊サービスの開発支援を行なっています。

ネット断食におすすめ!日帰り温泉・サウナも楽しめるSPA15選

この記事は一休.comアドベントカレンダー2018の7日目です。

こんにちは。スパ事業部 デザイナーの東根です。

約1年かけて10月25日にローンチした一休.com スパ即時予約サービス をご紹介したいと思います。

一休.com スパ

SPAとは?

エンジニアのみなさまは「SPA」と聞いてまず 「Single Page Application(シングルページアプリケーション)」 を思い浮かべたかもしれませんが、罠でした。すみません。

ここでご紹介するのは、 日帰り温浴やサウナのほかリラクゼーションマッサージの施術が受けられる 「デイスパ(Day spa)」や「ホテルスパ」のことです。

最近では「サ道」「サウナー」も増えているそうですが、 温浴施設で身体を温めてからアロマトリートメントなどの施術を受けると、 血行やリンパの流れが良くなりより効果が高まります。

「Day spa」のwiki英語版によると、

A day spa is a business that provides a variety of services for the purpose of improving health, beauty and relaxation through personal care treatments such as hair, massages and facials. A day spa is different from a beauty salon in that it contains facilities such as a sauna, pool, steam room, or whirlpool that guests may use in addition to their treatment. ...

一休 .com スパの特徴・UIUXのポイント

つまり、一休 .com スパが厳選するスパは、
街中にあるリラクサロンやエステサロンとは違い

  • バスサウナなどの温浴施設
  • プールフィットネスジムなどの運動施設
  • バスローブのまま寛げるリラクゼーションラウンジ

などの施術前後に使える付帯施設があったり、

  • 一般には流通しないこだわりの化粧品ブランド

を使っていたり、といった魅力をアピールしていく必要があります。

UIUXのポイント① 施設の魅力を伝える

f:id:higashinek:20181206134036j:plain

・・・

f:id:higashinek:20181206144238j:plain

施設の魅力といえば、何と言っても写真です。
一休.comの他のサービスにも共通しますが、高級感のある美しい写真をできるだけ大きく見れるとユーザーにその施設を訪れたときのイメージを持ってもらえます。

なので、登録するのに写真は必須。また、お部屋のスペック(広さやスパスイートかどうか、完全個室であるかetc)や温浴施設の内容(ホットバス、コールドバス、ドライサウナ、スチームサウナ、温泉、岩盤浴があるか)、プールやフィットネスジムは水着やトレーニングウェアをレンタルできるかどうかをシンプルなテキストアイコンで表現しました。

UIUXのポイント② プランの魅力を伝える

f:id:higashinek:20181206152217j:plain

ここからはスマホ版のキャプチャで説明します。 プラン一覧およびプラン概要では、タイトル・利用できる付帯施設・滞在時間目安・料金がわかるようになっています。ちなみに、タイトルにある分数純粋なトリートメントの時間で、カウンセリングや施術前後に使える付帯施設の利用時間を含んでいません。そのかわりに前後の付帯施設利用時間を含めた全体の滞在目安の時間を別に表示することで、どのくらい時間に余裕があればこのプランをしっかり体験できるかがわかります。

f:id:higashinek:20181206153816j:plain

プラン内容ではトリートメントの詳細とともにどんなお部屋で施術受けるのか、付帯施設はそれぞれいつ(施術前か後か)どのくらいの時間、どんな施設を使えるのかわかります。

UIUXのポイント③ 空き時間・予約時間をわかりやすく

f:id:higashinek:20181206162455g:plain

プランが決まったら「予約時間を確認する」ボタンから空き時間を探してみます。 まずカレンダーで「日付」を選択、次に「施術スタート時間」を選びます。 時間はレストランの予約とは異なり来店時間ではありません。*印で「付帯施設は、施術スタートの○分前から利用できます」と表示していますので、それを参考に15分刻みの時間ボタンを選んで予約フォームに進みます。

UIUXのポイント④ 予約の前に利用条件をしっかり説明

f:id:higashinek:20181206164036g:plain

注意文言に入れて読んでるハズとしてしまうのではなく
女性限定マタニティ不可のプランなどで利用できない条件を選ぶと 予約完了できないようになっています。

その他、施設スタッフから事前にユーザーへ質問・確認ができるようになっています。

おすすめのホテルスパ15選

ということで、 一休 .com スパの中から以下のポイントでおすすめの施設をセレクトして15選ご紹介します。

  • ホテルスパ、旅館・リゾートスパである
  • 温浴施設利用付きプランがある
  • 男性も女性も利用できる

東京のホテルスパ

1. ザ・ペニンシュラ スパ

東京・日比谷/ザ・ペニンシュラ東京 6階 https://spa.ikyu.com/day_spas/660025/?ikac18i ★こちら実際に体験してきました!私の中で満足度1位★

お風呂はなくサウナのみですが、しっかり暑いドライサウナと ほどよい暖かさのスチームサウナ、アロマの香りがするシャワーのほか アイスファウンテンがあるのでサウナーの方も満足いただけるはず。

アロマテラピーの施術がとっても気持ちが良いのはもちろん、 この写真のリラクゼーションルームではよく冷えたグレープフルーツジュースと マンゴージュースがいただけます。 またこのチェアではヘッドホンで音楽を聞くことができたり、読書灯で 雑誌を読むこともできるので、ネット断食にはぴったり。ゆったり寛げます。

2. ザ スパ アット マンダリン オリエンタル東京

東京・三越前駅直結/マンダリン オリエンタル 東京 37階 https://spa.ikyu.com/day_spas/660038/?ikac18i

おそらく一休.com スパで一番お高いプランのあるホテルスパ。 でも、ローンチしてすぐにペアでXmas近くに予約が入りました。すごい。 とても人気なのでいつか行ってみたい!

完全個室の贅を極めたスパスイートのほか、お風呂やサウナからも眺望が楽しめます。

3. アマン・スパ/アマン東京

東京・大手町駅直結/アマン東京 34階 https://spa.ikyu.com/day_spas/660030/?ikac18i

見てください、このプール!左端には富士山が見えます。 迷わずトップページのメインビジュアルに採用してしまいました。

プールのほかフィットネスジム、大浴場、トリートメントルーム毎の リラクゼーションエリアからも眺望が素晴らしいです。

4. AO スパ&クラブ/アンダーズ 東京

東京・虎ノ門ヒルズ/アンダーズ 東京 37階 https://spa.ikyu.com/day_spas/660032/?ikac18i

白を貴重とした洗練された空間。木のぬくもり溢れるトリートメントルームからも高層階ならではの青空や皇居を望む素晴らしい眺望が楽しめます。

全トリートメントルームにはプライベートロッカー、シャワールームがあり他のゲストの目が気になりません。 ロッカーエリア併設の温浴エリアではお風呂、シャワーの他、男性はドライサウナと水風呂、女性はスチームサウナと360°シャワーを完備されています。

5. スイス・パーフェクション スパ キオイ/ザ・プリンスギャラリー 東京紀尾井町

東京・赤坂見附・永田町/ザ・プリンスギャラリー 東京紀尾井町 30階 https://spa.ikyu.com/day_spas/660019/?ikac18i

世界中のセレブから愛されるスイス製植物性セルラー化粧品「スイス・パーフェクション」を使用する国内初の直営サロンで、エイジングケアの先端技術を結集したトリートメントを堪能できます。

写真はスパスイート・ペアルーム。角部屋のパノラマの眺望が素敵ですね。 スパスイートのプランではこのお部屋でアフターティーをいただくことができます。

6. スパ&ウェルネス ジュール/ハイアット リージェンシー 東京

東京・新宿西口/ハイアット リージェンシー 東京 28階 https://spa.ikyu.com/day_spas/660005/?ikac18i

著名デザイナーが手掛けたスタイリッシュな空間は、木などの天然素材の温もりを感じさせながらコンテンポラリーな雰囲気が漂います。

トリートメント前にはプールやフィットネスジムも利用可能。写真のとおり、プールにはジャグジーやウォームルームを備え、プールサイドは居心地のよいデッキチェアとテーブルを配したウッドデッキとなっています。

7. フォルトゥーナ/ホテルニューオータニ

東京・赤坂見附/ホテルニューオータニ ガーデンタワー 3階 https://spa.ikyu.com/day_spas/660003/?ikac18i

100℃前後の乾燥した高温で発汗を促すドライサウナ、50℃前後の温度で肌や体に負担がかかりにくいスチームサウナの2種類をラインナップ。 バイブラとジェットの機能を持つ浴槽や、体をゆったりと休められるラウンジも併設します。

プール・フィットネスジム付きのプランなら水着やトレーニングウェアを無料でレンタルできるので手ぶらで利用できます。

8. SPA THE SAKURA/ザ・プリンス さくらタワー東京

東京・品川/ザ・プリンス さくらタワー東京 B1階 https://spa.ikyu.com/day_spas/660020/?ikac18i

都会のなかにたたずむ静寂と広い空間のなかで、日本古来の伝統的な香りに包まれるトリートメントルームです。

トリートメント後、アフターティーをいただけるラウンジは、竹林にたたずむような静けさに包まれながら、ゆっくりと過ごすことができる空間になっています。

9. 庵スパ TOKYO/ヒルトン東京お台場

東京・台場/ヒルトン東京お台場 5階 https://spa.ikyu.com/day_spas/660015/?ikac18i

レインボーブリッジ、東京湾ビューが楽しめる大浴場は、プールを併設する水着着用エリアにあります。(有料レンタルあり)

海を眺めるテラスがついた開放的なフィットネスセンター。 各種マシーンを備え、ご宿泊者は無料で24時間利用できます。

東京から2時間以内で行ける旅館・リゾート

10. 赤沢スパ/赤沢迎賓館

静岡・伊東市/赤沢温泉郷 https://spa.ikyu.com/day_spas/660047/?ikac18i

壮大な緑に囲まれた赤沢温泉郷内に、美と健康、リラクゼーションの場所として誕生。 フランス発祥の海洋療法「タラソテラピー」の発想をもとに生まれた海洋深層水のプールには、赤沢沖の深海800mから汲み上げた新鮮な海水を温めて使用されています。

エステエリアには、特徴の異なる3つのドーム(サウナ)と温・冷の足湯などを完備。 海洋深層水のプールで代謝をアップさせた後には、完全個室のアロマの香りに包まれたお部屋でトリートメントを受けられます。またご希望のお客様には、プラス料金で生花のバラを100輪浮べたバラ風呂をご用意することも可能です。

11. GINYU SPA(ギンユウスパ)/箱根吟遊

神奈川・箱根・宮ノ下 https://spa.ikyu.com/day_spas/660048/?ikac18i

日本一予約の取れない宿として噂の旅館「箱根吟遊」その静寂な杜の空気に包まれた「Ginyu Spa」

トリートメント前後に ウォーターガーデンの向こうに望む雄大な自然を眺めていただきき源泉から湧き出る大地の力で、五感を解き放つことができます。 セラピストのテクニックにより、体の疲れをとることだけでなく、自分本来のバランスを整え健康や美しさへと導きます。

12. 庵スパ KARUIZAWA/軽井沢マリオットホテル

長野・軽井沢/軽井沢マリオットホテル B1階 https://spa.ikyu.com/day_spas/660017/?ikac18i

こちらのお風呂は「小瀬温泉」泉質はナトリウム-炭酸水素塩泉。 “美肌の湯”とも呼ばれ、肌の不要な角質や毛穴の汚れを取ってくれる女性に嬉しい効能がたくさん。湯上がりがさっぱりするので、スポーツやアクティビティで汗を流した後などにもおすすめです。

和のエッセンスを随所に取り入れたヒーリング空間「庵スパ KARUIZAWA」。 日本人ならではの繊細で丁寧な施術と、日本由来の贅沢な粧材を使用し、心と身体を解きほぐしていきます。

大阪・京都のホテルスパ

13. CONRAD SPA/コンラッド大阪

大阪・中之島・梅田/コンラッド大阪 38階 https://spa.ikyu.com/day_spas/660006/?ikac18i

淀川側を望むトリートメント全室からは、地上200mから望むスカイラインが刻一刻と表情を変える景色を眺めながら、安らぎの時間をお過ごしいただけます。シャワーブース、トイレ、パウダーコーナーが完備された完全プライベートな空間です。

温浴施設はサウナ、ジェットバス完備。
男性:ホットバス / コールドバス / ドライサウナ
女性:ホットバス / スチームサウナ

ロッカールーム内にはダイナミックな景色を眺めながら、ゆっくりとした時間をお過ごしいただけるラウンジも併設。ドリンクコーナーには、季節のフルーツウォーター、温かいお茶が用意されています。

14. MEGURI SPA & WELLNESS/インターコンチネンタルホテル大阪

大阪・梅田・グランフロント大阪/インターコンチネンタルホテル大阪 4階 https://spa.ikyu.com/day_spas/660021/?ikac18i

全室がクローゼット、シャワー、トイレ、ドレッサーを備え、お着替えからトリートメント後のお支度まで、全て個室内で行えるスパスイート。

温浴施設はまるで高級旅館の温泉を彷彿させる日本式浴場。 大都会の中心で味わう、想像を越えた極上のリフレッシュ&リラクゼーションジャーニーを楽しめます。

15. ザ スパ アット フォーシーズンズホテル京都

京都・東山周辺/フォーシーズンズホテル京都 https://spa.ikyu.com/day_spas/660044/?ikac18i

インドアプールは、京都屈指のゆったりとした広さを確保しております。20メートルの広さに加え、2つのジャグジーも完備。リゾート感溢れる雰囲気のなか、贅沢なひとときをお楽しみください。

お風呂(温浴・冷浴)・サウナをお楽しみいただけます。
男性:ドライサウナ
女性:スチームサウナ

入念に選び抜かれた自然の力とラグジュアリーが融合したスキンケアブランドと、深いヒーリング効果をもたらす京都ならではの素材、それらを使用したトリートメントは、本物のくつろぎと新鮮な安らぎをもたらします。

おわりに

みなさん毎日PC仕事やネットサーフィンで目を酷使しているので 肩こり・腰痛持ちの方も多いのではないでしょうか?
(わたしは慢性的な肩こりの解消にアロマテラピーにはまりました。)
たまには自分の身体もメンテナンスしないと高いパフォーマンスを出せません。

一休.com スパではただいまXmasまでのウィンターセールを開催中です。 この機会にぜひ心身を癒やす贅沢な体験をしてみませんか?

https://spa.ikyu.com/specials/timesale/?ikac18i

Rundeck in practice [導入編]

この記事は一休.comアドベントカレンダー2018の6日目です。

qiita.com


一休では、2016年の10月からRundeckを使ってバッチジョブの実行管理を行なっています。
導入からおおよそ2年たちました。
その間にデータセンターからAWSへの移行やいくつかの運用トラブルなどを経験しました。知見が溜まってきたので導入編と運用編の2つの記事に分けて紹介したいと思います。

今回はまず、導入編として、導入の背景と実際の導入作業で工夫した点、苦労した点を紹介します。また、Rundeckを導入したことで得られた改善についても紹介します。

Rundeckとは

f:id:s-tokutake:20181206084400j:plain

  • Rundeck社が提供するOSSのジョブ管理ソフトウェア。有償版もある。
  • ジョブフロー構築、失敗の自動リトライ、開始終了に対する通知フックなど、 一般的なジョブエンジンの機能を持つ。
  • Java + Groovy + Grailsで実装されている。
  • スケジューリングの定義は、cron形式。
  • 管理画面はWebブラウザで操作できる。
  • SSH経由でリモートのコマンドを実行できる。
  • SSHが通るマシンであれば、OSを問わず、どのマシンのプログラムでも定期実行できる。

脱Windows タスクスケジューラ

Rundeckを導入する前、一休ではサービス運用に必要なバッチ処理をWIndows タスクスケジューラで実行していました。登録されたタスクの数は、100を超えていました。
タスクスケジューラで100を超えたタスクを管理するのはとても辛いです。なにが辛いかというと、

タスクスケジューラのGUIは大量のタスクを管理するのに向いていない。

タスクスケジューラのGUIはフィルタや検索ができません。タスクの数が少なければ問題ないのですが、100以上のタスクを管理しようとするとかなり苦労します。例えば、手動でタスクを動かす必要がある場合、タスク一覧を目視で舐めて目的のタスクを見つけるという辛い作業をしていました。

ジョブフローが組めない。

  • 100以上のタスクがあれば、実行順序に依存関係のあるタスクもあります。しかし、タスクスケジューラでは、「タスクAが正常に実行を完了したらタスクBを動かす」というようなジョブフローが組めません。

なぜRundeckを選んだのか

脱Windows タスクスケジューラを目指すため次の4つの要件を満たすジョブエンジンを探しました。

  • ジョブフローが組めること
  • GUIがわかりやすいこと。ブラウザでアクセスできること。
  • WindowsとLinux両方で動くこと。
    • 一休はWindows系の技術スタックをメインに使っていますので、Windowsで動くことが必要ですが、Linuxサーバも使いますので、Linuxもサポートする必要があります。
  • OSSであること。
    • プロプライエタリな製品だと設計や運用でライセンスを気にする必要が出てきます。また、機能検証しにくいこともあるかもしれないので、まずは、OSSで探して、良さそうなものが見つからなければ市場調査をしようと考えました。

この基準で判断した結果、以下のふたつが、候補になりました。

決め手のひとつはGUIのシンプルさでした。SOS JobschedulerもブラウザでアクセスできるGUIを持っていますが、Rundeckの方がわかりやすいです。 また、日本語の情報もRundeckの方が多かったため、Rundeckを選択しました。
そして最大の決め手は、「SSHさえ疎通すれば、どんなマシンのどんなコマンドでもcron実行できる」という柔軟さです。これによって、ジョブ管理をするサーバとジョブを実行するサーバを分離できます。ジョブ実行に必要なリソースが足りなくなったら、ジョブ実行をするサーバだけ増やせば済みます。そして、ジョブを実行するサーバはOSを問いません。

構成

現在の構成は以下の通りです。 f:id:s-tokutake:20190106110930p:plain

RundeckサーバもジョブサーバもすべてEC2です。導入当時はデータセンターの物理マシンでしたが、構成自体は上の図とほとんど変わりません。 ジョブの実行ログはS3に保存し、RundeckのデータベースにはRDSを利用しています。AWSのサービスを最大限利用した構成にしました。 現時点では、RundeckサーバもジョブサーバもWindows Serverです。

導入にあたって、工夫した点、苦労した点を紹介します。

管理画面の認証

Rundeck自身がユーザー管理の機能を持っています。しかし、50人近くいる開発者全員のアカウントをRundeckに登録して適切に管理するのは大変です。外部の認証機構と連携する必要があるのは自明でした。 RundeckにはActive Directoryと連携する機能があります。また、oauth2_proxyに対応しているので外部のOAuth 2.0 サービスプロバイダーとも連携できます。 一休ではデータセンターにRundeckがあった時代は、AD連携機能を使って認証を行なっていました。クラウドに移行したときに、Rundeckサーバから社内のADが見えなくなってしまったので、oauth2_proxyとGitHubのOauth2の仕組みを使って、GitHubアカウントでログインできるようにしました。

oauth2_proxy は bitly社が開発しているOSSで、リバースプロキシとして動作し、oauthプロバイダとのやりとりを代理してくれる便利なツールです。Goで開発されているのでWindowsでも問題なく動作します。

※ このoauth2_proxy+GitHubでの認証の仕組みの構築には、minamijoyoさんの記事を参考にさせていただきました。ありがとうございます!

EC2プラグイン

RundeckにはEC2プラグインがあります。このプラグインを使うと特定のタグがついているEC2インスタンスを自動的にRundeckのジョブサーバにできます。

f:id:s-tokutake:20190106111304p:plain

これによって、なんらかのメンテナンスでジョブサーバの入れ替えや再構築が必要なときも簡単に対応できます。 また、動的にジョブサーバを追加することもできます。例えば、常時動作しているバッチサーバのスペックでは処理しきれないような大規模バッチ処理がある場合、高スペックなEC2インスタンスを起動して処理をさせ、完了したら、そのEC2インスタンスを停止する、という一連の流れを手動操作を介在させることなく実現できます。

sshサーバ

Linuxサーバなら悩む必要はないのですが、一休ではジョブを実行するサーバはWindowsサーバです。環境を構築した時点では、公式のWindows環境のOpenSSHの実装は、動作が不安定で使えませんでした。そこで、Windows環境で動作するSSHサーバを有償無償問わず、調査したところ、bitvise ssh server 最適と判断しました。

  • 有償ですが、安い。ライセンス買い切り。
  • 設定が簡単でシンプル。

当初は、無償のfreesshdで進めようと思っていたのですが、動作が安定せず、断念しました。

タスク移行

タスクスケジューラ上の100以上あるタスクを手動でRundeckのジョブとして移行していたら絶対にミスをします。また、当然、移行作業中も通常のサービス開発は行われています。新しいバッチ処理が追加されているかもしれません。移行作業と開発との間の齟齬が起きないようにする必要がありました。 そこでタスクスケジューラからxml形式でタスクをエクスポートし、Rundeckのジョブ定義xmlに変換するプログラムを書き、そのジョブ定義xmlをRundeckにインポートすることで、スムーズかつ齟齬がないように移行しました。

ジョブのエラー通知

Rundeckには、タスクの完了(成功、失敗)を通知する仕組みがあります。メール通知、webフックの呼び出しができます。また、プラグインを利用することでslackにも通知が飛ばせます。一休ではサービスのエラー通知はすべてslackに飛ばしています。なので、プラグインを使ってslackに通知しようと考えました。しかし、以下のふたつの理由でこのプラグインを使ってのslack通知はやめました。

  • ひとつひとつのジョブに設定しなければならないのが面倒。新しく追加したジョブに設定漏れが起きそう。
  • 通知内容が少ない。特にエラーになった場合は、実行したコマンドのステータスコードや標準出力の内容も通知したい。

そこで、以下のような方法にしました。

  • S3プラグインを使ってジョブの実行ログをS3に出力する。
  • S3へのログのPutをトリガにして動作するAWS Lamdbaを実装する。
  • このLamdbaはログの中身をみてエラーだったら、エラー内容(ステータスコードや標準出力)をSlackに通知する。成功の場合は通知しない。

このようにすることで、ジョブの通知設定に関わらず全てのエラーをslackに通知することができました。

ansibleを使って環境構築

oauth2_proxyのインストールやRundeckのインストール、各種構成ファイルの設定、監視の設定はすべてansibleで行うようにしました。こうすることでトライアンドエラーを繰り返しながら上述したような技術検証ができました。また、ansibleがWindows環境でも問題なく使えることがわかったのも収穫でした。

改善ポイント

  • 当初導入によって目論んでいた改善はほばすべて達成しました。
  • ブラウザから管理画面にアクセスできるようになったのでタスクスケジューラよりもはるかに簡単に管理できるようになりました。ジョブのフィルタもできます。また、タスクスケジューラ時代はさまざまな事情がありジョブの登録や変更は特権を持った特定のエンジニアしかできないようになっていました。このルールもRundeck移行によって見直すことができました。

  • ジョブフローも活用されています。導入当初は、使われていませんでしたが、数ヶ月経つと、特定のエンジニアが導入を推進する、ということをしなくても、自然と使われるようになっていきました。

  • また、当初見込んでいた改善ではないですが、タスクスケジューラを使ったバッチ処理よりもインフラの可用性は大きく向上しました。バッチの実行管理をするRundeckサーバと実行の定義のストアであるRDS、そして実際にバッチを実行するバッチサーバの3つを分離できました。この3つそれぞれ別々に障害対策を考えればいいので運用がしやすいです。フェールオーバの手順はタスクスケジューラ時代の障害時フェールオーバの手順よりもかなりシンプルになりました。

終わりに

今回紹介した内容は1年半くらい前に実施したことなので少し情報が古いかもしれません。しかし、LinuxのcronではなくWindowsのタスクスケジューラからRundeckに移行した例はあまりないのでは、と考え、紹介しました。RundeckはWindows環境でも十分に活用できます。 次回は、運用編として、この2年間で起こった運用トラブルとその対処について、紹介したいと思います。

この記事の筆者について

  • システム本部CTO室所属の 徳武 です。
  • サービスの技術基盤の開発運用、宿泊サービスの開発支援を行なっています。