KLabGames Tech Blog

KLabは、多くのスマートフォン向けゲームを開発・提供しています。 スマートフォン向けゲームの開発と運用は、Webソーシャルゲームと比べて格段に複雑で、またコンシューマゲーム開発とも異なったノウハウが求められる領域もあります。 このブログでは、KLabのゲーム開発・運用の中で培われた様々な技術や挑戦とそのノウハウについて、広く紹介していきます。

KLab Advent Calendar 16日目の記事です

はじめまして、jukey17です
Advent Calendarを書くのは初めてです
よろしくお願いします!

この記事に書いてあること

この記事では、jukey17がVRゲームを初めて作ってみて、気をつけた内容を簡単にまとめています

これからVRのゲームを作ってみようと思っている方の参考に少しでもなってもらえれば幸いです

VRのゲームを作ろうと思ったキッカケ

VR元年と言われた2016年 、個人でも楽しむことができるVR製品が次々に発表・発売されていき「VR製品買いました」「VR楽しい!」「VR凄い」という声が開発仲間の周りから少しずつ聞こえてくるようになりました

私自身全く興味がなかったわけではなく最初は体験ブースなどを見つけたら軽く遊んでみる程度の興味はありました
が、今一歩踏み込めないままでいました

そうこうしているうちについにはゲーム開発とはゆかりのない仕事をしている友人からもVRの話題が聞こえるようになってきてやっと ちょっとこれは実際に作ってみたほうがいいのでは? と思うようになりました

気がついたら今年も残りわずか、 このまま踏み込まないまま1年が終わってしまうのも勿体無い! と思い、VRゴーグルを購入しゲーム開発をはじめました

作ったゲーム 『World Jab VR』

image

正面から飛んでくるジャブ(パンチ)を左右に避けるゲームです
少し昔、TVで話題になっていた某ボクシング3兄弟の父親が行っていた練習の1つをモチーフにしました

このゲームは Google VR SDK for Unity(※以下SDK)を使ってUnityで作りました

ジャイロセンサーを使用して横の傾きを検出し、プレイヤー自身を左右に動かしてジャブを避けます
何回連続で避けられるかを競うゲームとなっています

初めてのVRゲーム開発だったのですが、実際に開発を進めていく中で気をつけたことの中でやってよかったと思った内容がありました

以下にその内容を紹介します

気をつけて良かったこと

先にUnityEditor上で遊べるようにした

私はまず初めに VRのゲームを作ろう! と勢い良くSDKのサンプルシーンを起動し、実機に転送して遊んで見るところからスタートしました

しかし、プロジェクトをビルドして、端末にバイナリを転送し、起動した状態で端末をVRゴーグルに装着して…
というのを 開発中に何度も繰り返すのはとても大変 だとすぐに気づきました

そこでまずはSDKの導入は一旦せず、UnityEditor上のみで動作が確認できるように開発をはじめました

とはいっても、SDKは簡単にゲームのVR対応ができるように GvrViewerMain.prefab をシーン上に配置するだけでVR対応が済むような構造になっているので、この対応は非常に簡単です

初めはUnity上で開発を進めていき、 ある程度遊べるようになってきたら対象のプレハブをひょいとシーンに置けば良いのです

但し、今回作った World Jab VR はゲームの仕様上プレハブの設置とセットでジャイロセンサーの入力機構も実装しなければなりません

ある程度遊べるようになってからも膨大な微調整の時間がかかるのは容易に想像できます

そこで更にもうひと工夫することにしました

入力操作方法を切り替えられるようにした

今回は3つの入力操作方法を切り替えられるようにしました

  • キーボード操作

PC(UnityEditor)想定
※先程のgifアニメーションがPC操作

  • ジャイロセンサー

スマホ(VR)想定

vr_play

  • タッチ操作

スマホ(非VR)想定

touch_play

最後のタッチ操作はキーボードとジャイロセンサーの出し分けを作ったときに「幾らでも新しいパターンが追加できるな」と思い、サクッと作ってみてしまった副産物です

この3つの入力操作方法は実行前に設定を切り替えられるようにしました

