一休.com Developers Blog

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

KMLを元にしたSolrの空間検索に挑戦

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

いよいよ今年も終わりですね。 みなさん クリスマスの、忘年会のご予約はすみましたか?

というわけでアドベントカレンダー2打席目、一休.comレストラン 検索 & 集客担当のにがうりです。

一休の本社は赤坂見附の駅からほど近くにあり、お昼ごはんの選択肢が非常にバラエティに富んでいるのが嬉しいところです。 もちろん、その中には一休.comレストランにご加入いただいている店舗様もたくさんあります。

本エントリでは

  • 筆者のお昼休み中に通える範囲内にあり
  • 一休.comレストランでランチが予約できる

レストランがどのくらいあるのか、Solrの空間検索( Spatial Search )を利用して調べてみました。

なお、前回のエントリ同様、Solrのバージョンは7.1.0を前提としています。

事前準備

Solrのスキーマ構成

ひとまず、以下の項目を用意します (restaurant_idがuniqueKey)

  <field name="restaurant_id" type="string" indexed="true" required="true" stored="true"/> <!-- レストランのID -->
  <field name="restaurant_name" type="string" indexed="true" stored="true"/>  <!-- レストランの名称 --> 
  <field name="lat_lon"           type="location"   indexed="true" stored="true" /> <!-- 緯度、経度 -->
  <dynamicField name="*_coordinate" type="pdouble" indexed="true" stored="false"/>

対象となるデータ

「赤坂・永田町・虎ノ門」エリアで12時に予約できるお店が120件ほど見つかりました。 これらの店舗のID, 名前, 緯度経度をSolrに登録しておきます。

Solr検索のテスト

試しに3件取得

URL

http://localhost:8888/solr/ikyu-advent-2017-spatial/select?wt=json&echoParams=none&fl=restaurant_id,restaurant_name,lat_lon&q=*:*&rows=3

取得結果

無事に登録されていました

{
    responseHeader: {
        status: 0,
        QTime: 5
    },
    response: {
        numFound: 120,
        start: 0,
        docs: [{
            restaurant_id: "100152",
            restaurant_name: "春秋 溜池山王店",
            lat_lon: "35.6736091,139.740132"
        }, {
            restaurant_id: "100195",
            restaurant_name: "沖縄懐石 赤坂潭亭",
            lat_lon: "35.6685154,139.732890"
        }, {
            restaurant_id: "100197",
            restaurant_name: "赤坂浅田",
            lat_lon: "35.6738200,139.738229"
        }]
    }
}

オフィスの位置を中心に範囲検索

Spatial Searchのgeofilt を使い、

  • オフィスの場所を中心に
  • 半径400メートル以内の店舗を
  • 近い順に

探してみます

URL

http://localhost:8888/solr/ikyu-advent-2017-spatial/select?wt=json&echoParams=none&fl=restaurant_id,restaurant_name,lat_lon,geodist:geodist()&spatial=true&sfield=lat_lon_rpt&q={!geofilt}&pt=35.675471,139.737937&d=0.4&sort=geodist()%20asc&rows=3

pt=35.675471,139.737937 が一休本社オフィスの緯度経度、d=0.4 が400メートル以内という指定です

取得結果

{
  "responseHeader": {
    "status": 0,
    "QTime": 0
  },
  "response": {
    "numFound": 36,
    "start": 0,
    "docs": [{
      "restaurant_id": "100729",
      "restaurant_name": "個室会席 北大路 赤坂茶寮",
      "lat_lon": "35.6752587,139.738609",
      "geodist": 0.06510001  /* geodistは中心地からの距離 (km) */
    }, {
      "restaurant_id": "106301",
      "restaurant_name": "土佐料理 祢保希 赤坂店",
      "lat_lon": "35.6751768,139.737050",
      "geodist": 0.086561576
    }, {
      "restaurant_id": "107172",
      "restaurant_name": "赤坂 金舌",
      "lat_lon": "35.6746016,139.737555",
      "geodist": 0.10269355
    }]
  }
}

無事取得できました。 一見、後は半径を調整すればいけそうに思われます・・・が、一休本社の周辺マップはこのようになっています。

一休周辺地図 ※ 赤いピンが株式会社一休本社

ご覧の通り、

  1. 東側には大通り
  2. 大通りを抜けても日枝神社や大使館
  3. 北側もガーデンテラスの方向に抜けるためには何回か信号を渡る必要がある

となっているため、単純な半径の調整で良い具合に、というのは中々厳しいものがあります。 ここは、円形ではなく任意の範囲を指定して検索したいところです

任意の範囲を指定して検索

任意の範囲を検索するためには、

  1. どうやって任意の範囲を指定するのか
  2. 任意の範囲を使った検索をどのように行うか

という2つの課題をクリアする必要があります。 幸い、1はGoogleマイマップ、 2はJTSを利用したSpatial Search を使うことで対応できました。

Googleマイマップで範囲データを作成

Googleマイマップでは地図上に自由にラインを引くことでき、さらにそれをKML形式のデータとして出力することが可能です。 ※ Googleマイマップの使い方については本稿の主旨と異なるため、割愛します

ランチタイムの徒歩行動圏をこのような枠線で表現しました。 お昼の行動半径

こちらをKML形式でエクスポートした結果が以下です。

ikyu-advent-2017-spatial.kml

<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
  <Document>
    <name>ikyu-advent-2017</name>
    <Style id="poly-000000-1200-77-nodesc-normal">
      <LineStyle><color>ff000000</color><width>1.2</width></LineStyle>
      <PolyStyle><color>4d000000</color><fill>1</fill><outline>1</outline></PolyStyle>
      <BalloonStyle><text><![CDATA[<h3>$[name]</h3>]]></text></BalloonStyle>
    </Style>
    <Style id="poly-000000-1200-77-nodesc-highlight">
      <LineStyle><color>ff000000</color><width>1.8</width></LineStyle>
      <PolyStyle><color>4d000000</color><fill>1</fill><outline>1</outline></PolyStyle>
      <BalloonStyle><text><![CDATA[<h3>$[name]</h3>]]></text></BalloonStyle>
    </Style>
    <StyleMap id="poly-000000-1200-77-nodesc">
      <Pair><key>normal</key><styleUrl>#poly-000000-1200-77-nodesc-normal</styleUrl></Pair>
      <Pair><key>highlight</key><styleUrl>#poly-000000-1200-77-nodesc-highlight</styleUrl></Pair>
    </StyleMap>
    <Placemark>
      <name>ランチエリア</name>
      <styleUrl>#poly-000000-1200-77-nodesc</styleUrl>
      <Polygon>
        <outerBoundaryIs>
          <LinearRing>
            <tessellate>1</tessellate>
            <coordinates>
              139.7344959,35.6802109,0
              139.7360516,35.6778317,0
              139.7344422,35.6766465,0
              139.7351772,35.6739926,0
              139.7361803,35.6719924,0
              139.7388196,35.67116,0
              139.7404772,35.672341,0
              139.7392058,35.673352,0
              139.7372103,35.680516,0
              139.7344959,35.6802109,0
            </coordinates>
          </LinearRing>
        </outerBoundaryIs>
      </Polygon>
    </Placemark>
  </Document>
</kml>

「coordinates」の中に経度、緯度の羅列が入っています。こちらが枠線の情報のようです。

ちなみに、この地図に対して前述の120件のレストランをプロットするとこのようになりました

f:id:ikyu_com:20171222135449p:plain

範囲広すぎましたね・・・ 気を取り直して続けます。

検索の下準備

インストール直後のSolrでは空間検索ができない状態でした。 空間検索を行うためには、JTSを入手する必要があります。

JTSの入手 & 設定

  1. https://repo1.maven.org/maven2/com/vividsolutions/jts-core/ より jts-core-{バージョン}.jar をダウンロード (本稿作成時点では1.14.0)
  2. SOLRインストールディレクトリ/server/solr-webapp/webapp/WEB-INF/lib/ にコピー ※ SOLRインストールディレクトリ/server/lib と間違えないように

