GENIEEエンジニアブログ

株式会社ジーニーのエンジニアがアドテクノロジー・マーケティングテクノロジーなどについて情報発信するブログです。

広告配信サーバーのパフォーマンス問題を解決した話 (_raw_spin_lock_irqに関して)

はじめに

こんにちは、ジーニーR&D本部アド・プラットフォーム開発部の宮下です。

今日は弊社の開発業務で普段どんなことをしているのかな、

という業務の一旦を弊社プロダクトのGenieeDSPを例にとってご紹介したいと思います。

現象

GenieeDSPは、弊社のDSP広告配信システムでSSPから来るリクエストのうち、おおむね月間800億リクエストを処理して、広告を返しています。

ある日のこと、監視システムから、配信サーバーの一台で負荷が高まっていることを示すアラートが上がってきました。

f:id:tech-geniee:20170718175612p:plain

ジーニーでは各サーバーの稼働状況をモニタリングするものとして、Grafanaというツールを使用しています。

この時の状況でいうと、

  • CPU使用率は30%程度
  • load averageはほぼ限界まで高まっている(全てのCPUが処理待ち)という症状でした。

現象はリクエストの多い時間帯で起きており、症状の起きたサーバーではリクエストの処理能力が著しく落ち、ろくにリクエストを処理できない状態でした。

何かの処理がボトルネックになっていて配信サーバーのパフォーマンスが悪化しているものと思われました。

困った時はプロファイラ

ここで原因を特定すべく、Linuxのパフォーマンス解析ツールであるperfを使用しました。

perfは、関数別にCPU使用率を計測・表示してくれる便利なツールです。