これのおかげで下記のような対応がとてもしやすくなりました

  • VRで遊んでいる間に問題が発覚!
    1. UnityEditorで動作を確認と問題を修正の繰り返し
    2. 修正が完了したらVRで再度遊ぶ
  • 新しい機能を追加したい!
    1. UnityEditorで機能の実装と微調整の繰り返し
    2. 実装が終わったらVRで最終チェック

一度VR対応したせいで細かい動作確認が億劫になり、開発スピードが落ちていく…なんてことは防げるはず!
(モチベーション維持は大事です)

今後の展望

UI上からのパラメータ調整・設定切り替え

いわゆるデバッグ機能です
現在はUnityEditor上のインスペクタから設定できるようにしています

しかしこれだと実機で動作確認したときに微調整をするために一旦UnityEditorに戻らなければなりません
大きな問題の修正や新機能の追加などプログラムを書き換えなければならない内容では仕方ないことですが、パラメータの調整やオプションの切り替えだけで済む内容なのであればなるべくUnityEditorに戻りたくありません

VRゴーグルを装着しているときは 視点ポインターで選択 できるようにしないといけないため歯応えがありそうな内容です

追加したい機能のアイデアももう思い浮かんでおりドンドンと実装をしたいところ...
もあるのですが、こういった 何度も繰り返し時間の掛かる部分は積み重なっていくとかなりの時間を消費していて開発スピードを下げる原因になる ため、足回りの部分の整備も怠らずに進めていきたいところです

ジャブを繰り出す側もプレイヤーにしてしまう

現状はAIが繰り出すジャブをひたすら避けるゲームになっていますが、通信機能を使って2人で遊べるように したら見栄えも含めてもっと面白くなりそうです

避ける側はVRゴーグルを装着、ジャブをする側はスマホにフリック操作してジャブを繰り出す

フェイント機能 などがあると駆け引きが増えて盛り上がりそうです

(※操作方法は暫定)

最後に

初めてのVRのゲームを作ってみましたが、どんな内容のゲームをつくるかを考える際に『どんな場所で遊ぶのか』『遊んでいる人は外から見てどのように見えているのか』など、 ゲームの内容の外側のことも気にしたりしながら作ったので予想以上に難しく・そして楽しかったです

今回は開発を始めていく中での気をつけておくと良いことを紹介しましたが、今後はそういった空間を含めたゲームデザインや新デバイスならではの技術に触れるような内容にもチャレンジしたいと思いました

以上です

この記事は KLab Advent Calendar 2016 の14日目の記事で @kakkun61 が担当します。

compile SDK version や build-tools version を Unity アプリで指定するにはどうすればいいの?という話をします。

この記事の内容は OS X 10.11.6 で確認しています。

Android アプリケーションのビルド時に指定するバージョンについて

Android アプリケーションのビルド時に指定するバージョンについては「基本をおさえる!Androidアプリで指定するバージョンについて(compileSdk,buildToolsVersion,....) - Qiita」にまとまっています。簡単に引用・要約します。

Android アプリケーションのビルド時に指定するバージョンは下記7つあります。

  • SDK バージョン
    • コンパイル SDK バージョン
    • 最小 SDK バージョン
    • 最大 SDK バージョン
    • ターゲット SDK バージョン
  • build-tools バージョン
  • Gradle の Android プラグインのバージョン
  • Gradle のバージョン
SDK バージョン
コンパイル SDK バージョン
コンパイル時に使用される Android.jar のバージョンでここにない API はコンパイルできません。build.gradle に記載します。値としては API レベルです。次の3つも同様です。
最小 SDK バージョン
インストールできる OS の下限で、条件付きコンパイル等しない限りこれより古い API は利用できません。build.gradle に記載します。
最大 SDK バージョン
インストールできる OS の上限です。build.gradle に記載します。
ターゲット SDK バージョン
OS がこの値を見て処理をし分けます。例えば UI テーマの変更や実質メニューキーの表示非表示があります。引用元記事にスクリーンショットがあります。build.gradle に記載します。
build-tools バージョン
その名の通り build-tools のバージョンで aaptaidldxjack.jarjill.jar などが含まれます。build.gradle に記載します。
Gradle の Android プラグインのバージョン
プロジェクトの build.gradle に記載します。
Gradle のバージョン
gradle/wrapper/gradle-wrapper.properties にダウンロード元 URL として記載します。