SolrにJTSを反映

1. 定義済みの型「location_rpt」に対しspatialContextFactory="JTS" を追記 (これをやらないとエラーになります)
<!-- 変更前 -->
<fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType" geo="true" maxDistErr="0.001" distErrPct="0.025" distanceUnits="kilometers" />
<!-- 変更後 -->
<fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType" geo="true" maxDistErr="0.001" distErrPct="0.025" distanceUnits="kilometers" spatialContextFactory="JTS"/>
2. location_rpt型の列を追加

lat_lon_rptという列を追加しました。 データを作り直すのは面倒なので、copyFieldでlat_lonの値をコピーさせています

  <copyField source="lat_lon" dest="lat_lon_rpt"/>
  <field name="lat_lon_rpt"  type="location_rpt"   indexed="true" stored="true" />
3. Solr再起動

この後データを再度登録し、lat_lon_rptにデータが入っていることを確認して下準備は完了 

任意の範囲を検索

説明 を読むと、

&q=:&fq={!field f=geo}Intersects(POLYGON((-10 30, -40 40, -10 -20, 40 20, 0 0, -10 30)))

という指定で範囲の指定ができるようです。 つまり、Intersects(POLYGON((...))) の中に、先のkmlのcoordinatesの内容を使えばいけそうです。 手で加工するのも面倒なので、Pythonの力を借りて検索しちゃいましょう。

ikyu-advent-2017-spatial.py (Python3.6.xで実行)

import re
import urllib.request
import urllib.error
import xml.etree.ElementTree as et

def main():
    root = et.parse('ikyu-advent-2017-spatial.kml').getroot()  # 先程のkmlファイルを読み込み
    polygon = root.findtext(".//{http://www.opengis.net/kml/2.2}coordinates")  # coordinates内の文字列を取得

    polygon = re.sub(r'\s+', '\n', polygon)  # スペースをトリミング
    polygon = re.sub(r',0$', '', polygon, flags=re.MULTILINE)  # 行末の.0を削除
    lon_lats = [_.split(",") for _ in polygon.split('\n')]
    lon_lat_str = ",".join([f'{_[0]} {_[1]}'  for _ in lon_lats if len(_) == 2])  # lon1 lat1,lon2 lat2,lon3 lat3... の組み合わせの文字列を生成

    query = (
        ('wt', 'json'),
        ('echoParams', 'none'),
        ('rows', '120'),
        ('fl', 'restaurant_id, restaurant_name,lat_lon'),
        ('q', '*:*'),
        ('fq', f'{{!field f=lat_lon_rpt}}Intersects(POLYGON(({lon_lat_str})))'),
    )

    url = "http://localhost:8888/solr/ikyu-advent-2017-spatial/select?" + urllib.parse.urlencode(query)
    print(url)
    print("-------------------")

    with urllib.request.urlopen(url) as req:
        try:
            response = req.read().decode('utf-8')
            print(response)
        except urllib.error.HTTPError as e:
            print("HTTPError")
            print(e.reason)
        except Exception as e:
            print(e)

if __name__ == '__main__':
    main()

出力されたURL

http://localhost:8888/solr/ikyu-advent-2017-spatial/select?wt=json&echoParams=none&rows=120&fl=restaurant_id, restaurant_name,lat_lon&q=*:*&fq={!field f=lat_lon_rpt}Intersects(POLYGON((139.7344959 35.6802109,139.7360516 35.6778317,139.7344422 35.6766465,139.7351772 35.6739926,139.7361803 35.6719924,139.7388196 35.67116,139.7404772 35.672341,139.7392058 35.673352,139.7372103 35.680516,139.7344959 35.6802109)))

取得結果

45件取得されました

{
  "responseHeader": {
    "status": 0,
    "QTime": 1
  },
  "response": {
    "numFound": 45,
    "start": 0,
    "docs": [{
      "restaurant_id": "100197",
      "restaurant_name": "赤坂浅田",
      "lat_lon": "35.6738200,139.738229"
    }, {
      "restaurant_id": "100729",
      "restaurant_name": "個室会席 北大路 赤坂茶寮",
      "lat_lon": "35.6752587,139.738609"
    }, 
    /* ---- 略 ---- */
    {
      "restaurant_id": "107172",
      "restaurant_name": "赤坂 金舌",
      "lat_lon": "35.6746016,139.737555"
    }, {
      "restaurant_id": "107347",
      "restaurant_name": "ビストロMATSU",
      "lat_lon": "35.6760843,139.735606"
    }, 
    /* ---- 略 ---- */
    {
      "restaurant_id": "108549",
      "restaurant_name": "鉄板焼877",
      "lat_lon": "35.676792,139.73558"
    }, {
      "restaurant_id": "108862",
      "restaurant_name": "焼肉しゃぶしゃぶシャンボール",
      "lat_lon": "35.6745112,139.736611"
    }]
  }
}

この45件のみで地図にプロットしなおすとこの通り。

f:id:ikyu_com:20171222135928p:plain

見事、徒歩圏内のレストランのみに絞り込むことに成功しました。 45件、コンプリートの道は遠そうです。

まとめ

  • 範囲指定の情報の作成はGoogleのマイマップを使うと楽
  • 任意の範囲で検索するためにはJTSが必要
  • KMLデータをSolrの検索条件に変換する処理はPythonなりで自動化可能

この結果を活かして、検索をもっと使いやすく、便利にしていきたいと思います。

明日は id:ryo-utsunomiya さんの「一休.comスマホ版予約入力画面リニューアルの舞台裏」です。

Dev旅のススメ。オフサイト・開発合宿におすすめな宿3選

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

qiita.com