ジーニーでは速度上のボトルネックになりそうな箇所を特定するためによく利用しているツールになります。(詳細はこちら

早速実施してみたところ、下記のような配信サーバーの関数の各使用率が表示されました。

$sudo perf top

7.55%  adserver                        [.] backend::doPreAdSelect(geniee::lot&, std::vector<geniee::banner, std::allocator<geniee::banner> >&)
5.09%  [kernel]                    [k] _raw_spin_lock_irq
4.06%  adserver                        [.] geniee::util::FastIDSet::includes(int) const
3.29%  adserver                        [.] targeting::Targeting::doIncludeTargeting(std::vector<unsigned long, std::allocator<unsigned long> > const&, std::vector<
2.87%  adserver                        [.] geniee::memory::zone(std::string const&)
2.40%  adserver                        [.] std::string::find(char const*, unsigned long, unsigned long) const

上記から見ると、案件のターゲティング処理をしている関数が重たそうなことがわかります。

しかしそれは今までも上位に来ていたので特に問題ではなさそうでした。

ここで一点、見慣れない関数が上位に表示されていました。

5.09%  [kernel]                    [k] _raw_spin_lock_irq

また、他の現象が発言していない同スペックのサーバーを調査した結果、 _raw_spin_lock_irqCPU使用率の上位に現れることはないということがわかりました。

どうやらこの関数が怪しそうです。

この関数がおそらく原因であろうという仮説のもと、より詳細に調査をすることになりました。

_raw_spin_lock_irq:ですが、これは[kernel]とperfで出力されていることからも分かるとおり、osのスケジューラ内で使用されている関数で、ある処理が、あるCPUのコアを使用している時に、他の処理からの割り込みを受けないように、変数をロックするためのものになります。

ここで、perfの機能である、その関数がどこから呼び出されてるか(callgraph)の出力をしてみます。

$perf record  -a  -- sleep 30
$perf report --stdio
               
                       |
                       --- _raw_spin_lock_irq
                          |          
                          |--49.40%--
 void std::vector<geniee::banner, std::allocator<geniee::banner> >::_M_emplace_back_aux<geniee::banner>(geniee::banner&&)
                          |          |          
                          |          |--1.55%-- 0x7fd18d0a1000
                          |          |          0x0

結果、ここでわかったのは、std::vectorに要素を追加するときに呼ばれている、ということでした。

何か自分たちで作り込んだ関数が変な処理をしていてそこが特定できればなおせるのではないか?と考えていましたが、std::vectorへの要素追加は基本的な操作なので、改善の余地はあまりないように思えました。

そこで、一旦状況を整理してみました。

  • 配信サーバーはマルチスレッドかつイベントドリブンで動いている。
  • 全スレッドはカーネルにより任意のCPUを割り当てられる。
  • あるスレッドがvectorへの要素追加をしようとすると_raw_spin_lock_irqが呼ばれ、そのCPUを使おうとする別のスレッドが処理待ちになる?のではないかと思われました。

すると、

vectorへの要素追加時に他の処理がそのCPUをそもそも使わないようにすれば良い、ように思えます。

これでいくと、各スレッドが特定のCPUのみを使用するようにすると解決しそうです。

そこでtasksetコマンドを利用して、各スレッドに一つのCPUを割り当てることにしました。

tasksetコマンドは、コマンドやプログラムを特定のコアで実行することを可能にするコマンドです。(詳細はこちら

$taskset -c -p CPUコア番号 プロセスID

というように、配信サーバーのスレッドのプロセスIDを、順々に各スレッドに対してCPUコアを割り当てていきます。

後日この現象が再発した際にサーバーにログインし、上記コマンドを実施したところ、load averageが下がり、_raw_spin_lock_irq関数がhtopの上位に来ることはほぼなくなり、なんとか事象を解決することができました。

また、この方法だとサーバーが再起動するたびに全スレッドに対してtasksetを実施しないといけないことになりそうだったので、後日配信サーバーのプログラムに1スレッドが1CPUコアを使用するように修正を行いました。

終わりに

いかがでしたでしょうか。

ジーニーでは日々増加していく膨大なリクエスト数に対して、いかにしてその処理をさばいていくのか、という課題に日夜向き合っております。

大規模なトラフィックに向き合う中で通常では遭遇しないような問題・課題もありますが、そんな時に知恵を絞って解決した時の喜びや達成感があります。

当記事でその一端をご紹介させていただきましたが、そのような課題解決に興味を持たれたり、面白そうだな、と感じていただけましたら幸いです。

ありがとうございました。

MAJINのフロントエンドを支える技術

はじめに

どうも、R&D本部のマーケティングオートメーション開発部所属の張です。

業務では弊社のマーケティングオートメーションプラットフォーム「MAJIN」のフロントエンド開発・保守、基盤の改善、DevOps推進などなどに携わっています。最近仕事で主に使う言語はGoです。

初回なので、今回はMAJINのフロントエンドで使う技術を簡単に紹介いたします。

MAJINのフロントエンド

MAJINは2016年7月にリリースしたばかりのマーケティングオートメーションプラットフォームで、社内では比較的若いプロジェクトです。そのため、MAJINでは他のプロジェクトよりトレンディな技術を多く採用する傾向があります。

ローンチの時にはフロントエンドとバックエンドを分けて別々で開発する体制をとりましたが、最近はチーム内の交流が増え、フルスタックエンジニアを目指すチームメンバーも急増しました。

今回は「フロントエンド」をテーマとしていますが、JavascriptやHTMLを使ったWebサイトの設計・実装といった「狭義のフロントエンド」ではなく、DBのデータを取得や更新する為のサーバ側の設計・実装(API等)も含めた「広義のフロントエンド」と定義させていただきます。

技術沿革

  • ローンチ時のフロントエンド:PHP 7 + Symfony 3.x + ES6 + Vue.js 1.x
  • 最近のフロントエンド: Python 3.5 + Flask + ES6 + Vue.js 2.x

PHP+Symfonyを選んだ&やめた理由

PHP+Symfonyを選んだ一番最初の理由は、社内で既に使われていて短期間にエンジニアを確保しやすいところでした。しかし、MAJINの開発加速に伴い、PHP+Symfonyの限界を徐々に感じました。

  • MAJINではRDBMSMySQLの他に、DynamoDBやAerospikeなどのNoSQL系のデータベースも使用しています。これらDBに対し、Symfonyのジェネレータ(Ruby on Rails の scaffolding に相当)やORMapperなどの機能のメリットをほとんど生かせませんでした。例えば最初にDoctrine Annotation ReaderでDynamoDB用のORMapperを作りましたが、クエリの性能とデータ一貫性のコントロールを考慮して結局AWS SDKをそのまま使うことにしました。
  • MAJINのフロントエンドの他のマイクロサービスのAPIとのやりとりが想定よりも多くなりました。これらのAPIで処理する機能にさらにSymfonyのようなフレームワークを挟むとオーバーヘッドが増えます。デバッグとテストもやりにくくなります。
  • MAJINの仕様上、コンテンツやシナリオなどのビジネス要件にはリッチな画面が要求されています。フロントエンドのSPA化(Single-Page Application)によってSymfonyの存在感がさらに薄れました。

Python 3.5+Flaskを選んだ理由

  • 一部のバックエンドとAPIPython 3.5で書かれているのでコードを共有ができ、PHP車輪の再発明をせずに工数を節約できます。
  • Flaskは手軽で軽量でウェブアプリケーションフレームワークとして使いやすいです。ライブラリも豊富で特に不自由さを感じません。
  • Flaskは自由度が高いので、必要に応じて機能の拡張も簡単にできます。MAJINにも開発効率向上のためにFlaskでいくつかのプラグインを開発しました。

ES6+Vue.jsを選んだ理由

f:id:tech-geniee:20170718181028j:plain

モダンなSPAの開発にはMVVM(Model–view–viewmodel)フレームワークが不可欠です。数多く存在するMVVMフレームワークの中でも、下記の理由でVue.jsを選びました。

  • 使い方が簡単、勉強コストが低い。
  • Monolithicなフレームワークではなく、初めから少しづつ適用していける(Progressive framework)。
  • 理論上と実際の性能がよかった。

Vue.jsの実装はごく簡単で、下記のようにModel,View,ViewModelをそれぞれ定義するだけで、入力値が即Modelに保存され、Model内の値もすぐViewに反映されます。一見簡単そうな仕組みですが、データフローを正確に定義することによって大規模な開発に莫大な威力を発揮できます。

See the Pen Example of two-way binding by Cheung Chifung (@cheungchifung) on CodePen.

<div id="main_view">
  <label for="name">Enter name:</label>
  <input type="text" v-model="name" id="name" name="name" />
  <p>Welcome, {{ name }}!</p>
</div>

世の中のMVVMフレームワークのReactiveの実装はいくつもあります。参考

  • Pub-Sub(backbone.js): pub, subの方式でデータを更新する。
  • Dirty Checking(augular.js): click等の特定のイベントが発火したらデータの更新検知を行う。
  • Virtual DOM(React): Virtual DOMを用いて、Modelの変化を高速にViewに反映する。
  • Hook(Vue.js): ECMAScript5のObject.defineProperty()を利用して値の変化を検知し、変化があったらSubscriberにメッセージを送りコールバックを呼ぶ。(2.0からVue.jsもVirtual DOMを導入した)

Vue.jsがObject.definePropertyを使うことで、this.name = "MAJIN"のように書くだけで、Observer(Vue.jsで変化を監視するコンポネート)が自動的に変化を検出してテンプレートに反映され、開発はかなり楽になります。その代わり、IE8以下などのECMAScript5未対応のブラウザは対応できません。 (MAJINのサポートブラウザは全てES5対応なので問題ありませんでした) Vue.jsのReactiveの仕組みを詳しく説明したいところですが、今回は割愛します。

また、Vue.jsの公式の比較記事によると、Vue.jsはReactに劣らないパフォーマンスを提供できます。MAJINではVue.js 1.0と2.0を使っていますが、性能面の問題は特に感じませんでした。(と言っても使い方次第でパフォーマンスが落ちることはもちろんあります。例えば検知不要な値を大量にObserverに突っ込んでブラウザが固まることもあります)

当初Vue.jsを選んだ時、公式のデモで使われていたES6をそのまま導入しましたが、ES6には型チェックがないので心細く、TypeScriptに乗り換えようと試みたこともあります。しかし、当時はTypeScriptとVue.jsの相性が悪く感じ、挫折してしまいました。 最近Vue.jsとTypeScriptを取り巻く環境も大きく変わりましたので、そろそろ再チャレンジしようと思います。

Vue.jsでハマったところ

Vue 1.x から 2.x

MAJINリリース時に2.xはまだstableになっていないため、古いコードは全部1.xを使いました。

2.xではいろんな特性が追加されましたが、特にアップグレードしなければならない理由が無く、そのまま放置しようと思いました。しかしVue.js 2.0リリース後半年も経たず、多くのプラグインが2.0にしか対応しない状態になってしまって、止むを得ずにVue.js 2.0に移行しました。

2.0へ移行するには、書き直しが必要な箇所が多く思ったより大変でした。MAJINは複数のエンドポイントでVue.jsを使っているので、vue1.xと2.xのページのwebpackファイルを分けて少しずつ移行することにしました。Vue.js 1.xに書いたUIコンポーネントもVue.js 2.xでそのまま動かないので、一部のUIコンポーネントも2.0版で書き直しました。

移行は辛かったですが、1.xと比べて使えるライブラリが増え、Virtual DOMなどの2.xのメリットを手に入れたので、移行する価値はあると思います。

エコシステムが未熟

Vue.js 1.0を導入する時にすでにVuex(ReactのReduxのVue.js版)やVue-routerがすでに出ましたが、多くのVue.jsのライブラリはまだ使いづらいものでした。最近はある程度改善されたようですが、いざという時にはやはり自力でカスタマイズするしかありません。

解決案は主に二つ:

  1. jQueryなどのライブラリのVue.js版を作る。
  2. 自分でVue.jsのプラグインを作る。

1についてはVue.jsの公式サイトにデモがあります。多くのUIコンポーネントはこちらの方法で対応しました。Vue.jsには様々なUIのライブラリが存在していますが、MAJINに合うものはなかなか見つからず、従来のUIライブラリをこの方法で流用し、Vue.jsに対応しました。

2は難しそうに見えますが、実は簡単でした。Vue.jsのプラグインを作ることで、既存のシステムから移行する時にそのギャップを埋められる重要な手法です。ここでは一番簡単なMAJINの「i18nプラグイン」を使って説明いたします(説明の便宜上、言語を日本語に固定します)。

plugins/i18n.js

import campaign from 'i18n/campaign.ja.yml'
import _ from 'lodash'

const i18nMaster = {
    campaign,
}

let i18nCache = {}  // In-memory Cache

export const translate = (expression) => {
    if (i18nCache[expression] !== undefined) {
        return i18nCache[expression]
    }

    const paths = expression.split('.')
    let f = (_paths, master) => {
        try {
            let p = _paths.shift()
            let v = master[p]
            return typeof master === 'string' ? master : f(_paths, v)
        } catch (e) {
            if (__DEV__) console.warn(`[i18n] cannot find expression: ${expression}`)
            return null
        }
    }
    let v = f(paths, i18nMaster)
    if (v !== null) {
        i18nCache[expression] = v
    }
    return v
}

function I18n (Vue) {
    // Step 1: プラグインがインストール済みかのチェック
    if (I18n.installed) return
    I18n.installed = true

    // Step 2: Mixinでコードを入れる
    Vue.mixin({
        beforeCreate() {
            Object.defineProperty(this, "translate", {
                value: translate,
            })

            Vue.filter('translate', translate)
        },
    })
}

export default I18n

このプラグインの目的は、Symfony形式のyamli18nの定義ファイルをそのままVue.jsで使い回すことです。 冒頭のi18n/campaigin.js.ymlは今回使うi18nファイルの名前であり、その中の i18nwebpack.config.jsで設定したパスです。

webpack.config.js(抜粋)

resolve: {
  "alias": {
    "i18n": __dirname + "/path/to/translations",
  }
}

npm install yaml-loaderしてwebpack.config.jsに下記のコードを追加すると、i18nの定義ファイルはJavaScript Objectとして読み込まれます。

module: {
  loaders: [
    ...
    {
      test: /\.(yaml|yml)$/,
      loader: "json!yaml"
    },
    ...
  ],
}

そしてエンドポイントファイルに

Vue.use(I18n)

を追加すれば、ViewModelで使えます。

var myApp = new Vue({
  el: '#ma-app',
  data(): {
    return {
      message: this.translate('campaign.path.to.translate')
    }
  },
})

またはViewでfilterとして使えます。

<span>{{ 'campaign.path.to.translate' | translate }}</span>

MAJINでは、i18nのほか認証やValidatorなどのプラグインも自作しました。これらのプラグインを利用しフロントエンドのシステム移行を加速できました。

ビルド時間が長い

Vue.jsで書かれたコードの増加に伴い、ビルド時間が急増しました。また、Webpackのビルド時のCPUとメモリの消費率もかなり高まり、ひどい時はAWS上のJenkinsがOutOfMemoryで死ぬこともありました。

この問題の解決案は二つあります。

  1. webpack.optimize.CommonsChunkPluginを利用して重複ビルドを省く。(マルチエンドポイントのMAJINでは大きな効果がありました)
  2. HappyPack(https://github.com/amireh/happypack)でビルド処理を非同期化する。

この二つの手段の組み合わせで、本来10分以上かかったビルドが、今は2分弱で完了します。

終わりに

MAJINのフロントエンドで使った技術を選んだ理由とそれぞれの問題点を簡単に紹介しました。より細かい話は、これからのブログで随時掲載いたします。 どうぞご期待ください。

MySQLを捨てClickHouseでレポートAPIのレスポンスを1000倍高速化した話

はじめに

みなさん始めまして、R&D本部 基盤技術開発部の石田祥英です。
北大の情報科学研究科を卒業前に飛び出しジーニーに半年早く17卒入社し、現在は主にFlink, Kafka, Play Framework(scala)や、バッチ用途でgolang,pythonなどを使いプロダクト横断のシステムの開発・検証をやっています。

TL;DR

  • ClickHouseで分析系クエリが1000倍の速度に爆速化
  • DBの容量もMySQLと比べ1/13に軽量化
  • ClickHouse導入はMySQLには無い制限もあるけど、なんとかなる
  • あんまり日本での導入事例が見当たら無いけど、クエリの高速化・DBの軽量化したい人はぜひ使うといいことがあるかもしれません

 

社内勉強会用の資料

 

ClickHouse導入の背景

一般的に広告プラットフォームでは「どの広告を、どの広告枠(サイト)に、いつ、どのように(RTB or Adnetwork)配信したのか」という様な情報をDBに格納し、広告主向けの管理画面がDBからレポートする内容を取得し、代理店や広告主の運用者に配信結果をレポート表示しています。

通常、「〜ごとのレポートを見たい」となった場合、それに相当するカラムをキーとして保存しなくてはいけません。

しかし、実際には「1日ではなく1時間ごとにレポートを見たい」、「OS、地域、デバイスごとなどのImpression(広告表示)や広告のClickを集計したい」となると、キーがどんどん増え、それに伴ってDBの行数が指数関数的に増えていってしまいます。

1日に15TBほどの広告配信のログが出るジーニーでは、実際にキーの数を4個から10個に増やそうとした時に、もうMySQLではインデックスを正しく張って頑張っても、運用者が我慢できる時間内にクエリが返ってこなくなってしまう、という問題にぶち当たっていました。

また事業成長の要因も考慮し、トラフィックが10倍に増えても低レイテンシでレポートを返すDBが求められており、何か手はないのかと言うことで色々な方法を模索していました。

DBに必要な要件

  • 広告の運用者の方々がレポート画面をみて運用のPDCAを回せるように、とにかく低レイテンシであること
  • 集計プログラムにバグが見つかった時に再集計してDBの内容を正せること
  • ログ転送遅延時やハードウェア故障時などへの耐障害性があること

検証した他サービス・手法

MySQLに並列でクエリを投げる

レポートAPIscalaのPlayで書かれていることもあり、akkaを使い並列でクエリを投げる案があったのですが、正しくソートされたレポートを並列化されたクエリでうまくかける方法が見つからず実現が難しいと判断しました。

BigQuery

Googleの巨大なリソースを使い、SaaSであるので開発・メンテが不要なことやSQLライクなインターフェースと柔軟なUDFが魅力で検討対象に。

実データでの検証は行なっていませんが、Google社の営業担当にテーブルの行数とそれに対するクエリのベンチマークの結果を教えてもらい、それをクエリのパフォーマンスとして考えることができました。

BigQueryはクエリごとの課金とデータ量による課金があり、クエリ課金部分は月額3万ドル(2016/12時点)で叩き放題のプランがりましたが、データ量が膨大なことにより予算をオーバーしたことと、そもそもAPI向けの秒単位の低レイテンシ用途に最適ではないと判断し諦めることになりました。

InfoBright

MySQL上にビルドされたカラムナDB。列志向による軽量化と高速化が期待できる上、MySQLからの移行もスムーズに行えそうなことが魅力に映り実データでの検証に至りました。

しかしユニークキーがはれないことや、INSERT 〜 ON DUPLICATE KEY UPDATEができない、頻繁なアップデートに不向きで、特別な運用をしないといけないことに加え、手元のデータではClickHouseよりもクエリ性能が出なかったこともあり今回は使用しないことになりました。

ClickHouse

公式HPにMySQL・VerticaやInfoBrightなどのカラムナDB・Hiveとのベンチマークを乗せており、ほとんどの場合で、高速なことが魅力でした。特にレポートで頻出するSum関数やGroup By句でのパフォーマンスが高く、実データでの検証に至りました。特にフリーかつオープンソースであること、何よりドキュメントがしっかりしていたことも大きな魅力でした。

https://clickhouse.yandex/benchmark.html 

ClickHouseの性質・性能

スキャンする性能がものすごく高い

手元の検証用データではスキャン性能 3億行/s 9GB/sほど出る。(後述のSumming Merge Treeエンジン使用時)

圧縮率が高くストレージの節約になる

カラムナDBなので圧縮率が高い。
検証用データ使用時にはMySQLに比べてストレージ使用量が1/13になりました。

ただし後述のMergeテーブルを使い、1つのテーブルを450テーブルに分散した状態で測定したので、1テーブルで行った場合にはさらに圧縮されるポテンシャルがあると考えられます。

多種多様なテーブルエンジンがある

ClickHouseには20種類のテーブルエンジンがあり、CREATE TABLE時に1つorいくつか組み合わせて選んでテーブルを作ります。

This is the most advanced table engine in ClickHouse. Don’t confuse it with the Merge engine.

とドキュメントにあるように、Merge TreeエンジンがClickHouseに置ける看板エンジンであり、広告主向けレポートで使用したテーブルもMerge Treeエンジンの亜種であるSumming Merge Treeエンジンを使用しています。

広告主向けレポートで使用している2つのテーブルエンジンを紹介しようと思います。

Summing MergeTreeエンジン

Summing Merge Treeエンジンは足し上げが起こるレポート等の用途に向いているエンジンです。キーになるカラムとメジャーとなるカラムを指定しておき、同じキーのレコードをinsertするとメジャーは足し上げられて一行として扱われるようになるという性質を持ちます。ImpressionやClick数、広告主にかかるコストなど、足し上げが発生するレポートにおいては非常に便利なテーブルエンジンで広告向けレポートにはピッタリでした。ClickHouseの行のUPDATEができないという問題も、足し上げるだけのためのUPDATEであればこのエンジンを使うだけで問題なくなります。

Mergeエンジン

Mergeエンジンは複数のテーブルを1つのテーブルとして扱うエンジンです。

テーブル作成時にテーブルの正規表現を指定しておくと、それにマッチした複数のテーブルを1つのテーブルとして扱い、Mergeエンジンにクエリを投げれば配下のDBにクエリを投げた結果を返してくれるという性質を持つため、巨大なテーブルを分割して保存しておくことが可能になります。

 

UPDATE・DELETEがない

ClickHouseでは行に対するUPDATEとDELETEができません

これがClickHouseの性能に飛びつくも導入時に困るであろう大きな問題で、今回広告主向けレポートではどう乗り越えたかを紹介したいと思います。 

 

ClickHouseの運用

困ったこと

SQLはほぼMySQLと同じであり、ClickHouse移行で最大にして唯一困ることは

「UPDATE・DELETEが無い問題」

でした。

具体的には

  1. UPDATEができないので、同じ行への更新ができない。
  2. DELETEができないので、集計プログラムのバグ発覚や、集計遅延時等にDB内のデータ修正ができない。行う場合は、DROP TABLEしてまた正しいデータを入れ直さないといけないため時間がかかる。

といった問題にぶつかりました。

今回の広告主向けレポートの要件として

  1. 1時間ごとの集計
  2. 再集計が短時間で可能

というものがあり、要件を満たしながら制限を回避する方法にたどり着きました。

 乗り越え方

簡単に箇条書きにすると

  • Mergeエンジンを使い1日ごとにテーブルを作成することで、データが壊れた時のデータの入れ直しの時間を減らす。
  • Summing Merge Treeエンジンを使い、1時間ごとに最新データを入れていく。同じキーごとにメジャーが足し上げられるので、同じ行のimpressionやclick数などのメジャーの集計が遅れても正しく足し上げられる。
  • RENAME句のみがトランザクションを使える(複数テーブルの一括RENAME時にグローバルロックが走る)ので、それを使って壊れたデータと正しいデータに入れ替えを行う。

ここら辺は文字で書くとややこしいので冒頭のスライドにデータの入れ直しのテクニックを図解しているので参照してみてください。

 

ClickHouseの苦手だと感じたこと

非常に派手なベンチマークのClickHouseも以下の用途には向かない(そもそもそれ用に設計されてない)です。

OLTP処理

ClickHouseはそもそもがOLAP処理に作られたDBであり、トランザクション処理も実装していません。唯一RENAME句内での複数RENAMEにグローバルロックがかかるぐらいです。ECなどのトランザクション取引が発生するサービスのDBとしては向いていません。

分散ストリーミング環境からののインサート

ドキュメントに

 We recommend inserting data in packets of at least 1000 rows, or no more than a single request per second

とあるよう1回のINSERTには1秒あけることと少なくとも1000行のデータをINSERTすることが推奨されており、ストリーミング処理したデータをポイポイ入れていくのではなく、ストリーミングの場合はどこかに一度貯めてからバッチ的にINSERTする必要があるようです。

分散処理基盤からのインサート

また広告主向けレポートでも現在Apache KafkaとApache Flinkなどを用いて分散ストリーミング処理を今後行っていくつもりですが、Apache kafkaやApache Flinkで分散したデータを最終的には1つのtsvファイルにまとめる必要があり、分散したままのINSERTには向いていないようです

https://clickhouse.yandex/docs/en/single/#performance-on-data-insertion

 

まとめ

社内の広告運用者からは「ロードに3分かかっていたレポート画面が0.5秒で返ってくるようになりました!」と感謝され、DBAからは「DBを圧迫していたレポートテーブルが小さくなった」と喜びの声をいただきました。

現状の使い方ではレプリケーションしかしていませんが、今後格納行数がさらに大きくなった時にもShardingしながら分散するDistributeテーブルエンジンもあるので、まだクエリが遅くなることがすぐに起こる予定はないですが、そちらを使って対処していこうかと思います。 ClickHouseの開発元であるYandex社では374台のサーバーに20兆行を超えるデータが入っているようです。

最後に、大規模なデータを持っていて超早い分析系のクエリを求めるプロダクト(アドテク、EC、ゲーム・メディアのユーザ分析等)なら本当に検証の価値ありだと思います。クエリが相当早くなりますし、データも軽量になります。

また、本番投入できるかの見極めについては、 「データ入れ直しが許容時間内に終わる見通し」が立つのであるならば本番投入できる可能性が高いと言えるのではないかと考えています。

https://clickhouse.yandex/docs/en/single/index.html#distributed

 

以上。

 

 

【CTO×編集長 対談】ジーニー、ついにエンジニアブログを始めます!

はじめまして。このブログの編集長を務めることになりました、R&D本部マーケティングオートメーション開発部の張です。ジーニーへは、2016年4月に入社して、主にフロントエンドを担当しています。


初回ということで、CTOの篠塚とブログを始めるきっかけや、ジーニーの開発部門の紹介をさせていただきます。

 

f:id:tech-geniee:20170719155615j:plain

 (写真左:CTO・篠塚 英伸、右:エンジニアブログ編集長・張 志鋒)

ブログを始めるワケ


張:

ジーニーは、結構、面白い技術を使っていて、色々発信したいと思っていたんです。でも、ブログもなく、あまり社外に伝える機会がないな、と思っていました。


篠塚(CTO):

そうですね。R&D本部全体では、「誇れるものをつくろう」といつも言っているので、「誇れるものをつくったら、自慢しなきゃ!」と。それを発信していきたいというのが、このブログを始めた1つのきっかけですね。
あと、自分で勉強して、プロダクト開発に反映させて、「よかったね」と。それはそれで良いんですけど、エンジニアのキャリアやスキルアップを考えると、社内外の場でお互いに共有したり、発信したりしていくのも大事。そこでいろんな人からフィードバックを得て、ブラッシュアップしていく方が成長できると思うんですよ。


張:
このブログを通して、優秀なエンジニアの方たちにジーニーの技術を知ってもらいたいし、それによって、友を呼びたいですね。


篠塚:
ジーニーは、あまり中のことを語らない人たちが多いけれど、アドテク自体がマニアックなので、おもしろい内容が書けるんじゃないかと思うんです。アドテクノロジーやマーケティングテクノロジーについて、興味を持ってくれるエンジニアが増えたらいいなと。そして、仲間が増えたら嬉しいですね。

 

ジーニーのエンジニアリング部門

篠塚:
R&D本部には、「アド・プラットフォーム開発部」「マーケティングオートメーション開発部」「基盤技術開発部」「経営情報システム開発部」の4つの部があり、それぞれ25人・15人・10人・10人で合計60人ぐらいいます。


張:
メンバーのバックグラウンドは、コンピュータサイエンス系が多いですが、文系の人もいれば、美大(アート)出身者もいて、面白い人が集まっています。ちなみに僕は経済学部でした。


篠塚:
張さん、文系だったんだ!びっくり。
新卒も8割くらいはコンピュータサイエンス出身ですね。理工系も少しいます。


張:
中途では、1/3ぐらいがアドテク出身で、SI出身も多いですね。あとは、ゲーム系とか、Webサービス系とか、いろんなバックグラウンドの人がいると思います。


篠塚:
総じて、尖っている人が多い気がしますね。みんなそれぞれ特長的。
あと、女性は現状2名しかいません!


張:
特にアドテク業界は、女性が少ないんですかね?!


篠塚:
アドテク女子って、本当に少ない(笑)


張:
ジーニーは、女性も働きやすい環境ではあると思うんですけど。


篠塚:
男性も育休取っていますし。エンジニアではないですが、女性社員で産休育休をとって復帰している前例もあります。女性の感性がもっとプロダクトに入ると、いいと思うんですよね。

f:id:tech-geniee:20170719155811j:plain

仕事の仕方や雰囲気

張:
僕は、何でも屋ですね。以前は、フロントエンドとバックエンドが分かれていましたが、最近は、一緒に仕事をするようになってきています。タスクに対して、みんなで力を合わせてやるフルスタックというイメージです。


篠塚:
そうですね、インフラは分かれていますが、フルスタックを目指しています。
もちろん、人によって得意領域はあるけれど、他の人の業務を知らないと、最適な設計ができないと思います。
これまで規模が小さい頃は、とにかくスピード重視で役割を分けてやってきました。しかし、規模も大きくなり、今後、ジーニープラットフォームとして連携していくことを考えると、技術的な切り分けではなく、タスクの解決方法に合わせて分担していく方がいいと考えています。そのためには、エンジニア1人ひとりが広範囲のことを知らなければいけないですね。


張:
もちろん初めから全部できる、というのは難しいですが、例えば、データベースは何を使っているかとか、どういう構成で何を処理しているかなど、プロダクトを知らないと、フロントでもアプリケーションでもモノづくりはできません。お互いを知らないと、チームとして動けないんですよね。


篠塚:
この体制は、特長的かもしれませんね。


張:
最近は、チームを越えてお互いを知るようになってきたと思います。もっともっと、壁をなくしていきたいですね。そのための1つの方法として、週1回社内勉強会をやっています。こちらのチームでは新技術を使って開発しているのに、隣のチームはよくわからない、というのではもったいないですから。プログラミング業界でよく言われるような“車輪の再発明”とかしたくないからね。知識が共有できれば、ジーニーの開発パワーはもっと上がってくると思うんです。


篠塚:
勉強会のほかには、月に一度、R&Dの全体会があります。メンバー持ち回りでLTをやっているんですけど、わりと面白いですよ。飲み会とかより、ずっと盛り上がるんです。この前のLTでは、あるメンバーが「モテたいから髪色を変えたい」ということで、似合う色を判定するためだけの仕組みをわざわざAI使って作ってみましたって発表がありました。みんな大爆笑でしたよ。「変えればいいじゃん!」って。そんな雰囲気の部門です(笑)

f:id:tech-geniee:20170719160107j:plain

ジーニーならではのこと

篠塚:
普段よく目に触れるWebサービスは、UIを提供するのが一般的ですが、アドテクやマーケティングオートメーションでは、見える部分がほとんどありません。表には出ませんが、例えば、潜在顧客に1通メールを出したり、1つバナー広告が表示されたりするまでには、いろいろなドラマがあるんですよ!データを集めて、次も出すべきかどうかというスコアリングや分析をして、良かったのか悪かったのか、その結果をフィードバックする仕組みは、顧客のマーケティング施策全体や業績に大きく関わってきます。目に見える部分はほんのわずかですが、裏側ではけっこう壮大なことをやっているんですよ。


張:
MAJIN(マーケティングオートメーションツール)も、目に見えないところで結構頑張っているんですよ。MAJINは、フロントもバックもなかなか複雑で、データベースの開発とかにも手を出そうとしています。最先端の論文で研究して技術を実装してみたり、普通ならあまり使わない言語を使ってみたり、いろいろ試して、改善して、結果どんどん良くなっています。ジーニーの場合、実験的な試みや失敗も許容してくれる環境があるので、最先端の技術にチャレンジできるのがいいですね。


篠塚:
他社が採用していないような技術も、積極的に研究したり、実験的に採用したり、産学連携したり。いろいろやっていることを活かして、オープンソースへの貢献もしていきたいですね。


張:
新しい技術の開発は、他社だとあまり裁量権がもらえないことが多いと思うんです。僕の場合、どんどん勉強して、実際に試せるというのは、ジーニーに入って良かったことの1つだと思います。


篠塚:
営業のみんなが頑張ってくれているから、エンジニアにチャレンジできる余裕が生まれるので、ありがたいですね。いい循環が生まれていると思います。
あと、ジーニーの特徴として、データ量(トラフィック)が多いことがあります。1日のデータ処理量は、だいたい15テラくらいあるんですが、それを効率よく24時間365日動かし続けなければいけない環境って、わりと珍しいと思うんですよ。


張:
MAJINの方では、機械学習も強化していますし、AI活用も進んでくるので、そういうチャレンジをしたい方にもいい環境だと思います。

f:id:tech-geniee:20170719160145j:plain

アドテクノロジーで世界を変える(=ジーニーのミッション)

篠塚:
世の中にまだ解決できてない課題があって、それを解決すると、少し世界がよくなります。なぜ、いろいろな課題が解決できないかというと、アイデアがないわけではなくて、いろいろな困難があって越えられないんですよ。
顧客の本質的な課題にしっかり応えるために、必要な技術を見極めて、ベストなものを真面目につくる。有り体のものを使うのではなく、自分たちにしかできないものを自分たちの手でつくるんです。課題自体がどんどん複雑になっているので、必然的に最先端の技術を取り入れていかなければならないわけです。そうやって、1つずつ課題を解決していくと、世の中が良くなっていくんです。それが僕たちのミッションだし、それができると「誇れるものがつくれた」ということになります。


張:
その通りです。困難があって、他の人が解決できなかったことを、僕らがエンジニアリングで解決できたら達成感がありますよね。


篠塚:
MAだったら、どこまでオートメーションにすると、本当にマーケターが楽になるかとか、人では思いつかない(できない)成果が得られるかとか。そういった壁を一つひとつ解決していくことが大事だと思っています。


張:
こんな僕たちの学びとか気づきとか、勉強会や研究について、このブログで発信していきたいと思っています。
よろしくお願いします!