Unity 3D から生成した Android プロジェクトを Gradle でビルドする

Unity 3D を使って直接 APK を生成する場合、コンパイル SDK バージョンと build-tools バージョンを指定することができず、ビルドするコンピューターにインストールされている最新版が使用されます。これでは、ビルドサーバーを複数案件で共用している場合、案件ごとでこれらバージョンを固定してビルドすることができなくなります。なので、Unity 3D から Android プロジェクトを生成し、Gradle でビルドすることを考えます。

ただ、これではまだ問題があります。Unity 3D の生成する Android プロジェクトの形式が ADT 形式なのです。Google による ADT のサポートは終了しています。

これを Gradle でビルドするために Gradle ビルドスクリプトに少し工夫を加えます。

処理の流れ

  1. Unity エディターから Android プロジェクト(ADT 形式)を出力
  2. Gradle でビルドできるよう Gradle 関連ファイルのリンクを作成
  3. Gradle でビルドして APK 生成

ディレクトリー構成

下記のようなディレクトリー構成とします。サンプルリポジトリーはこちら

  • Assets/
    • Editor/
      • BatchBuild.cs
  • ProjectSettings/
  • gradle/
    • gradle/wrapper
    • build.gradle
    • gradlew
  • make-gradle-buildable.bash

BatchBuild.cs

バッチビルドでは Build() 関数を呼び出せば build-adt ディレクトリーに Android プロジェクトが生成されます(処理の流れ 1)。BuildOptions.AcceptExternalModificationsToPlayer を指定することで Android プロジェクトの生成を指定しています。その他のパラメーターは適宜変更してください。

using UnityEngine;
using UnityEditor;
using System.Linq;

public static class BatchBuild
{
    public static void Build()
    {
        EditorApplication.Exit(DoBuild());
    }

    static int DoBuild()
    {
        Debug.Log("Build Start");
        var options = BuildOptions.SymlinkLibraries
                      | BuildOptions.Development
                      | BuildOptions.AllowDebugging
                      | BuildOptions.AcceptExternalModificationsToPlayer; // このオプションでプラットフォームのプロジェクトが生成される

        var scenes = EditorBuildSettings.scenes.Where(s => s.enabled).ToArray();

        PlayerSettings.bundleIdentifier = "com.example.gradletest";
        PlayerSettings.bundleVersion = "1";
        PlayerSettings.productName = "Gradle Test";
        PlayerSettings.Android.bundleVersionCode = 1;
        PlayerSettings.Android.minSdkVersion = AndroidSdkVersions.AndroidApiLevel22;

        var errorMessage = BuildPipeline.BuildPlayer(scenes, "build-adt", BuildTarget.Android, options);
        if (string.IsNullOrEmpty(errorMessage))
        {
            Debug.Log("Build Success");
            return 0;
        }
        Debug.LogError("Build Failure");
        Debug.LogError(errorMessage);
        return 1;
    }

    [MenuItem("File/Export Android Project #%a")]
    public static void MenuBuild()
    {
        DoBuild();
    }
}

make-gradle-buildable.bash

処理の流れ 1 で、build-adt ディレクトリーに Android プロジェクトが生成されるので、Gradle でのビルドができるようにそこにファイルのリンクを置きます(処理の流れ 2)。prod_nameBatchBuild.csPlayerSettings.productName に指定されたものです。

#!/usr/bin/env bash -ex

prod_name=$(ls -1 build-adt)
ln -sFv "$(pwd)/gradle/"* "build-adt/$prod_name"

gradle/build.gradle

処理の流れ 3 のADT 形式のプロジェクトを Gradle でビルドするためのビルドスクリプトです。sourceSets の部分がキモで、各ソースファイルの位置を ADT 形式に合うように指定しています。他の部分は一般的な Android のビルドスクリプト同様に適宜変更してください。

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.1.0'
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    defaultConfig {
        applicationId "com.example.gradletest"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }

    // Lint エラーが出てもビルドを継続するように指示
    // Unity の生成物が Lint にひっかかるので必須
    lintOptions {
        abortOnError false
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-unity.txt'), 'proguard-rules.pro'
        }
    }

    // ADT 形式のディレクトリー構成に対応するための記述
    sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            aidl.srcDirs = ['src']
            renderscript.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']
            jniLibs.srcDirs = ['libs']
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:23.4.0'
}