一休コンシェルジュ(https://www.ikyu.com/concierge/)のディレクターをしているid:aitamxです。2017年1月に一休にJoinし、もうすぐ1年となります。
アドベントカレンダーの一枠をいただいたので、『月1でのリリースサイクルの回し方』的な投稿も考えましたが、 それは別の機会とし、メディアっぽい内容のエントリーにしました。
箸休め的な形で愉しんでいただければ幸いです。

今回は一休.com掲載施設で、オフサイトや開発合宿、ハッカソンといったエンジニアが使うシーンに合いそうな宿をご紹介します。

\オフサイト・開発合宿で必要な設備/

◆ 何はなくともWi-Fi環境
◆ プロジェクタ・モニター・ホワイトボード
◆ 非日常な滞在

オフサイト・開発合宿におすすめな宿3選

オフサイトミーティングや開発合宿をするにあたり、自分たちで機材を持ち込む方もいらっしゃると思いますが、とはいえホワイトボードを持ってくのは何かとツラいですよね。
それに何かとかさばる荷物は少しでも減らしたいところ。
また、「非日常な滞在」=絶景やオシャレなインテリア、美味しい空気の中で得られるフレッシュな感覚は、クリエイティビティを必要とされる開発者の皆さんにこそ体験していただきたいので、必要項目に入れました。

しかし、条件をクリアした施設は、探してみると意外にありません。

\ マ ジ か マ ジ で か /

焦りながら結構な時間を費やし、営業の方にもアドバイスをいただき、バケーションレンタル(貸別荘)・リゾートホテル・旅館の3施設を厳選しました。

団体旅行にも。千葉の「バケーションレンタル(高級貸別荘・コンドミニアム)」

Cairns House (ケアンズハウス) オフサイト・開発合宿におすすめな宿3選

【おすすめポイント】
・とっても豪邸
・「ホワイトボード」「スクリーン」の貸出付きの宿泊プランがある
・一棟貸し切りなので、会議室代がかからずに作業に没頭できる

こちらは千葉の館山に位置する施設で、一棟貸し切りなので自分たちが自由にアレンジできます。
とてもゴージャス&オシャレな空間で、柔軟なアイデアが生まれそう。
別館を含めて利用すると、最大15名利用ができます。

人数少なめなスタートアップ企業の合宿にいかがでしょうか。
▼Cairns Houseについての詳しい紹介記事
こんなお家に住みたい!が叶う、憧れの豪邸 | 一休コンシェルジュ

箱根のリゾートホテルでオン&オフの滞在

ハイアット リージェンシー 箱根 リゾート&スパ オフサイト・開発合宿におすすめな宿3選

【おすすめポイント】
・広くて居心地のよい客室で、プライバシーが保たれる
・「ホワイトボード」「スクリーン」のある会議室では、窓越しに明星ヶ岳の大文字を見渡せる
・付属のレストランが美味しい

オフサイト・開発合宿におすすめな宿3選 強羅の自然を感じられるスパリゾート。上質な空間で、さすがハイアットといった安心感があります。
56平米という広々とした開放的な客室は、ゆったりくつろげる雰囲気。雑魚寝苦手な方が多いときには、ホテルは気楽です。
ホテル内のレストラン&バーが複数あるので、チームメンバーの好みに合わせたランチやディナーが楽しめるのもいいですね。
エグゼクティブな雰囲気に包まれ、濃縮したパワーミーティングをしたい方に。

友ヶ島、淡路島、四国を望む絶景宿でパワーチャージ

休暇村 紀州加太 オフサイト・開発合宿におすすめな宿3選 【おすすめポイント】
・紀淡海峡を望む絶景で湯ったりできる
・最大100名利用可能な和室の会議室
(ワイヤレスマイク・ホワイトボード・プロジェクター・スクリーン完備)
・紀州の旬の食材を使った豪快な海の幸を満喫

オフサイト・開発合宿におすすめな宿3選 高級宿泊施設のイメージがある一休.comですが、実は休暇村の予約もできることはご存知でしょうか。
こちらの「休暇村 紀州加太」は開放感のある眺望と、四季を感じる海の幸を堪能できる施設です。
露天風呂は最近はやりの「インフィニティ風呂」!
雄大な自然の中で煮詰まった頭をリフレッシュしたら、インスピレーションがわきそうですね。

まとめ

「オフサイト・ミーティング」といっても、各企業で解釈がさまざま。
「組織力向上」や「目標・ミッション理解」、「事業部毎の課題解決」を目的とし、チームビルディングに活用されている企業も多いと聞きます。(一休でも実施しています)
業務中の会話はSlackやhipchatだけということもあるかもですが、たまに場所を変えてミーティングをすると思わぬ発見があるかもしれません。

また、今回改めて施設を探しましたが、国内にはまだまだ開発合宿向きの施設が多くないと感じました。(特に、プロジェクター+ホワイトボード+Wi-Fiの貸し出しに対応している施設が少ない)
貸別荘等を利用するケースも多いかと思いますが、自分たちで何でもしないといけないのって結構大変だったり…。
新サービスのアイデアブレストや、プロトタイプ作成、興味のある技術やトレンドをみんなで味見…など、開発合宿といっても目的はひとそれぞれ。
ニーズに合わせた宿選びができるよう、おすすめ施設を見つけたら、定期的にご紹介していこうと思います。

明日はnigauryyyさんの「KMLを元にしたSolrの空間検索に挑戦」です。

データ分析基盤、その後

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

データサイエンス部所属のエンジニア 笹島 id:sisijumi です。

今日はクラウド環境へのデータ分析基盤構築にまつわるお話をさせていただこうと思っています。

データ分析基盤の構築に関して

夏にデータ分析基盤を Azure SQL Data Warehouse を中心にした構成で構築

構築はしましたが、残念ながらこの構成での運用には至りませんでした。

一休では元々社内にデータ分析基盤を構築し運用していましたが、運用負荷の増大に伴いその基盤のクラウド環境への移行を進めました。
下記は今年の8月のあるイベントでの発表資料ですが、イベントではデータ分析基盤は Azure SQL Data Warehouse を中心としたものに と話しさせていただきましたが、現状そうはなっていません。

二度の作り直し

実は上記は二度目のチャレンジでした。
一度目は Redshift(AWS), 二度目が Azure SQL Data Warehouse(Azure) へとそれぞれの環境に構築しました。
その環境を利用して実際に分析業務を行っているメンバーに検証してもらい、その結果を受けてさまざまなディスカッションをした結果、作り直し、という意思決定を行っています。

なぜ作り直しという意思決定に至ったのか

いろいろな理由はありますが、最大の要因は実際に分析業務を行うメンバーとのコミュニケーション不足だと考えています。
他社の事例などを参考に構築に関してはエンジニア側で主導しました。自分たちとしてはこうあるべきという絵図を描き、その形をきちんと実現しました。
もちろん構築の途中で分析業務を行っているメンバーにも、こういう構成で作りますという説明は行っていますが、その時点の相互理解が不足していたと考えています。結果として、運用不可を下げるというエンジニアの課題を解決することがメインになってしまい、分析業務を行っているメンバーの求めている形にはなっていませんでした。

それでも二度の構築に価値がなかったとは思っていません。実際にその基盤上で分析業務用のSQLを試してみてもらい、求めているものはこうじゃない、遅い、等フィードバックを通じて相互理解が深められました。

最終的な構成

AWS上にRDSを利用して構築しました。DBのエンジンはSQLServerを利用しています。

f:id:sisijumi:20171220223443p:plain

ついに完成しました。三度目の正直です。やったー

どうしてこの構成になったのか

分析業務に関わるメンバー全員にとってはこの構成が最適だと判断しました。
データ分析を行っているメンバーは今まで通り分析業務が行えます。社内にあったデータ分析基盤はSQLServerがメインになっているものであり、さまざまな業務がSQLServerに最適化されている為です。既存の資産(分析用のクエリ等)が再利用できたり、データ分析業務を行っているメンバーの道具を変える必要が無かったり(例えばSQLServerManagementStudio 等がそのまま流用可能)、さまざまな利点がありました。
また、マネジードな製品を利用することでエンジニアの運用負荷も下げられます。課題としてパフォーマンスに対する懸念はありましたが、列ストアインデックスなどを利用してさまざまなパフォーマンスチューニングを行った結果、現行と同等の性能は出ています。

運用開始

社内のデータ分析基盤を利用していた業務は徐々にクラウドデータ分析基盤に移行していっています。 また、日々のETLの結果も下記のような形でSlackに投稿されています。 今後は安定的にこの基盤を運用していければ良いと考えています。

f:id:sisijumi:20171221011437p:plain

明日は id:aitam による オフサイト・開発合宿に。プロジェクター+ Wi-Fi環境のある宿3選 です。

一休.comレストラン アプリのローンチと2度のメジャーアップデートを通して、デザイナーとして学んだこと

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

一休.comレストランでアプリのUI/UXデザイナーをしているid:vivashion です。

私は一休.comレストランのアプリを開発し始めた時からデザイナーとして関わっています。 当アプリはファーストローンチから4度のメジャーアップデートをし、現在(Ver.5.3.2)に至ります。 ファーストローンチのVer.1.0.0、Ver.2.0.0、Ver.3.0.0のメジャーアップデートは大きくデザインが変わり、アプリ開発体制や環境も大きく変化してきました。

これまでのアプリ開発において紆余曲折ありましたが、その中で私がデザイナーとして何を考え、何を学んだか、ご紹介すると共に、この記事を読んでくださる皆様にとって良いプロダクト開発のための気づきやヒントになればと思います。

開発しているアプリはこちら。



一休.comレストランアプリ ローンチ Ver.1.0.0

f:id:vivashion:20171218140501j:plain

アプリ開発プロジェクトの立ち上げ

一休.comレストランは、元々Webしかない状態でしたが、社内のエンジニアからの発案で、アプリを開発しようという意見が寄せられたことがアプリ開発プロジェクトが立ち上がったきっかけでした。しかし、「アプリ開発をしよう」と動き始めたはいいものの、社内にアプリ開発のノウハウが無かったのです。

そのような状況の中、たまたま弊社のエンジニアが、UIデザインを強みとしているGoodpatchの代表である土屋さんと知り合いで、アプリ開発におけるノウハウも多く持っていることから、アプリの共同開発をお願いしました。共同開発を行った理由としては、一緒にプロジェクトを進めながら自社内にもアプリ開発のノウハウを取り入れたいと考えていたためです。

Goodpatchからは、ディレクター1名、iOSデザイナー1名、
一休からは、iOSエンジニア1名、Androidエンジニア1名、Androidデザイナー1名(私)の
計5名でプロジェクトメンバーが構成されました。
また、GoodpatchからiOSエンジニア、Androidエンジニアの方が実装のアドバイザーとして協力してくださいました。

アプリ開発における全てのことが初めての経験で、全てのことが学びとなったプロジェクトでした。

Ver.1.0.0の開発の仕方

まずGoodpatchが一休.comレストランのサービスについて知ることから始まりました。どんなユーザー層がサービスを利用していて、どんなシーンで利用しているか。そして、どんなステップを踏んでレストランを予約しているか。それらの情報からペルソナを複数人作成し、ペルソナ毎のカスタマージャーニーを書き、リーン・キャンバスを埋め、ユーザーにどんな価値を提供していくかを詰めていきました。

この時、提供しようとした価値とは、「Webよりも簡単に予約できること」。例えば、Webよりも予約するまでの画面遷移のステップを減らすこと、予約するプランの見比べをしやすくすること等です。

このように開発するアプリの根底部分を固めた後、画面遷移図を作成し、プロトタイピングを行っていきました。まずは「色」の概念が入らないようにグレースケールで画面をデザインしていき、作って捨ててを何度も繰り返し、全画面を作成し、その後、サービスやペルソナに合わせたカラーを入れ込みアプリ全体のデザインを完成させました。

完成したデザインを基にエンジニアが実装していき、都度実装された画面を確認し、細かいデザインの調整も行いアプリをローンチすることができました。

Ver.1.0.0でデザイナーとして学んだこと

全てが学び

アプリ開発に関するノウハウが皆無だったので、プロジェクトで発生するすべての事を学ぼうと必死でした。

プロジェクト発足当時、私はAndroidのデザイナーとしてジョインしましたが、それまでAndroidのデザインガイドラインも、iOSのHuman Interface Guidelines( HIG)もちゃんと見たことがない状態でした。アプリのデザインに関する知識や経験も皆無だったので、Prottを使ったプロトタイピング、デザインのフェーズにおいては、まずGoodpatchのiOSデザイナーの方が出してきた案に対し、それをAndroidのMaterial Designに落とし込むということを行い、アプリのデザインの仕方とMaterial Designについて学んでいました。


Android 4系とMaterial Designの壁

当時はMaterial Designのガイドラインもそれほど充実していなかったので、概念の理解を深めそれをデザインに落とし込み、ユーザーが意識しなくても使えるUIを心がけました。しかし、その当時はMaterial Designを実装するのに大きな壁がありました。Android 4系OSのシェアがまだ大きく、アプリのサポート対象としていたことです。Material Designを実装ベースで比較的簡単に表現できるのは5系のOSからでした。

Material Designの大きな特徴の1つとして、画面遷移のインタラクションになめらかなアニメーションを取り入れるというものがあります。いくらUIをMaterial Designにしても4系をサポートするとなるとこのなめらかなアニメーションを表現できないという制限があり、デザイナーとしては非常にもどかしい思いをしました。

今、Androidアプリをリデザインするとしたら、とてもスムーズでなめらかな画面遷移を表現でき、よりMaterial Designらしいアプリを開発できることでしょう。
(現在、Androidアプリの開発はほぼストップしていますが...)

1度目のメジャーアップデート Ver.2.0.0

f:id:vivashion:20171218140537j:plain

Ver.2.0.0 開発プロジェクトの立ち上げ

iOS、Android両アプリをファーストローンチして間もなく、会社の上層部からアプリのコンセプト変更とともに機能追加の要請が降りてきました。

降りてきた要望としては、「もっと簡単に操作できるデザインに」、「当日行けるレストランを見つけやすく」というものでした。この1度目のメジャーアップデートプロジェクトが発足した時点で、Goodpatchとの契約は終了し、新たに社内からディレクター1名、iOSデザイナー1名がジョインし、完全内製 のチーム体制になりました。

多くの問題を抱えながらのプロジェクト立ち上げ、iOS Ver.2.0.0のリリースでしたが、非常に多くのことを学べたプロジェクトでした。
(プロジェクトの途中にAndroidの開発は一旦中止となりました)

Ver.2.0.0の開発の仕方

改めて新しいコンセプトのアプリのリリースに向けて、どんなアプリにするかミーティングを重ねました。

「もっと簡単に操作できるデザインに」のコンセプトに対しては、デザイン的なアプローチで改善する。「当日行けるレストランを見つけやすく」のコンセプトに対しては、地図検索で現在地付近のレストランを探せる機能を提供することに決定しました。さらに、新しくジョインしたiOSデザイナーからの提案として「ラグジュアリー感の演出」を追加のコンセプトとしました。

この時のプロジェクトでは、iOSデザイナーが画面遷移やデザインをガリガリ作って、それをエンジニアが実装していくスタイルでした。私が担当していたAndroidのデザインは、この時もiOSデザイナーが出してきたデザインをMaterial Designに落とし込むようにしていました。

Ver.2.0.0でデザイナーとして学んだこと

チーム内コミュニケーションの大切さ

上述しましたが、私は、Ver.2.0.0の開発においてもiOSデザイナーが出してきたデザインをMaterial Designに落とし込むというアプローチを取っていました。

iOSデザイナーがデザインをガリガリ作って、どんどん作業を先行してしまったので、デザインの意図がどのようなものかというコミュニケーションが欠落しがちで、デザインの意図をよく理解しないまま、iOSからAndroidへのデザインの移植をしていました。

iOSデザイナーが1人で完璧なデザインを作れるということではないので、コミュニケーション不足によって改善できるポイントを見逃していた可能性もあります。より良いユーザー体験を提供するにはまずチーム内のコミュニケーションを透明にすることが大切だと改めて感じました。

自分の意見をはっきりと伝えることの大切さ

Ver.2.0.0からジョインしてきたiOSデザイナーはそれまでアプリのデザインをしたことはありませんでしたが、優れたWebデザイナーでした。その彼がデザインした画面はHIGに沿っておらず、初めて目にするようなUIや、ユーザーが予期しないであろうインタラクションが散見されました。当然、ユーザーにとって使いやすいだろうとデザインされたものだったのですが、馴染みのないUIだったためにユーザーを迷わせていた箇所も多かったことでしょう。

私は、そのようなUIの欠点を認識していたにも関わらず、修正・改善すべきという発言を控えてしまっていました。当の彼が優れたWebデザイナーで、彼にデザインを任せておけば大丈夫というフィルターがかかってしまっていたためです。

結局、そのデザインのままアプリをリリースすることになり、ユーザーに提供してしまったことを後悔しています。自分が気づいていたUIの欠点、修正したほうが良いという意見をはっきりとチームメンバーに伝えていればそのような結果にはならなかったことでしょう。

アプリは会社の事業としての1つのプロダクトであり、収益を上げていかなければならない

至極当然のことなのですが、事業会社において、アプリはビジネスをしていく上でのひとつの武器です。つまり、アプリを開発したということはそれ相応の結果を求められますし、収益を上げられないアプリに価値はありません。

プロジェクトが立ち上げ時に会社上層部から提案された「当日行けるレストランを見つけやすく」というコンセプトには、それをコンセプトに掲げるだけの数字的根拠があり、ユーザーにアプリならではの機能を提供し、それの収益化が見込めるという判断によるものでした。

2度目のメジャーアップデート Ver.3.0.0

f:id:vivashion:20171218140557j:plain

社内の組織体制の変更によりVer.2.0.0のiOSデザイナーが部署異動になり、晴れて私がアプリのメインデザイナーになりました。

CTO伊藤直也さんが一休にジョインし、アプリチームの開発体制、デザインの改善に乗り出しました。まず、直也さんからアプリのUIがどんなものであるべきか勉強会が開かれました。

f:id:vivashion:20171218142739p:plain

この勉強会が大きなきっかけとなり、まずはHIGにできるだけ沿った形のUIにし、使い勝手を向上させることが大きな目的となりました。

Ver.3.0.0の開発の仕方

まずHIGからかけ離れている画面、UIを洗い出しました。例えば、アプリ立ち上げ後のトップ画面はドロワーメニューを採用していたり、検索のパーツが独自UIになっていたり、ナビゲーションバーが無い画面があったり、プッシュで画面遷移するべきところがモーダルで表現されていたり。それらの画面をそれぞれHIGに沿った形のUIにするためにプロトタイピングを繰り返し、開発メンバー全員で議論しながらアプリ全体を再設計しました。

Ver.3.0.0でデザイナーとして学んだこと

HIGの大切さ

HIGはiOSアプリをユーザーが触る上で非常に重要なものです。HIGに記載されているUIは、iPhoneユーザーが日常で頻繁に触れ、自然と使い方を学んでいて、意識せずとも操作できるUIであるからです。

そもそも一休.comレストランのアプリは、簡単に、スムーズにレストランを予約できるということが大前提です。なので、ユーザーがこの目的を達成するために、如何に意識させないUX、UIにするかを考えなければなりません。

プロトタイピングの大切さ

プロトタイピングすることで、実装する前にアプリを疑似体験することができます。デザインモックをベースにユーザー体験を向上させるために議論し、プロトタイピングすることが実際のユーザー体験の向上につながります。ビジュアル化されたものはチームメンバーのさらなる改善案を引き出し、より良いプロダクトをデザインできます。

おわりに

今回は、ファーストローンチから、デザイン的に大きく変更がなされた2度のメジャーアップデートを通して学んだことを紹介しました。これらはデザイナーとして多くの学びを得た体験でした。

まだまだ一休.comレストラン アプリは、ユーザーにより良い体験を提供するために改善しなければならないことがたくさんあります。一休として「こころに贅沢を」を提供できるアプリになるよう、さらなる改善をしていきます。




明日はhayatoiseさんの
「Google Analytics APIでWordPressのタグとカテゴリのPV数を取得する方法」です。

qiita.com

あえてテクニカルなコーディングをしないという選択肢

この記事は一休.comアドベントカレンダー2017の17日目です。 残すところ一週間とすこしですね

一休.com スパ を運用・開発しているid:kichion0526です。

テクニカルな話や一休の苦労話etcは諸先輩方がたくさん書いてくれているので

最近、何を意識して実装しているかを書き残したいと…

ギークなテッキーになりたかった故に小難しいことをしていた

前職からC#で書くことも多く、リフレクションなどを使い倒しメタプログラミングがすらすらできるようになるのが当時の目標でした

一例として
~ゲームにおける様々なアイテムを生成するファクトリ~

public abstract class ItemBase
{
    protected ItemBase(int id) {
        Id = id
    }

    /// アイテムカテゴリを取得します。
    public abstract ItemCategory Category { get; }

    /// アイテムIDを取得します。
    public int Id { get; }

    /// アイテム名を取得します。
    public abstract string Name { get; }
}

public enum ItemCategory
{
    Weapon = 0, // みんな大好き武器
    Armor = 1, // みんな大好き防具 
    GachaTicket = 2, // みんな大好(ry
    ...(などなどいっぱい)...
}

public class Weapon : ItemBase
{
    public override ItemCategory Category => ItemCategory.Weapon;
    ....(コンストラクタ等でid指定etc)...
}

public class Armor : ItemBase
{
    public override ItemCategory Category => ItemCategory.Armor ;
    ....(コンストラクタ等でid指定etc)...
}

public class GachaTicket : ItemBase
{
    public override ItemCategory Category => ItemCategory.GachaTicket;
    ....(コンストラクタ等でid指定etc)...
}

アイテムクラスを定義したら端的にswitch文でファクトリ作ると下記のイメージ

public static class ItemFactory
{
    public static T Create<T>(int id, ItemCategory category) where T : ItemBase
    {
         switch (category)
         {
             case ItemCategory.Weapon:
                 return new Weapon(id);
             case ItemCategory.Armor:
                 return new Armor(id);
             case ItemCategory.GachaTicket:
                 return new GachaTicket(id);
                ...(その他もろもろ)...
         }
    }
}

「新しいアイテムが追加されるたびにここをいじるのはめんどくさいなぁ」
と思うとリフレクションの出番です

public static class ItemFactory
{
    private static readonly Dictionary<ItemCategory, Func<int, ItemBase>> Items;

    static ItemFactory()
    {
        var constructors = typeof (ItemBase).Assembly.GetTypes()
            .Where(x => x.IsSubclassOf(typeof (ItemBase)))
            .Where(x => !x.IsAbstract)
            .Select(x =>
            {
                // コンストラクタの引数の型
                var argumentType = typeof (int);

                // コンストラクタ
                var constructor = x.GetConstructor(
                    BindingFlags.Instance | BindingFlags.Public,
                    null,
                    CallingConventions.HasThis,
                    new[] {argumentType},
                    new ParameterModifier[0]
                );

                if (constructor == null)
                    return null;

                // コンストラクタの引数
                var id = Expression.Parameter(argumentType, "id");

                // コンストラクタをデリゲート化
                return Expression.Lambda<Func<int, ItemBase>>(
                    Expression.New(constructor, id), id
                    ).Compile();
            })
            .Where(x => x != null)
            .ToArray();

            Items = constructors.ToDictionary(x => x(0).Category);  // Keyを生成するためなので引数は何でもいい
    }

    public static ItemBase Create(ItemCategory category, int id)
    {
        return Items[category](id);
    }
}

これでItemBaseを継承するクラスのコンストラクタを辞書化しておけるのでCreateメソッドがこんなシンプルに!
しかも、辞書はstatic変数で保持しているのでアイテム生成のコストも気にしなくていい!
(こんな書き方できるのかっこいい!)

何が得られたの?

メリット

  • 新しいカテゴリのアイテムが追加されるたびにファクトリを修正しなくて良くなった
  • 生成メソッドの行数の少なさ
  • (かっこいいという満足感)

デメリット

  • switch文の実装より初期実装に時間がかかる
  • リフレクション知っているとしても初見は黒魔術
  • Dictionaryなので2つ以上のクラスで同じカテゴリを使うと一律エラーで死ぬ
  • アイテム種類数が少ないと単純に行数が増えてる
  • 後任の人が困る材料になりかねない

もともとは
「新しいアイテムが追加されるたびにここをいじるのはめんどくさいなぁ」
というモチベーションから始まった改修でした

今でこそ振り返ると複雑な概念を導入した割には解決したことが薄すぎると思っています

こうなるとswitch文を書き換えないという選択肢の方が良さそうです

結局何が言いたいの?

ホントの目的がないとテクニカルなコーディングはただの自己満足になると感じています

私自身、本当にテクニカルな手法が必要なのかを意識するきっかけになったのは一休に転職するようになってからです
極端な例では「この改修でどれくらい儲かるか」「今のチームの人が全員入れ替わったら扱えるか」なんてことを考えたりします

一休はエンジニアでもビジネス視点が求められているのでより「事業目標」に向かってコーディングできていると思います

より目的を意識してプログラミングと付き合うと
どうコーディングしたらいいか?の問いに答えが出しやすいのかなと実感してます

まとめ

目的を見失ったプログラミングは自己満足になりがち という話でした

この考えに至ったのも一休にいる優秀な方々のおかげだと思っています これからも社内のエンジニアの方から学びを得て全能感を高めていけたらと…

つらつらと書きましたが先人たちがいい言葉を残してくれていますので、それで締めたいと思います

プログラマが学ぶべき最も大切な技能というのは、コードを書かないときを知ることなのかもしれない。


最も読みやすいコードは、何も書かれていないコードだ。

(「リーダブルコード」和訳版 第13章「短いコードを書く」P.168 より)


明日は sagisakat さんの
アプリのローンチと2度のメジャーアップデート、何を考えてデザインしたか」 です

レストランアプリのアイコンをクリスマス仕様にした話

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

一休レストランiOSアプリの開発ディレクターをしています、id:tsuchidah です。

クリスマスまであと10日となりました。 今回は、ふとした思いつきでアプリのアイコンをクリスマス仕様に変更して、どんなことが起きたのか、数字的な面や得られた知見などをご紹介したいと思います。

きっかけ

一休.comレストランにとってクリスマスはとても大事な時期です。クリスマスに大事な人と素敵なひとときを過ごすため、レストラン選びは外せない…。多くのユーザーさんが一休.comレストランに訪れます。ですが、他の予約サービスなどを使うユーザーさんもたくさんいるはず。そこで、少しでも弊社サービスを候補に入れてもらおう、と考えた時にアプリをクリスマスアイコンにすることを決めました。

狙う効果は2点

  • Storeで目を引くこと → 新規インストール数の増加
  • ホーム画面で目を引くこと → 起動率の増加、そこからのコンバージョン

クリスマスを意識していない人に早いうちからアイコンを目にしてもらい、「あ、そういえばそろそろか」「今年は一休で探してみようかな」と意識してもらえればいいな、という割りと軽い気持ちでスタートしました。

リリース

11月初旬に次のようなかたちでリリース。

f:id:tsuchidah:20171215120437p:plain

背景画像に雪を降らせる…といった案もありましたが、11月上旬という時期は逃したくなく、このかたちに落ち着きました。クリスマスまで一月半ほど期間がありますが、実はクリスマス予約は10月初頭から始まっています。ハロウィンが終わり、11月は一般的にもクリスマスに向かって盛り上がっていく時期。いいタイミングでリリースできたのではないかと思っています。

結果

数字的な変化

新規インストール数

特に伸びませんでした。

これはサービス内容によりけりで結果は異なると思います。弊社サービスはレストラン予約サービス。レストランを予約する時、「まずStoreへ…」となる方は少ないと思います。とは言え、インストール数が増える時期なので多少拍車を掛けられるかもと期待をしたのですが、残念な結果となりました。

起動率

起動セッション数、DAUが伸びました!

セッション数、DAUどちらもおよそ+20%ほど。アプリをフォルダーに入れる方も多く、なかなか目に触れないのでは…と懸念していたので、予想以上の数字でした。 こういった数字の成果が出たことは大変よい知見になりました。

予約数

明確な成果はわかっていません…。

予約数は上昇したものの、この時期は予約数が増える時期なので、クリスマス仕様にした成果とは断言できません。ただしセッションが増えた分CVRが下がらなかったので、悪い結果ではないと判断しています。

その他

別チームへの影響

効果を社内に共有したところ、PCブラウザ、スマホブラウザでも取り入れようという話になり、間もなくリリースされました。

f:id:tsuchidah:20171215123328p:plain

クリスマスの日付をクリックすることで、すぐに検索結果に遷移することが出来ます。そのアイデアは思いつかなかったので、やられた!という気持ちになりました(笑)

社内からの声

とてもポジティブな意見をたくさんいただきました。また、「友達からいいね!と言われたよ」など、間接的にユーザーさんのフィードバックを受け取ることができました。アイコンやUIを季節に合わせて変えるという一見効果がわかりにくい施策で、ユーザーさんにポジティブな印象をもっていただけると思うと、やってよかったと心底思います。

まとめ

ふとした思いつきだった割には、得られたものが多かったため、とても満足しています(笑)また、他社のレストラン予約アプリがクリスマスアイコンに変わっているのを見つけては、「弊社サービスが影響しているんだったらいいなぁ」と妄想をしています。 次もアイコンを変えるチャンスがあれば試してみよう、とチーム内で話していますので、次回乞うご期待です。

一点、iTunesConnectが12/22〜12/27までお休みのため、良いタイミングでアイコンを通常仕様に戻せるかが気がかりです…。

最後に、

クリスマスまであと少し、クリスマス仕様なのは今だけ!(笑)是非アプリやサイトに足を運んでみてください。 まだクリスマスのご予約がお済みでない方は、クリスマス直前割という企画も行っていますので、是非。

みなさま、素敵なクリスマスをお過ごしください。

明日は @hirosawak さんの「一休.com iOSアプリでのfastlane使用例」です。お楽しみに。

データの民主化とオープンソースソフトウェアと SQL Server

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

昨日に引き続き、一休データサイエンス部の id:kitsuyui です。 13 日目のエントリでは Embulk, Redash, DatabaseMEMO の導入の経緯について解説しました。 とても素晴らしいツールを導入できましたが、実はそのままでは一休に導入することができない箇所がいくつもありました。

GitHub 上でどんなアクションをしたかを振り返りたいとおもいます。 その後、自分なりに見出したコントリビューションのコツと反省点について説明します。

私の英語力が不足していますので、つたない英語のプルリクエストを送ってしまっています。 この点についてはご容赦ください。

コントリビューション例

Re:dash + SQL Server が日本語が含まれていると正しく動作しない問題

Re:dash には Google のユーザ認証でログインする機能があるのですが、 このユーザ名と SQL Server の Query Runner がバッティングする問題がありました。

Re:dash から発行したクエリには SQL のコメントでユーザ名を埋め込むのですが、 そこが文字化けを引き起こしていました。

Re:dash のような Python 2.7 系ではありがちなこと (オープンソースソフトウェアの世界ではありがちなこと) ですが、文字コードを正しく扱っていないという基本的な点でした。

データソースの設定で文字コードを指定出来るようにし、クエリもエンコードするようにしてプルリクエストを出し、マージしていただきました。

https://github.com/getredash/redash/pull/1104

f:id:kitsuyui:20171213161119p:plain

Re:dash の Azure SQL Data Warehouse 対応

Azure SQL Data Warehouse という Microsoft 公式の SQL Server 互換の DWH 向けソリューションがあるのですが、 こちらが Re:dash に対応していない、という問題がありました。

そもそも Re:dash は SQL Server に対して pymssql (+ FreeTDS) を利用したクエリランナーを持っていたのですが、 こちらが Azure SQL Data Warehouse には対応していませんでした。

Python を使用して Azure SQL Database に照会する の解説にもある通り、 Microsoft としては pyodbc での実行ができることは検証されているため、 こちらを利用してアクセスできるようにし、プルリクエストを出し、マージしていただきました。

https://github.com/getredash/redash/pull/1906

embulk-output-jdbc で SQL Server Native Client (BCP) 対応

embulk は embulk-output-jdbc というプラグインをインストールすることで、各種の JDBC 互換のデータソースを出力先に追加することができます。 この embulk-output-jdbc には embulk-output-sqlserver が含まれています。

embulk-output-sqlserver には Windows 独自の機能として、 Native Client という Windows の DLL を利用した (非 JDBC) ロード方法があります。 これが SQL Server の最速の Bulk Insert 機能となっていますが、前述の通り Windows にしか対応していないという欠点がありました。

しかし、近年 Microsoft は Linux, macOS 環境にも力をいれており、公式で ODBC ドライバを配布しています。 こちらの ODBC のライブラリの中に Native Client (BCP) 周りの実装も含まれているため、 Linux, macOS でも実行可能であることがわかりました。

embulk-output-sqlserver の Windows 専用となっていた箇所にメスを入れ、 Linux, macOS にも対応し、 プルリクエストを出し、マージしていただきました。

具体的には、 Windows で読んでいる sqlncli11.dll というライブラリの代わりに Linux や macOS では msodbcsql.so, msodbcsql.dylib を使うように書き換えました。

途中 Shift_JIS (CP932) を前提とした箇所があったのを UTF-8 に対応する際にミスをし、一時的にバグを追加してしまいました。 こちらもすぐに修正をプルリクエストし、マージしていただきました。現在ではどの OS でも高速なロードができるようになりました。

一休内では予約情報のトランザクションや会員の行動履歴といった件数の非常に多いデータをロードする際にこのモードを活用しています。 該当の箇所で 5 倍以上の高速化が実現できました。

https://github.com/embulk/embulk-output-jdbc/pull/209 https://github.com/embulk/embulk-output-jdbc/pull/214

embulk-input-bigquery のバージョン変化に伴うプルリクエスト

embulk-input-bigquery という BigQuery を入力データソースとして使えるようにする embulk のプラグインがあるのですが、 こちらが依存しているライブラリ google-cloud-ruby が動作しなくなっていました。

バージョンアップによりインターフェースが変わり、クエリ結果のカラム名などを渡すインターフェースが、文字列ではなく Ruby のシンボルに変化していたためです。 embulk-input-bigquery にもこの変更を加え、プルリクエストを出し、マージしていただきました。

https://github.com/medjed/embulk-input-bigquery/pull/7

DatabaseMEMO (dmemo) の SQL Server 対応について

dmemo はテーブル情報を取得するデータソースとして、 MySQL, PostgreSQL, Redshift に対応しているのですが、 未だ SQL Server には対応していませんでした。

内部の実装をみたところ、テーブル情報を取得する際に、 Rails の Active Record を使っているようでした。

activerecord-sqlserver-adapter という、 SQL Server 用のアダプタを見つけたので、こちらを利用できるように実装しました。

なんとなく勘所はつかめましたので、 Active Record 用のアダプターさえあれば、ほかの種類のデータベースも移植できるかもしれないと考えています。一休ですと、他に BigQuery, Presto などが候補としてあります。

こちらは私 kitsuyui の方の実装がまだこなれていない点があるので、 GitHub 上でやりとりさせていただいている最中です。 https://github.com/hogelog/dmemo/pull/91

DatabaseMEMO (dmemo) を閲覧のみログインせずできるようにする

DatabaseMEMO は Google のアカウントでログインできるのですが、ログインせずに社内からの閲覧のみは許可したいケースがありました。全員が Wiki の編集者とならずとも、利用はできるようにしたいのです。 こちらは crowi-plus などにも 「ログインしていないユーザーにも閲覧のみ許可するオプション」があるのと同様の意図です。

こちらも GitHub 上でやりとりさせていただいている最中です。 https://github.com/hogelog/dmemo/pull/93

まとめ

SQL Server について

基本的には「SQL Server に対応してない」のケースは多いです。 しかし、実際には社会的な土台はほぼ整っていて、ソフトウェア的には少しの修正で対応できてしまう、というケースが多いです。

以前であれば、 SQL Server はエンタープライズ製品・導入が難しい・検証には Windows 機が必要、といういろいろな壁があったかと思います。 しかし、今ではありがたいことに SQL Server には Docker 版 が存在し、 Linux や macOS でも充分に検証できるようになりました。

SQL Server 自体はオープンソースソフトウェアではないですが、実行環境自体がオープンソースソフトウェアに近い構造で用意されていることで、 検証にかかるコストがグッと減りました。

この点は大きな転換点になると思います。

コントリビューションのコツ

期待以上の動作をさせてみる

私なりのコントリビューションのコツは、オープンソースソフトウェアを使い始めたときに、期待以上の使い方をすることです。

  • あえて Linux で embulk-output-sqlserver を Native Client で動作させようとしてみる
  • あえて DatabaseMEMO (dmemo) を SQL Server で動かしてみる
  • あえて Re:dash で Azure SQL Data Warehouse を使ってみる

こういうときに、最初から期待以上の動作をすることはありません。しかし、そっと閉じるのではなく、 想像力を働かせて「似た環境では動いてるはずなのに、なぜ?」「誰かの環境では動いているのに、なぜ?」といった問いからスタートして、あとは地道に帳尻を合わせていくことです。

できそうな材料をしらべる

ここまでわかれば、まずは一点だけが突破するような状態を作ることができます。

  • embulk-output-sqlserver で呼んでいる DLL の関数を調べ、全てが ODBC ドライバにもあるかを調べる
  • DatabaseMEMO で使っているフレームワーク (Rails) のドライバを調べ、 SQL Server 版が使えないか調べる
  • Re:dash の実装言語である Python で Azure SQL Data Warehouse に接続する方法を調べる

道具を揃える

材料があっても、見知らぬ環境ではうまく戦えません。道具を調べます。 ビルド方法・テスト方法・デバッグ方法・インタプリタ・パッケージマネージャあたりをおさえておけば大丈夫です。

  • embulk の場合は ./gradlew を叩けばビルドができます。 embulk irb でインタプリタに入れます。
  • DatabaseMEMO は Dockerfile があるので Docker で環境をつくれます。 rails console コマンドで Rails の irb に入れます。
  • Re:dash は (Python なので私はあまり困りませんでしたが) ./manage.py shell でインタプリタに入れます

また、未知のオープンソースソフトウェアの場合だと、これらを調べるのに時間がかかってしまうことがあります。

その場合には

  • strace (Linux のプロセスが実行するシステムコールを見ることができるコマンドです)
  • netcat (ポートの開閉をしらべたり、ポートを転送したり、サーバをでっちあげたりするのに使います)
  • print デバッグ (あまり上品ではありませんが、どこでも使えます)

などの、より低レベルな道具を使えば問題ありません。 (どこでも使える、というメリットもあります。)

また、利用するソフトウェアに CONTRIBUTION.md やライセンス等がある場合、それらも読み込んでおきます。

一点突破

後先考えずにあちこちに手を入れて、一点でいいので動くようにします。 このレベルでは 100% の状態である必要はありません。ボロボロでも疎通までができれば最高です。

  • DLL のパスだけ .so や .dylib に変えてコンパイルすると、 Linux でも文字化けした状態でなら文字列が挿入できる状態
  • SQL の実行の大部分は失敗するが、 DatabaseMEMO から SQL Server への接続まではできる状態
  • 幾つかのデータベースが見えないが、 Re:dash で SELECT 1 クエリが実行できる状態

きれいにする

一点突破したら、あとはそこを中心に綺麗に整地していきます。 一点突破したときにソースコードを散らかしてしまった場合も、このタイミングできれいにしていきます。

なるべく git diff が最小になるようにします。場合によっては複数のプルリクエストに分けます。

  • ドライバのパスや名称を指定出来るようにする ・文字化けしないように修正
  • SQL Server 用にソースコードをちまちま修正 (SELECT ... FROM ... LIMIT nSELECT TOP n ... FROM ... に、といった翻訳)
  • データベース一覧のクエリなどを修正する

あとはコミッタの方と相談

ここまでできたら、プルリクエストが作れます。 当たり前ですがコミッタやメンテナのほうが、自身のソフトウェアに対して深い洞察とモチベーションをもってます。

できるさえわかったら、深追いする前に Issue などで挙げて、具体的な実装については相談しつつ進めるのが良いと思います。

反省点

自分の場合、原因がわかると修正も同時にできてしまうことが多いので、 Issue とプルリクエストを同時に送ってしまう、 またはプルリクエストのみを送ってしまうというということが多かったです。だいぶ失礼なことをしてしまったと今は反省しています。

また、 Issue でのディスカッションを踏まえて実装に入るほうが、より問題の本質を捉えやすかったかな?とも思います。 この点は 2018 年は改善していきたいです。

最後に

日頃いろいろなオープンソースソフトウェアにお世話になることが多いので、来年もどんどんオープンソースソフトウェアを利用し、ガンガン還元していきたいです。

明日 (15 日目) は tsuchidah さんによる「アプリアイコンをクリスマス仕様にしたらどうなった」です。

データエンジニアとデータの民主化 〜脱・神 Excel 〜

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

一休データサイエンス部の id:kitsuyui です。データエンジニア兼データサイエンティストをやっています。 この記事はもともとアドベントカレンダー上では「脱・神 Excel (仮)」という名前で枠で取っていたのですが、 少し主語が大きすぎたかな?と反省しています。 書いているうちに全く主旨が変わってきましたので、副題とさせていただきました。

今回は一休社内でのデータエンジニアリングにまつわる負担、それらを解決する Redash, Embulk, DatabaseMEMO の導入の流れを書こうと思います。

また、その過程で副次的に発生した FLOSS へのコントリビューションなどなどについては、 14 日目のエントリで説明したいと思います。

一休とデータ活用

一休は今日まで上質な宿・レストランの予約サービスを運営してきて、今年の 7 月で創業 20 年目になりました。 Web でのサービスを提供する企業としては比較的に古参プレーヤーの方だと思います。会員数も 500 万会員を突破しています。

蓄積されてきた膨大なユーザの行動・予約データがあり、データの持つ価値が非常に大きい企業です。

  • 2012 年ごろから、一休ではデータ分析を重視して施策を決定することが増えてきました。
  • 2014 年ごろから、 BigQuery などの巨大データ向けのソリューションを併用することが増えてきました。
  • 2015 年ごろから、基幹データベースとは独立したデータ分析のための専用のデータベースとして、データウェアハウスの構築が進んできました。

今では一休社内の様々な施策が、データドリブンまたはデータ分析にもとづいて行われるようになっています。

1. Re:dash 導入によるデータの民主化

Before

社員にデータ分析の習慣が定着していき、こぞってデータを見るようになると、データの抽出業務が大量に発生していました。 エンジニアでなくとも CSV ファイルさえあれば Excel で勝手に分析することができるのですが、その CSV ファイルこそがデータエンジニアなしでは用意できないものだったのです。

データ活用者からすると分析をしたいのにデータが手に入るまで時間がかかり、データエンジニアからすると「自分は右から左にデータを渡すだけで手一杯になってしまう」というところに負担やフラストレーションを感じるという、お互いに嬉しくない状態になっていました。

f:id:kitsuyui:20171211175520p:plain

Do

そこで一休では 2016 年にデータウェアハウスと共に Re:dash を社内に導入しました。

Re:dash はデータベースへの接続機能を持っており、登録した SQL を実行することで、きれいなグラフや表を生成することができます。 また CSV や Excel ファイルを生成することができます。 Re:dash は Web アプリケーションであり URL を持つので、 URL を Slack で共有することができます

f:id:kitsuyui:20171211180137p:plain

f:id:kitsuyui:20171211183015p:plain

After

定型的なデータ抽出作業は全て Re:dash のボタンを押すだけで実行できるようになったため、データエンジニアがボトルネックとなることは減っていきました。

また、一休では Re:dash 以外にも Tableau を営業ツールとして導入しており、これもデータエンジニアの負荷を低減しています。

f:id:kitsuyui:20171211175534p:plain

2. データウェアハウスと Embulk

Before

Re:dash によってデータ抽出業務の負荷は大きく下がったのですが、データを観る人が増えたことによって、ますます社内のデータ活用のモチベーションは上がりました。 そのため、基幹データベースからデータウェアハウスにロードしたり、その過程で正規化するタスクしたり、また別のデータベースに移し替えたりという、いわゆる ETL 業務の効率化と安定性が急務になっていました。

しかしながら、個々の ETL のコンフィギュレーションが分散して存在していたため、著しく可搬性・メンテナンス性が損なわれていました。

Do

そこで Embulk の出番です。 embulk では .yml ファイルで ETL (Extract, Transform, Load) の組を記述することができます。

in:
  type: sqlserver
  table: some_table
  ...

out:
  type: sqlserver
  table: some_table
  ...

素晴らしい特徴として、 input と output のインターフェースが完全に独立しているということがあります。 そのため、「データウェアハウスに入れているあのテーブルを BigQuery にもロードしたい」というようなケースでは、単に output だけ変えればそのまま動作します。

After

embulk の導入によって、いろいろなサーバに分散していた ETL 処理とそのコンフィギュレーションを 1 つのシステムとして統合することができました。 また、これらの .yml ファイルは GitHub で管理しているため、 ETL の過程がどのように変化したかを時系列で追うことができます。

3. 肥大化するテーブル定義の山と DatabaseMEMO の導入

社内でデータ分析がいよいよ活性化して、道具が整ってくると、今度はデータの文脈や名称に対しての理解が重要になってきます。

今日まで何度も改良を加えながら受け継いできた基幹のデータベースは 宿泊予約 のサービスで 500 テーブル以上レストラン予約のサービス200 テーブル以上 、その他全社の基幹システムのテーブルは合計で 1,000 テーブルを超え ます。 また、基幹システム以外にもデータ分析用に使うテーブル (500 テーブル以上) や、社内システム用のテーブル定義なども含めると、 2,000 テーブル以上 にもなります。 カラム数はさらにその 10 倍程度 でしょうか。

Before

これら基幹データベースのテーブル定義は、全て SVN 上の Excel ファイルに記述する運用をしていました。 2016 年に SVN から GitHub への切り替えを行ったのですが、 Excel によるテーブル定義は Git での差分検出・マージがしづらい という問題がありました。 また、 Excel ファイル自体の行数・列数が大きくなりすぎ、検索性が下がり開発速度が非常に低下 していました。 この Excel ファイルが含んでいた職人的なマクロはメンテナンス不可能になっていました。

一方で Redash の導入により 営業やマーケターの中にも SQL を書ける方が増え てきました。 しかし、基幹 DB の定義は GitHub に置かれていたため、正しい定義はエンジニアにしか公開されていません でした。 また、 データウェアハウスのテーブル定義がどうなっているのかは、そのテーブルを作った本人以外には誰にもわかりません でした。

Do

そこで DatabaseMEMO (dmemo) を導入しました。 DatabaseMEMO は Cookpad がつくった データベース・テーブル・カラムを Markdown 書式で記述・検索・閲覧できるデータベース専用の Wiki のようなものです。

サーバを設置し、簡単なスクリプトを書いて旧来の Excel によるテーブルの定義をインポートしました。 f:id:kitsuyui:20171211181949p:plain

また、 一休独自のカスタマイズとして、テーブル説明文から GitHub へリンクも付与し、ログインなしでも閲覧までは可能に しました。

After

旧来のテーブル定義の歴史を維持しながらも、より高速に検索したり、気軽に Tips を追記したり ということができるようになりました。 テーブルが ソースコードのどこで使われているかも、瞬時に検索 することができます。

さきほど挙げた Re:dash のメリットと同じですが、 DatabaseMEMO は URL を持つので Slack 上で定義を簡単に受け渡しすることができるようになりました

まとめ

このようにして、データ分析業務に関わる様々なタスクの効率化・高速化・明文化を推し進めています。

世界的にデータ活用が叫ばれている時代の中で、データエンジニアは単にデータ活用の召し使いになるのではなく、だれもがデータ活用できるような風土づくりを率先して行い、社内のデータ活用のハードルを引き下げていく事が重要だと、私は考えています。