ビルド手順

以上の準備をすると、下記コマンドでビルドができるようになります。生成された APK は build-adt/$prod_name/build/outputs/apk 以下にあります。./make-gradle-buildable.bash./gradlew build の実行は、Unity の post process build にしてしまうのもありだと思います。

/Applications/Unity/Unity.app/Contents/MacOS/Unity -batchmode -quit -executeMethod BatchBuild.Build
./make-gradle-buildable.bash
pushd "build-adt/$(ls -1 build-adt)"
  ./gradlew build
popd

KLab Advent Calendar 10日目の記事です。KLab分析基盤チームの高田です。

分析基盤チームでは、社内向けに各種KPIを提供している他、KG SDKのKPIレポートシステムを通じて、パートナー向けにシステムを提供しています。

今回は、先日re:Invent 2016で発表されたばかりのAWSの新サービスAthenaを試してみました。KLabの分析基盤システムでは、すでにRedshiftやEMRを使用していますが、Athenaには、これらを補うような役割(低コストで導入し、アドホックな分析や定型的なレポートの作成をサポートするといった用途)を期待しています。

Athenaとは?

logo

ひとことで言えば、Athenaとは、S3上に置いてあるデータを高速にSQLで集計・分析できるサービスです。内部はPrestoをベースとしつつ、独自の改修をくわえて使用しているようです。Athenaの紹介としては、Amazon Web Serviceブログのこちらの記事も参考になるでしょう。

AWSでは以前より、EMRというサービスでHadoopやPrestoの機能を提供していましたが、Athenaでは自前でクラスタを組む必要なしに、クエリ検索機能を使用できます。なお、料金はクエリ量単位(5TBスキャンあたり$5)で設定されています。検索機能のみを提供するサービス形態や、課金形態はGoogleのBigQueryに似たものになっています。

複数のサービス・システム名が登場して複雑なので、以下に関連するサービスをまとめておきました。

名称 説明 提供者
Hadoop データの分散処理用のフレームワーク。 Apache Software Foundation
Hive Hadoop上で動くクエリエンジン。SQLベースの検索機能を提供。 Apache Software Foundation
Presto Hiveに似たクエリエンジン。Hadoop上でも動作するがそれ以外のデータソースも選択可能。 Facebook
EMR AWS上でHadoop/Prestoなどの分散システムを提供するサービス。 Amazon
Athena Presto相当の検索機能のみを提供。 Amazon

クエリ実行画面を試す

では早速クエリの実行を試してみましょう。sampledb というデータベースが最初から作成されており、クエリの実行をすぐに試すことができます。

athena1

新しいデータベースやテーブルの作成も、Web画面から実行できます。

athena2

データ準備

現実的なユースケースでパフォーマンスを見たいので、自前のデータも準備し、普段の分析業務で使用するようなクエリを投げてみます。

KLabの分析基盤チームの場合、データの多くは、tsv形式でS3上に置かれています。これをそのまま検索対象にできれば理想的なのですが、残念ながら圧縮形式やディレクトリ構成の問題で、何も手をくわえずに検索対象にするということはできませんでした。

※Athenaの検索対象にするためには、ディレクトリ構成に一定のルールが必要です。また、圧縮形式としては、現状Snappy, Zlib, GZIPのみがサポートされているようです(FAQを参照)。

今回は以下の三種類のデータを用意しました。

種別 説明 1日分のファイルサイズ目安(gzip圧縮時)
dau ゲームにアクセスしたユーザー 数百KB
install インストールしたユーザーのリスト 数十KB
locale ユーザーの国情報 数十MB

それぞれ、S3上の以下のようなパスにアップロードしておきます。Athena用にカラム名を除外し、gzip圧縮しましたが、それ以外はごく普通のtsv形式です。データはすべて日別にわかれており、 dt=日付 というパスにアップロードします。それぞれ11月1日から12月5日までのデータをアップロードしてあります。この key=value というパス名は、Athenaにパーティションを認識させるためのルールになります。この形式にのっとらない場合は、手動でパーティションを追加する必要があります。また、パスはテーブルごとにわける必要があります。

s3://--bucket--/athena/dau/dt=2016-12-01/dau_2016-12-01.tsv.gzip
s3://--bucket--/athena/dau/dt=2016-12-02/dau_2016-12-02.tsv.gzip
...
s3://--bucket--/athena/install/dt=2016-12-01/install_2016-12-01.tsv.gzip
...
s3://--bucket--/athena/locale/dt=2016-12-01/locale_2016-12-01.tsv.gzip

テーブル作成

あとは、Athena上でテーブルを作成するだけで、上記のファイルを検索対象とすることができます。

今回は以下のようなテーブルを作成します。テーブルの作成は、Web画面からも実行できるのですが、単純にCREATE TABLE文を実行するだけでも問題ありません。なお、ここでは dt (日付)をパーティションに指定しました。パーティションに指定したカラムは、SQL上で疑似的なカラムとして扱われるため、テーブル本体には同名のカラムを含めることができません。そこでテーブル本体の方の日付カラムには、 dtDontQuery という名前を設定しておきます。

ちなみに、テーブル定義はあれこれ試行錯誤していたのですが、テーブルスキーマがキャッシュされているのか、同名のテーブルをDROPしたあと、定義を修正して再度CREATE TABLEを実行しても、しばらくの間、古い定義が参照されてしまうという問題がありました。

CREATE EXTERNAL TABLE IF NOT EXISTS dau (
  dtDontQuery date,
  player_id string,
  pv int
)
PARTITIONED BY (dt string)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe'
WITH SERDEPROPERTIES (
  'serialization.format' = '    ',
  'field.delim' = ' '
) LOCATION 's3://--athena--/athena/dau/'
CREATE EXTERNAL TABLE IF NOT EXISTS install (
  dtDontQuery date,
  player_id string,
  datetime timestamp
)
PARTITIONED BY (dt string)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe'
WITH SERDEPROPERTIES (
  'serialization.format' = '    ',
  'field.delim' = ' '
) LOCATION 's3://--athena--/athena/install/'
CREATE EXTERNAL TABLE IF NOT EXISTS locale (
  dtDontQuery date,
  player_id string,
  region string,
  datetime timestamp
)
PARTITIONED BY (dt string)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe'
WITH SERDEPROPERTIES (
  'serialization.format' = '    ',
  'field.delim' = ' '
) LOCATION 's3://--athena--/athena/locale/'

S3上のパーティションを認識させるには、各テーブルについて以下のクエリを実行します。

MSCK REPAIR TABLE dau

athena-msck

パーティションが正常に認識されたかどうかは以下のクエリでパーティション一覧を表示することで確認できます。

SHOW PARTITIONS dau

検索

いくつかクエリを実行し、性能を見てみます。残念ながらデータそのものはお見せできないのですが、以下、参考のため、クエリと実行時間を掲載しておきます。現状SQL上で使用できる関数なども、公式ドキュメントに記載がないため、やや試行錯誤が必要でした(ANSI SQLに準拠ということなので、ある程度は検討がつくのですが)。

今回追加したファイルのうち、このlocaleのデータがもっとも巨大です。全体で1.45GBほどあるのですが、単純なCOUNTクエリであれば6秒ほどで返ってきました(スキャン自体は行なっているようです)。

SELECT COUNT(*) FROM locale

(Run time: 6.87 seconds, Data scanned: 1.45GB)

DAUを国別に集計してみます。これもlocaleのファイルが大きいためか、20秒ほどかかりました。

SELECT dau.dt, locale.region, COUNT(*)
FROM dau
JOIN locale
  ON locale.player_id=dau.player_id
     AND locale.dt=DATE('2016-12-05')
WHERE
  dau.dt BETWEEN DATE('2016-12-01') AND DATE('2016-12-05')
GROUP BY dau.dt, region
ORDER BY dau.dt, region

(Run time: 18.45 seconds, Data scanned: 45.54MB)

インストールユーザーの3日後の継続率を集計してみます。数秒です。

SELECT i.dt, COUNT(i.player_id) AS install,
  COUNT(dau3.player_id) AS r3
FROM install AS i
LEFT JOIN (
  SELECT player_id, dt
  FROM dau
  WHERE
    dt BETWEEN DATE_ADD('DAY', 3, DATE('2016-11-01')) AND DATE_ADD('DAY', 3, DATE('2016-11-05'))
) AS dau3
  ON dau3.player_id=i.player_id AND dau3.dt=DATE_ADD('DAY', 3, i.dt)
WHERE
  i.dt BETWEEN DATE('2016-11-01') AND DATE('2016-11-05')
GROUP BY i.dt

(Run time: 3.62 seconds, Data scanned: 3.65MB)

3日連続でログインしているユーザー数を出してみます。Athenaが苦手なクエリなのか、スキャン量が少ない割に1分半もかかっています。

SELECT t.dt, COUNT(*) as cnt FROM
  (SELECT d.dt
     FROM dau AS d
     LEFT JOIN dau AS d2
        ON d.player_id=d2.player_id
          AND d2.dt>=DATE_ADD('DAY', - 2, d.dt) AND d2.dt<d.dt
     WHERE
       d.dt BETWEEN DATE('2016-12-01') AND DATE('2016-12-05')
     GROUP BY d.dt, d.player_id
     HAVING COUNT(d2.dt)=2
  ) t
GROUP BY t.dt

(Run time: 1 minutes 28 seconds, Data scanned: 24.86MB)

こちらはサブクエリを使用するように書き変えることで大きく実行時間が変化しました。RedshiftやBigQueryと同じで、直接テーブルをJOINするのはあまり効率がよくないようです。参考のため、修正後のクエリも掲載しておきます。

SELECT t.dt, COUNT(*) as cnt FROM
  (SELECT d.dt
     FROM dau AS d
     JOIN (SELECT player_id, dt FROM dau
           WHERE
             dt BETWEEN DATE_ADD('DAY', - 2, DATE('2016-12-01'))
               AND DATE('2016-12-05')
          ) AS d2
     ON d2.player_id=d.player_id
       AND d2.dt >= DATE_ADD('DAY', -2, d.dt)
       AND d2.dt < d.dt
     WHERE
       d.dt BETWEEN DATE('2016-12-01') AND DATE('2016-12-05')
     GROUP BY d.dt, d.player_id
     HAVING COUNT(d2.dt)=2
  ) t
GROUP BY t.dt

(Run time: 4.34 seconds, Data scanned: 6.97MB)

以上のように、いくつかクエリの書き方で気をつけるべき点があるようですが、基本的には十分実用的な性能です。

Parquetへの変換

より効率のよいデータの格納方法として、AthenaではApache Parquetのような列志向のフォーマットもサポートされています(列志向フォーマット: データをカラムごとに格納するフォーマット)。Parquetへの変換方法として、公式ドキュメントで紹介されているHiveによる変換を試してみました(参照)。

EMR上でHiveを立ち上げ、S3上のテーブルを読み込ませ、Hive上でテーブルをParquet形式に変換します。

概念図: parquetへの変換

まず以下のようなSQL(HiveQL)スクリプトを用意し、S3上に保存します(DAU以外のテーブルは省略してあります)。なお残念ながらParquet形式はDATE型に対応していないため、ここでは文字列に変換しています。また以下のクエリでは、ダイナミックパーティション(クエリ結果によってパーティションを決定する)を使用しているため、 最初の方にある hive.exec.dynamic.partition の設定が必要となります。

ADD JAR /usr/lib/hive-hcatalog/share/hcatalog/hive-hcatalog-core-1.0.0-amzn-5.jar;
SET hive.exec.dynamic.partition=true;
SET hive.exec.dynamic.partition.mode=nonstrict;

CREATE EXTERNAL TABLE IF NOT EXISTS dau (
  dtDontQuery string,
  player_id string,
  pv int
)
PARTITIONED BY (dt string)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe'
WITH SERDEPROPERTIES (
  'serialization.format' = '    ',
  'field.delim' = ' '
) LOCATION 's3://--bucket--/athena/dau/';
MSCK REPAIR TABLE dau;

CREATE EXTERNAL TABLE  p_dau (
  dtDontQuery string,
  player_id string,
  pv int
)
PARTITIONED BY (dt string)
STORED AS PARQUET
LOCATION 's3://--bucket--/athena/parquet/dau/';

INSERT OVERWRITE TABLE p_dau PARTITION (dt) SELECT dtDontQuery, player_id, pv, dt FROM dau where dt BETWEEN '2016-11-01' AND '2016-12-05';

awscliを利用し、Hiveを立ち上げて実行させます。

export REGION=us-east-1
export SAMPLEURI=s3://--bucket--/athena/dau/
export S3BUCKET=--bucket--


aws emr create-cluster --applications Name=Hadoop Name=Hive Name=HCatalog \
--ec2-attributes KeyName=kg-kpi-keypair,InstanceProfile=EMR_EC2_DefaultRole \
--service-role EMR_DefaultRole --release-label emr-4.7.0 \
--instance-type m1.large \
--instance-count 1 --steps Type=HIVE,Name="Convert to Parquet",\
ActionOnFailure=CONTINUE,ActionOnFailure=TERMINATE_CLUSTER,Args=[-f,\
s3://path/to/hive-script.q ,-hiveconf,INPUT=${SAMPLEURI},-hiveconf,OUTPUT=s3://${S3BUCKET}/athena/parquet,-hiveconf,REGION=${REGION}] \
--region ${REGION} --auto-terminate

ジョブの実行には1時間程度かかりました。ジョブの完了後S3を見ると、確かにファイルが作られています。つづけてAthena側でもCREATE TABLEを実行し、作成されたParquetファイルを認識させます。

CREATE EXTERNAL TABLE  p_dau (
  dtDontQuery string,
  player_id string,
  pv int
)
PARTITIONED BY (dt date)
STORED AS PARQUET
LOCATION 's3://--bucket--/athena/parquet/dau/';
MSCK REPAIR TABLE p_dau

Parquet形式の場合、単純なカウントは、ファイルスキャンの必要がなくなるようです。

SELECT COUNT(*) FROM p_locale;

(Run time: 2.95 seconds, Data scanned: 0KB)

SELECT dt, COUNT(*) FROM p_dau
WHERE
  dt>=DATE('2016-12-01')
GROUP BY dt
ORDER BY dt

(Run time: 0.99 seconds, Data scanned: 0KB)

ただし試した範囲では、スキャン量が増えてしまうこともありました。実行時間もものによっては改善しましたが、大きな変化が見られないケースが多いようです。以下、実行結果の比較をまとめておきます。

形式 csv.gz実行時間 csv.gzスキャン量 Parquet実行時間 Parquetスキャン量
localeのCOUNT 6.87s 1.45GB 2.95s 0KB
国別DAU 18.45s 45.54MB 9.52s 300.03MB
3日後継続率 3.62s 3.65MB 2.69s 12.33MB
3日連続ログイン(修正前) 1m28s 24.86MB 1m32s 86.49MB
3日連続ログイン(修正後) 4.34s 6.97MB 3.28s 24.39MB

まとめ

Athenaを使用し、データ分析基盤で日常的に使用するような検索を試してみました。

以下は、触ってみた上での個人的な感想です。

  • リリース直後ということもあり、サービスとしての完成度は、まだこれからの印象。ドキュメントなどはまだ不足しているように感じられた。
  • 今後はAPIからの操作や、データインポート方法のバリエーションが増えることを期待したい。
  • 一方、S3にテキストファイルを置くだけで使用できる気軽さや、値段の安さは魅力的。
  • 性能も、定型レポートやアドホックな調査用途を想定すれば、十分実用的なもの。

KG SDKについて

KG SDKでは、データ分析基盤システムによるKPIレポートへの対応も行なっています。KG SDKの概要についてはこちら を、お問い合わせにつきましてはこちら をご覧下さい。

↑このページのトップヘ