• Home
  • コーポレートブログ Geniee’s BLOG
コーポレートブログ

Geniee’s BLOG

ジーニーは最先端の広告テクノロジーで
顧客の収益を最大化します。

こんにちは、R&D本部2019年卒エンジニアの浅井です。
マーケティングテクノロジー開発部MAJINグループでエンジニアをしています。
今回は、オンプレ上にあるHadoop環境をAWSに移行した話をしようと思います。

浅井迅馬/京都大学工学部卒業後、2019年にジーニーに入社
R&D本部マーケティングテクノロジー開発部・MAJIN開発チーム所属

Contents

1 オンプレ上の旧クラスタについて
2 AWS上の新規クラスタについて
3 おわりに 改善できた点・できなかった点

1  オンプレ上の旧クラスタについて

私の開発しているMAJIN(マーケティングオートメーションツール)というプロダクトでは、2016年からオンプレ環境にて構成されたHadoopクラスタを用いて、エンドユーザー行動ログの集計を行っています。
旧クラスタの構成は以下です。

■ハードウェア

クラスタはCloudera Managerを用いて管理されており、Cloudera Managerだけが立っているサーバー(cloudera)が1台あります。
Hadoopクラスタに対する問い合わせを行えるようなGatewayとなるサーバー(client)が2台、コアノードと呼ばれるようなサーバー(core)が19台、マスターノードと呼ばれるようなサーバー(master)が2台の全24台で構成されています。

■アプリケーション

・CDM/CDH
・HBase
・HDFS/YARN
・Zookeeper
・Spark2
・Java
・Scala
・Digdag

MAJINではHDFS及びHBaseをデータレイクとして扱っており、MapReduceやSparkでそれらの集計を行っています。ジョブのスケジューラーとしてはcronとDigdagが使われています。

■アーキテクチャ

旧クラスタの概略を図示すると、以下のようになっています。

旧オンプレアーキテクチャ

td-agentからHBase/HDFSにエンドユーザーの行動ログが流れてきます。そして、clientサーバーにてDigdag/cronを契機にそれらを集計するMapReduce/Sparkが走ります。
この旧クラスタで行われる集計は約30のMAJINの機能に関わっており、約60個のジョブが動いています。

さて、旧クラスタでは以下のような問題を抱えていました。

複雑化したclientサーバー
・Hadoopクラスタへの問い合わせが行えるということで、clientサーバーは様々な用途に使われてきました。
・MapReduceやSparkジョブのサブミット
・ジョブのスケジューラーが動いている
・cron及びDigdag
・ジョブのタスクランナーが動いている
・cronで定期実行される自社製タスクランナー
・Hadoopクラスタへのアドホックな問い合わせ
・MapReduceやSparkジョブのビルド
・Hadoopと関係のない謎のデーモン
clientサーバーは数年間「便利」に使われてきており、このサーバーが停止したらどうなるのかとを想定することすら難しい複雑な環境になっていました。

自社製タスクランナー
このクラスタを立ち上げた時から、cronと自社製のタスクランナーを合わせたジョブワークフローを利用しています。このタスクランナーはPerlで書かれており、さらにPython2系に依存しています。現在MAJINチームではPerlを読み書きできる人間がおらず、またPython2系はEOLを迎えていることから、この自社製タスクランナーのメンテナンス性が問題視されていました。

無駄の多いクラスター
前述の通り、このクラスター上では HDFSやHBaseといったデータレイクからYarnで管理されたジョブまで様々なHadoop周りのコンポーネントが動いています。
すべてが1つのクラスターで動いていることから、負荷が重なるとお互いにリソースを奪い合ってしまう可能性があります。もちろんYarnが完全にHDFSやHBaseのリソースを奪うということはなく、考えられる負荷の上限に合わせたクラスタ構成になっていますが、その分無駄も多くなってしまっています。

こうした問題がある中、2021年7月にクラスタを構成している大部分のサーバーの保守が切れるという事態が発生しました。
そこで、まず移行先として既存のオンプレ継続・IDCF・GCP・AWSと4つの選択肢が挙げられました。そして、移行の要件としては、既存の機能をそのまま移せること、移行自体のコストが小さいこと、移行後の保守コストが既存よりも下がることなどが求められていました。
それゆえに、可能な限りミドルウェア等の管理が不要でかつ既存のMapReduce・Sparkの資産が活かせる環境としてGCP・AWSが残り、最終的に既存のアプリケーションがAWS上で動作していることからAWSを選定しました。またAWSでデータの集計基盤を作るとしても様々な案が考えられますが、今回は移行自体のコストを下げるということで、Athenaなどは使わずAmazon EMRクラスタへ移行する計画が練られ、実行されました。

2 AWS上の新規クラスタについて

新規アーキテクチャの概略図は以下になっています。

大きな変更点としては、1つのクラスタ上で行われていたことを分離しました。
HDFSをS3に、HBaseをEMRクラスタに、MapReduceやSparkを実行するクラスタはSpot Fleetを用いて必要なときに必要な分のEMRクラスタを起動するように変更しました。
またclientサーバーはGatewayの役割だけをもつサーバーとタスクを実行するサーバーに分離しました。それに伴い、自社製タスクランナーを廃止しDigdagに統合しました。

この移行はまずtd-agentからのログをAWSへも転送させるところから着手しました。td-agentからKinesisへはfluent-plugin-kinesisを、HBaseへは既存の自社製pluginを使用しました。
続いてcronからDigdagへの移行に移りました。これによってタスクランナーとスケジューラーが別れて管理されていた問題が解消されました。また自社製タスクランナーにはなかった並行処理の機能や管理画面など、実行速度や運用面においても改善されました。
最終的な切り替えにあたっては処理を冪等に修正し、万が一誤ったデータを書き込んだとしても差し戻せばすぐに修正できる状態にすることで、無停止での移行を実現しました。

しかし、移行もスムーズに行えたわけではありませんでした。例えばDigdagのemrオペレーターはステージングディレクトリにusリージョン以外のS3バケットを指定できないというGitHub上のIssueに気づかず、バケットをusリージョン以外に作ってしまったため作り直しを行いました。他にもDigdagのemr-fleetオペレーターが存在しますが、今回利用にあたってステップの並行実行数やAllocation Strategyの設定ができなかったことから、必要なオプションを設定できるようにしました。

3  おわりに

この移行によって様々なメリットを得ることができました。
■Digdagによる集計アーキテクチャの一本化ができました。
■タスク実行クラスタの自由なプロビジョニングができるようになりました。
■継続して管理が必要なクラスタはHBaseのみになりました。

しかし、まだまだ課題が残っています。
■Digdagのタスクを実行しているサーバーは1台しかなくSPOFになっています。
・Ansibleによる構成管理を行っているため、すぐに復旧可能な状態ではありますが、今後はDigdagで可能なHA構成にする予定です。
■インフラの構成管理ができていません。
・本番環境は手動で作ってしまいました。
・ステージング環境からCloudFormationでの管理を行っています。

移行にあたって協力してくださったAWSソリューションアーキテクトの方々に、心から感謝申し上げます。
今後オンプレHadoopクラスタをAWSなどのクラウドサービスへ移行することを考えている方々の参考になれば幸いです。

一緒に働く仲間募集中!
【ジーニーのリクルートサイトはこちら】
https://geniee.co.jp/recruit/

こんにちは、R&D本部 18卒エンジニアの鈴木です。
経営システム情報開発部でリーダーをしています。
今回はタイトルの通り、リーダー業務について紹介しようと思います。
(経営システム情報開発部 鈴木望 2018年新卒でジーニーに入社。早稲田大学理工学部出身)

Contents

1 普段の業務紹介
2 メンバーとリーダーの違いとは
3 これからリーダーになる人へ

1 普段の業務紹介

開発業務
私の所属する経営情報システム開発部では、主に社内の各プロダクトを統合する業務管理ハブの開発を行っています。それに伴い社内の工数削減につながる業務を担うことが多いです。直近の業務だと、財務会計システムのオンプレミスソフトウェアからクラウドSaaSへの移行などを行っていました。
リーダーとは言っても、開発業務自体はメンバーと特に違いはありません。組織運営などがある分、実際に手を動かす時間は減ります。

プロジェクト管理
開発業務の中には、数日で終わるものから要件定義含めて数年かかるものがあります。
開発業務の箇所で紹介した財務会計システムの移行なども全体で1年ほどかかった大きなものでした。
小さな開発でもスケジュールを決めることは必要ですが、プロジェクトが大きくなればなるほどスケジュールの管理が重要になってきます。
また、プロジェクトの中のタスクを持ちつつも、プロジェクト全体の進捗にも意識を向けてPL(※1)のフォローをする必要があります。そのためには、各メンバーのタスク内容を把握して適切にフォローを行えるようにした上で、全体的にプロジェクトを俯瞰できることも重要になります。

組織運営
組織運営と言っても色々あるとは思いますが、今回は「チーム」運営に絞って話そうと思います。その中でも効果がすぐに期待できる会議運営とそれに付随した当事者意識の促進に関して話そうと思います。

各チームで定例的に開催している会議体はいくつかあると思いますが、会議自体に対しての改善を行っていく姿勢を少なくともリーダーは持つ必要があります。つまり自チームの運営に関して当事者としての意識をより強く持つ必要があるということです。また、チームメンバーに対しても当事者としての意識をより強く持たせるように促す必要があります。

今は在宅勤務をしている方も多いと思いますが、その中で当事者としての意識の有無がより顕著に出てきていると思います。例えば、会議に参加しているが裏で別の作業をしていて発言をしない、あるいは会議には参加しているが終始受け身になっているなどです。
前者に関しての問題点は、会議の進行中に別作業をしているので、会議の進行自体に対して何も意識を向けられないこと、また自分が会議に参加している理由を考えて提案を行うところまで行動にできないことです。後者に関しての問題点は、課題感を正確に把握してフィードバックを行うことができないことです。

会議で当事者意識を持ってもらうために、私は例えば、会議の中で、発言のない人に意見を聞いてみる、あるいはタスクを持ってもらうなどして積極的に関わらざるをえない状況を作っています。また全員でフィードバックの会(※2)を設けてチームメンバーに対してお互いに改善のためのフィードバックを与えるようにしたことで、それぞれがチームメンバーをちゃんと意識して業務を行えるようになったと思います。また、それによって意見を出す敷居が下がり、積極的に話し合いが行えるようになったと感じています。

このように、現状の課題を洗い出し、適切なアクションを考えチームに落とし込むことも業務の一つになると思います。

(※1)プロジェクトリーダーのこと。今回は比較的大きなプロジェクトだったので、プロジェクトに関わる各チームで一人ずつ割り当て、担当する各チームのプロジェクト作業を管理する役割を担ってもらいました。
(※2)お互いのメンバーに関して、改善のためのフィードバックを与える会。開発に関わる業務だけでなく、会議進行やslackでのメッセージの送り方など幅広くフィードバックを与えています。

2 メンバーとリーダーの違いとは

視座と価値の出し方
一般的に言われていることだと思いますが、メンバーとリーダーの違いとして視座の違いがあるかと思います。自分の動きだけではなく、メンバー全体の動き、延いてはチームの動きも考える必要があるからです。

リーダーとして動く中で意識していることは、メンバーとマネージャーの間になぜリーダーというポジションがあるのかということです。また、マネージャーや部長と動きの方向性がずれていないかということです。

メンバー全体の動きという点で一例をあげます。
メンバー全体の開発の進捗を、音頭を取ってスケジュール管理していくというのは、一つの重要な役割になるかと思います。メンバー一人ひとりの開発タスクの確認まで細かいことはマネージャーが行う必要はなくリーダーが行い、まとめたものをマネージャーに報告すればいいからです。マネージャーの資源はより上位の組織運営などに使われるべきです。

次に、チームの動きに関して一例をあげます。
チームの動きを考えていく姿勢は確かに重要なものです。しかし、同じ方向を向いていなければリーダー以下のチームの動きとしてはうまくいっているように見えても、より広いグループや部という視点から見ると混乱を招いているだけです。

このように、リーダーとしての動き方の前提になるのは、マネージャーや部長と同じ方向性を持った上で、メンバーとマネージャーの間のブリッジになることです。そのためにもマネージャーとは密にコミュニケーションをとって業務を行っていくことが重要です。
その上で、「どのように」「どれくらいの速さで」チームを取りまとめて動いていけるか、また、「どれくらい」マネージャーや部長の手からメンバーの細かいタスクへの意識を手放せられるかが、リーダーとしての価値の出し方になると思います。

3 これからリーダーになる人へ

いかがでしたか。
これからリーダーになる方や、リーダーではあるがメンバーとの違いを意識できていない方はこの機会に一度、なぜメンバーとマネージャーの間にリーダーという役職があるのか振り返ってみてはいかがでしょうか。これからの働き方のヒントが浮かび上がってくるのではないでしょうか。

一緒に働く仲間募集中!
【ジーニーのリクルートサイトはこちら】
https://geniee.co.jp/recruit/

こんにちは、R&D本部 20卒エンジニアの高橋です。
今回はタイトルの通り、GENIEEで行われた20卒エンジニア研修について、受講者という視点から振り返ってみたいと思います。

※ GENIEEにおけるエンジニア研修は “bootcamp” と呼ばれており、実施年度と共に表現します。(例 : 2020年度のbootcamp → bootcamp2020)
また、ビジネスを含む新卒全体に対する研修もbootcampの前に別途実施されます。

CONTENTS

1 新卒エンジニア研修「bootcamp」概要
2 研修内容
3 アンケートサマリ〜受講者から見る bootcamp2020〜
・評判のよかった研修について
・フルリモート実施について よかった点、課題点
4 来年度に向けて

1 新卒エンジニア研修「bootcamp」概要

例年、bootcampは1ヶ月半程度の期間で実施されており、GENIEEで使用されている技術を中心に、業務に最低限必要となる技術について学びます。また、具体的な研修内容やスケジュールについては、状況に合わせて毎年調整されています。
bootcamp2020は、個別研修が4月中旬から5月中旬頃、チーム研修が5月下旬から6月上旬頃、というスケジュールで実施されました。
また、新型コロナウイルスの影響が考慮され、bootcamp初のフルリモートでの実施となりました。

2 研修内容

bootcamp2020で実際に行われた主な研修内容について簡単にご紹介していきたいと思います。
【Git研修】
Gitの基礎的な知識と使い方を知り、コード管理の基礎を学びます。
【UNIXコマンド基礎研修】
よく使うUNIXコマンドを知り、状況に応じた使い分け方、調べ方を学びます。
【プロダクトマネジメント研修 】
プロダクト開発の一連の考え方を知り、プロダクトマネージャーの役割について学びます。
【ネットワークと仮想技術研修】
仮想化技術の基礎知識から、代表技術の一つであるDockerの仕組みと使い方について学びます。
また、コンテナ間の接続を通して、ネットワーク構築について学びます。
【MySQL研修】
データベースの基礎からその使い方、注意点など、業務に最低限必要となるデータベースに関する知識を学びます。
また、テーブル設計やレプリケーションなど、データベース設計の基礎知識を学びます。
【CSS研修】
CSSの位置付けとその用途を知り、その調べ方と試し方を学びます。
【LEMP研修】
Webアプリケーションの基礎から構築までを学びます。
※ LEMP; Linux, Engine-X(nginx), MySQL(or MariaDB), PHP(or Python) の頭文字を取ったもの
【アルゴリズムとデータ構造研修】
基本的なアルゴリズムとデータ構造を知り、計算量の感覚を学びます。
【JavaScript研修】
JavaScriptの基礎的な知識から、Next.js, React といった、ライブラリやフレームワークについて学びます。
また、サーバサイドである Node.js や、JavaScriptの拡張言語であるTypeScriptについて学びます。
【Python研修】
Pythonの基本的な構文とライブラリについて学びます。
【デバッグ研修】
デバッグの基本的な考え方や当たりの付け方を知り、デバッグ全体の流れを学びます。
【Go研修】
Go言語の基本的な構文やテストの書き方について学びます。
また、Go言語における非同期処理やプロファイラについても学びます。
【クラウド研修】
クラウドの基礎知識や、GENIEEで活用されているサービスの基本的な使い方について学びます。
【サーバー作成研修】
サーバーの基礎知識から構築までを、実際の作成をしながら学びます。
【開発ルール研修】
GENIEEにおける開発ルールを知り、実際の業務における開発の流れを学びます。
【セキュリティ研修】
セキュリティの基礎と重要性を理解し、開発で注意すべき点について学びます。
【テスト研修】
開発におけるテストの重要性とテストの種類などを知り、目的に応じたテストの仕方を学びます。
【コードレビュー研修】
コードレビューの意義と注意点を理解し、開発におけるレビューの仕方を学びます。
【Git研修(応用編)】
Gitにおけるcommit操作やブランチモデルといった、より高度なGitについての知識を学びます。
【チーム開発研修】
実際にチームに分かれ、チーム開発の流れを開発を通して学びます。

3 受講者から見る bootcamp2020

ここからは、研修後にbootcamp2020運営の方々が取ってくださった20卒エンジニアに対するアンケートの回答を元に、受講者視点から振り返ってみたいと思います。

評判の良かった研修について
各自で3つほど良かったと思う研修を選んでもらい、その理由とともに回答を集めた結果が以下になります。

また、上位3位(3位はタイなので4つ) の講義について、いくつかの詳細な回答とともにご紹介します。
【Go研修】
「Goの使い方から、低レイヤーを扱う処理についても実装する機会があったのが良かったです」
「初めてGoを触ったのですが、Goを好きになれるような研修でした」
【JavaScript研修】
「充実したドキュメントにしたがって進めていくことで、JavaScript, TypeScript, Reactのことなどフロントに関する知識を沢山仕入れることができました」
「モダンなサーバーサイド開発を学べて嬉しかったです。RESTAPI・ページネーション・セキュリティ認証などWeb開発において必要な技術を学ぶことができました」
【ネットワークと仮想技術研修】
「今まで Docker やデータベースに触る機会があまりなかったのですが、研修以降 Docker コンテナを作って作業するのは当たり前になりました」
「ネットワーク的な知識も勉強できたのが良かったです。研修序盤にあったことでその後の研修でも環境構築に利用できました」
【サーバ作成研修】
「ソケットを通してバイト列を送受信する仕組みを作ることで、同時に個人的に学んでいたHTTPの理解が進みました」
「作業時間に余裕があったので、設計にこだわることができました。また、他人の成果物を読んで、他の人がどういうアプローチをしたのか探ることができたのも良かったです」

フルリモート実施について
フルリモートという実施形式に対しての意見についてもご紹介したいと思います。こちらについては、各自良かった点と課題点を自由回答するアンケート形式でした。

リモート研修 良かった点
まずは、良かった点についてです。良かった点については、それぞれ異なる視点の意見が多く見られました。以下に簡単にまとめたものをご紹介します。

【環境要因による効率化】
周りに人が居ないことで、集中力を保つことができたり、自分のペースで自由に進めることができた。
【通勤時間分の有効活用】
通勤時間がないことで、その時間を有効に使うことができた。
【リモート開発の練習】
実際のリモートでの開発や、文字ベースでのコミュニケーションの取り方の練習になった。

リモート研修 課題点
次に、課題点についてです。課題点については、挙がった意見のほとんどがコミュニケーションに関するものでした。以下に簡単にまとめたものをご紹介します。

【コミュニケーションの難航】
同期間のコミュニケーションが取りにくく、関係構築に時間がかかった。
また、細々としたものなど、質問自体がし難いと感じた。

4 来年度に向けて

bootcamp2021は、筆者を含めた20卒エンジニアが主体となって運営をすることになり、実施に向けた準備を着々と進めています。bootcamp2020での良かった点はしっかり踏襲した上で、一部研修での課題難易度調整および説明不足の解消や、フルリモート研修におけるコミュニケーションの更なる促進など、反省点はしっかり活かし、より良い研修を目指していきたいと思います。

一緒に働く仲間募集中!
【ジーニーのリクルートサイトはこちら】
https://geniee.co.jp/recruit/

11期上半期、「GENIEE DSP」は機械学習を活用した自動入札機能の正式提供を開始しました。企画から半年、驚異的なスピードでの機能リリースを成功させたエンジニア二人に、開発の裏話とチームでの取り組みを聞きました。

R&D本部 マネージャー代理 内木 正隆
「大規模なデータを使い様々な開発ができそう」と感じたことが、入社の決め手に。
R&D本部 リーダー 遠藤 悠平
「広告事業はデータ分析を実践的にできそうで面白そうだった」という思いでジーニーへ。

チームのミッションを教えてください。

遠藤  担当プロダクトの指標、広告配信の指標であるCVとCPAを最適化することです。

自動入札開発チームは、上半期MVT(Most Valuable Team)を受賞しました。成果について、改めて説明をお願いします。

内木 機械学習の予測を用いて入札の価格づけを自動的に行う「自動入札機能」をジーニーとして初めて開発しました。今まで人手に頼っていた広告運用作業を部分的に自動化することで、工数削減と広告パフォーマンスの改善を実現できました。
遠藤 私達が入社する前から、DSPにこの機能を組み込むことはジーニーにとって悲願でした。しかしお客様にも納得していただけて、かつ運用チームの要望を実現するのは、技術的に難しいものでした。

かなりスピード感のある開発だったそうですが、なぜ短期間で成果を上げることができたのでしょうか。

内木 自動入札機能自体は企画から半年でリリースできたのですが、周辺の開発も含めると丸3年かかってようやくできたものなんです。第一弾としてCTR(クリック率)予測機能を開発し、その後プロトタイプであるCVR(コンバージョン率)予測機能をリリースしました。
遠藤 リリース後、もっとこういう機能があったらいいのではと二人で話し合い、他機能をいくつか追加した「自動入札」の企画を私が上げたのが4月です。
内木 PMとCTOが開発の大枠の方向性を決め、主に遠藤さんがアイディアの取りまとめや要件定義、スケジュール設定を行っています。
遠藤 チームは各々が専門的な知識を持っているので、企画についてミーティングやテキストで議論し、プロジェクトに関わるチームみんなで形を作っています。配信チームのメンバーには、非常に技術的に難しいところを実現してもらいました。私たちの作ったモデルが実際に機能するかどうかは配信にかかっています。1億、10億といったリクエストを捌けるような配信構成を取り、実装できたのは配信チームのおかげです。

プロジェクトが計画通り進まない時、どのようにして課題を解決し、進めていますか。

内木 プロダクト開発は積み重ねです。使えるものが一度でできることは滅多にありません。予測の精度は赤字に直結します。予測する範囲等のチューニングを繰り返し、チームを跨いだ話し合いと改善を何度も重ねました。計画通り進まないことの方がむしろ多いです。今回企画から半年、初回リリースでパフォーマンスを出せたことの方が驚きです。

数年来開発を積み重ね、一つひとつ技術を積み重ね、メンバーの皆さんが一体となって進めてきたことが、結実したのですね。

遠藤 そうですね。自チームだけでは解決しない課題も多いですし、他チームとのコミュニケーションがずれていてうまくいかない時もあります。実装に至るまでは多くの過程を経るので、周辺の知識や解決策を多く知り、積み上げていくことが成功につながると思います。それを乗り越えてうまくいった時は、やはり嬉しいですね。この時のためにやっていると言っても過言ではないです。

※役職・職務は取材時のものです

大学生活につまずいていた2年生の3月、1本の広告に出会ったことで、道は拓かれました。
文系学部出身の僕が、アドテク企業のジーニーでエンジニアになった今、同じように悩む後輩たちに伝えたいこと。
(R&D本部 東哲志 2020年新卒でジーニーに入社。東京大学経済学部出身)

Contents

・なぜエンジニアになろうと思ったか
・3ヶ月でアプリをリリース
・経済学部での経験もエンジニアの仕事に生きる
・GENIEEでの仕事

なぜエンジニアになろうと思ったか

きっかけはFacebookの広告。「4時間でマリオ風ゲームを作ってみよう」というプログラミング無料体験会の宣伝でした。経済学部に進学が決まり、専門科目の基礎を一通りやったところで、自分にはこの学部は向いていないなと思っていた時で、何かやってみたいと思っていたところだったので、秒で飛びつき、気付いたら申し込み完了していました。大学2年の3月これがエンジニアライフの始まりです!
実際の体験会ではUnityを用いて避けゲー(オブジェクトを左右に移動させて障害物を避けながらゴールを目指すゲーム)を作りました。

画面上を移動させる主人公や障害物、ゴールなどは、Unity上に予め用意されているオブジェクト(球とか立方体)をドラッグ&ドロップで、Scene上に配置するだけでできてしまいます。なので、実際にプログラミングした内容としては、「主人公が一定のスピードでX座標上を移動する。右左のキーの入力を受けてY座標を移動する」という至ってシンプルな内容です。
プログラミングを初めてつまずく最初の難関は、大量の「気にしてはいけない」コードです。Unityで言えば以下のような初期コードが以下のような物で、初めての人にとっては意味不明な概念が大量に出てきます。

(void?,public,class?,MonoBehaviour?)
しかし、そういった細かいことが気にならないたちで、 Start(){} のなかに書いた内容が再生ボタンを押した際に1度だけ実行され、Update(){} のなかに書いた内容が1秒間に60回[^1]処理されるといった説明を自然と受け入れ、オブジェクトが意図通りに動くのを眺めてめっちゃ楽しい!となったわけです。

[^1]: 1秒間に何回処理されるかは実際には端末に依存するが、そんなことは初心者にとってはどうでもいい。

プログラミングを始めて3ヶ月でゲームアプリをリリース

そんな訳で、体験会後に誘われるままGeekSalonというプログラミングスクールで3ヶ月間のUnityコースに取り組むことになりました。目標はなんとアプリリリース!
作ったのはピンポンダッシュ風のアプリです。連打するタイプのゲームで、ロジック部分は簡単でしたが、ゲーム中に出てくる3Dのオブジェクトやキャラを、MagicaVoxelというアプリを用いて一から作ったり、Admobを使ってアプリ内広告を出したりといった点まで作り込みました。
かなり熱中していて大学の授業中でもずーっと作業を進めていたりしていて、メンターの方に色々と手伝ってもらいつつ無事AppleStoreとGooglePlayにリリースすることができました![^2] [^3]
スクールを卒業した後はエンジニアインターンに誘われ、長期インターン[^4]を始めました。3ヶ月間のプログラミング漬け生活は本当に楽しくて、エンジニアは天職だと確信していたので迷いはなく、そのまま大学3年の後期の休学を決めるまで時間はかかりませんでした。

[^2]: アプリ名を「ピンポンダッシュ」で出そうとして、Appleに「反社会的な行為を助長するアプリは受け入れられない」とRejectを食らってしまい、名前とキャラだけ変えてゴーストダッシュという謎のアプリを生み出してしまった。
[^3]: 広告収入より、AppleDeveloppersへの登録料(年間¥12,000)の方が高くついてしまうので、現在はAppStoreには公開されていない。
[^4]: 大学3年の夏からGeekSalonを運営していた株式会社Scovilleという会社で長期インターンをさせてもらっていた。

経済学部の経験もエンジニアの仕事に生きる

元々、経済学部が自分には向いていないと感じていた部分もあり、インターンにのめり込んでるうちにだんだんと大学の方が辛くなってしまっていました。半年単位で休学・復学・休学を繰り返し、辛くて本気で退学も視野に入れていのですが、このまま逃げるように辞めてしまうのもちょっと嫌だなと思うところがあったので、最終的に腹を括って、インターンは継続しつつ復学もしました。[^5]
ただ、卒業を目指すからにはきちんとということで、
・授業はきちんと出席、課題はきちっと提出
・試験勉強もちゃんとして余裕を持って単位をとる
といった基本的なことを目標に、経済学部での勉強をやり切りました。また幸いにして、他学部での講義も単位取得が可能であったため、コンピュータアーキテクチャ、オペレーティングシステム、ネットワーク基礎などの理・工学部の単位も修了しました。大学の最後の一年間は、エンジニアとして文系未経験の弱点をそのままにせず、経済の勉強も思いっきりできたのが良かったです。
文系未経験からエンジニアになれたとしてもその後が大変なイメージがあるかも知れません。実際の情報系出身の人に比べたらコンピュータ系の基礎理解はやっぱり弱くて、そういう点では苦労することがあります。
しかし、仕事をする上ではいろいろな分野のことを知っていることは間違いなく強みになります。実際、エンジニアの仕事はプログラムを書くばかりではなく、事業の方針を見て何を作るかを考えたり、エンジニアの人的リソースの分配などの戦略の部分を決めたりと、ビジネス側と協力する部分も非常に重要です。そういった話し合いや決定の背景を理解するのに、経済学部で学んだ土台がとても生きていると感じます。

[^5]: 親に大学だけは絶対出たほうが良いと言われたのもある。

GENIEEでの仕事

R&D本部 アドプラットフォーム開発部 DOOHグループのフロントチームに所属しています。DOOHというのはDigital Out of Homeの略で、屋外のデジタル広告を指しています。
[YUNIKA VISION (新宿)]

主な業務内容は、このようなサイネージへ広告を配信するためのプラットフォーム開発です。、フロントエンドの開発ではTypeScript+React+Next.jsと、流行りの技術スタックを使っており、どれも初めて触る技術だったのですが、最近ではだいぶ慣れてスムーズに開発を進められるようになってきました。また、チームに囚われず、いろんな技術に挑戦できる環境で、時にはフロントだけでなくバックエンドAPIの開発、配信やレポートのバッチ、インフラ設定なども担当しています。

現在の目標はフロントチームのリーダーになることです。チームでの開発を通してシステム全体の構成や、仕様はだいぶ把握できてきたので、今度はフロントエンドの技術をさらに極めて、技術的にもリードできる頼れるエンジニアになりたいと思います!

一緒に働く仲間募集中!
【ジーニーのリクルートサイトはこちら】
https://geniee.co.jp/recruit/

Date
Author
エンジニアの視点から、様々な技術、サービス開発秘話、イベントをご紹介していきます。 ジーニーエンジニアチーム
Tag

ClickHouseのテーブル構成を考え直してみた


今回が二度目の投稿になります。ClickHouse Meetup TokyoでジーニーのLT(Lightning Talk)を担当しました、R&D本部 元 基盤技術開発部の犬伏です。(現所属はマーケティングテクノロジー開発部)

記事その1: 「ClickHouse Meetup Tokyo」イベントレポート

記事その2: 「ClickHouse Meetup Tokyo」エンジニアレポート

LT担当者の視点からイベントを振り返りました。

今回の記事:「ClickHouseのテーブル構成を考え直してみた」

Meetupの後、過去に弊社で作成した構成をどのように修正したかを概説します。


目次

  • はじめに
  • 2017年に本ブログで紹介した内容の振り返り
    • 紹介した構造の説明
    • 紹介した構造の問題点
  • 新規に構築した構造の決定過程
    • 複数あり得たPARTITION構造
    • 頻出クエリ抽出
    • 性能評価の結果
    • 性能評価結果についての考察
  • 新規に構築した構造の詳細
  • 新規に構築した構造への移行手順
  • おわりに
  • おまけ
    • text_logについて

はじめに

昨年の ClickHouse Meetup からはだいぶ時間が経ってしまいました。部署異動に伴う業務のキャッチアップ、研修やBootcampなどが重なり、3月中としていた本記事の公開がとても遅れてしまい、申し訳ありませんでした。

前回の記事の公表後、ClickHouseのドキュメントに日本語版が(多くがまだ機械翻訳ではありますが)できたり、Changelogが整備されたり、昨年のMeetup内容にも触れたQiitaに記事が投稿されるなど、じわじわと日本でも広がりを見せているかと思われます。(ほんまか?)

2017年に弊社ブログで紹介した内容の振り返り

紹介した構造の説明

まずは、過去に弊社で作成した構成がどのようなものであったかを振り返ります。

テーブル構造:

  • 1日単位のデータを保持する日毎テーブル ( records_YYYYMMDD = SummingMergeTree(…) ) を作る
  • 1日単位のテーブルをまとめた全期間テーブル ( merge_recordes = Merge(…, ‘^records_[0-9]{8}$’) ) を作る

集計値の更新:

  1. 定期的に一時的な日毎テーブル( tmp_records_YYYYMMDD = SummingMergeTree(…) )を作り、ここへINSERTして新しい日毎テーブルを構築
  2. 新しい日毎テーブルの数値の妥当性を検証し、怪しければ警告を発して中断
  3. RENAME TABLEにより対象の日毎テーブルを差し替える

紹介した構造の問題点

弊社では現在、この構造にレプリケーションを加えた構造のクラスター「V2」と、これにさらにシャーディングを追加してデータ量も増やしたクラスター「V3」の2つのClickHouseクラスターがあります。

V2、V3それぞれの場合について、どのような問題が、どの程度の深刻度で存在していたかを説明します。

  • P1. 全期間テーブルへのクエリは大量の資源を消費し、かつ時間もかかる
    • 全期間テーブルは、V2ではマージテーブル、V3では分散テーブルのマージテーブルという構成であるが、それぞれが性質の異なった問題を生じる。
    • マージテーブル(V2): マージテーブルは内部的にはマッチするテーブル全てに対してクエリを発行する。例えば3年分のデータがDBに入っていれば、内部的には1000を超えるクエリが発行される。マージテーブルへのクエリの結果はこれらすべての内部クエリに対する結果を得るまで返されないので、最悪ケースに引っ張られてトータルでは時間もかかってしまう。特に日付横断的でフルスキャンに近いようなクエリの実行ではメモリ不足に陥ってしまう場合がある。
    • マージテーブル+分散テーブル(V3): マージテーブルの配下に分散テーブルが存在する場合、上記に加えてもっと厄介な問題が発生する。ClickHouseでは分散テーブルへのクエリに対しては他のシャードからデータを取得する必要があるが、マージテーブルによって N 個の内部クエリが発生した後、さらにシャードの数 S に対し、 S-1 個のコネクションが生成され、これと同じ数のクエリが外部シャードに対して発行される。デフォルト値は 1024 ( 根拠はこちら ) なので、年単位のデータを入れていけばすぐに限界が訪れる。また、 ClickHouse v19.7.3.9 時点ではこのようなクエリを投入すると再起動以外では ( KILL QUERY でも ) 停止できないクエリが生じてしまうという問題があった。 ( 注: 実験は行っていないが、この問題はこの修正で解消しているかもしれない。 )
  • P2. 大量の Replicated テーブルは ZooKeeper XID を高速に消費し XID overflow エラーを頻発させる
    • Replicated テーブルは、毎分ZooKeeperに対してリクエストを行うが、リクエストの度に順序一貫性を保つための XID をインクリメントする。これは符号付き32bit整数であるが、これは数千のテーブルに対しては一週間持つか持たないかという程度の猶予であり、また XID overflow となった場合、一時的にではあるが ZooKeeper とのセッションが断絶する。これは次のような問題を生じている
      • 一時期はより重篤な不具合があり、一部のテーブルが readonly になってしまうという状況もあったが、 ClickHouse v19.7.3.9 では改善されている。
      • また DROP TABLE などがアトミックな処理になっておらず、途中のZooKeeperリクエストが失敗すると中途半端な状態で放置される場合があったが、こちらも改善される見込みである。
    • このように、セッション断絶による致命的な不具合は既に修正されているものの、当時のバージョンにおいては INSERT や CREATE TABLE が中途半端に失敗することはあまり好ましい状況とは言えないため、弊社ではやむなく clickhouse-server を定期的に再起動し XID をリセットすることで対処しているが、これもやはり一時的とは言え部分的には可用性・耐障害性が低下しているため、望ましくない。
  • P3. RENAME TABLE の操作が遅延する場合がある
    • 置き換え対象のテーブルに対して実行中のSELECTがあると、ロックが解除されるまで実行されない。そのため、時間のかかるクエリがロックを開放しなければ、データの反映が遅延してしまう状況がある。
    • RENAME TABLE によってデータを更新するのは「V2」のみであるため、この問題については本記事では扱わない。

P1. によるメモリ消費は、仮に1日分のデータを集計するクエリであっても、インデックス ( 詳細はこちら ) が効かないような条件を指定すれば、その列はすべてメモリ上に展開されるため、全期間分の条件列がメモリ上に展開され、大きなメモリ消費に繋がります。 ( これは日付範囲で絞っているつもりのクエリでも、書き方が良くなければ同じ状況が発生します。これについては後述します。 )

単にこの問題に対処するだけであれば、1日分の結果を得る場合に限り、全期間テーブルではなく日毎テーブルを参照すれば良いのですが、期間が1日ではなく1週間や1月といった単位になってしまうと対応できなくなる他 ( 注: merge table function を使うことでこの問題には対処できるが、この機能を使うためには、 readonly=2 以上の権限が必要となる。この権限を持っていると、 max_memory_usage の変更など、管理者側が望まない設定変更も行うことができてしまうため、望ましくない。 ) 、「V3」では時間軸が一次元ではない(後述します)ために、この対策も良い解決策とはなりませんでした。

また P2. によるサーバー再起動は、タイミングを制御して行っているためクラスター全体としての可用性は保っているもの、再起動の時間帯にはそのサーバーで実行中のクエリが失敗してしまうため、クエリ単位では失敗してしまう確率を上げてしまっていました。

そこで、クラスターの安定性を向上させるとともに、あわよくば応答速度も高速化しようという目的で、抜本的にテーブルの構成を変更することとなりました。

新規に構築した構造の決定過程

新規にテーブル構造を設計するにあたり、これまで登場していなかったパーティションという概念が必要になるので、先にこれを説明します。

次に、 Partitioning key をどのように選択したかを順を追って説明します。Partitioning key は通常データの取り回し方から決定されるもので、ClickHouseでは月単位でのパーティショニングが推奨されていると言えるでしょう (注: 明言されているドキュメントがあったか定かではないが、想定する最大のパーティション数が1000-10000とされているほか、多くの例示において toYYYYMM が用いられている) 。

しかし私たちは、とある理由でパフォーマンス上の問題も考慮する必要がありました。この経緯や詳細については「複数あり得たPARTITION構造」で述べます。

次に私たちは、最終的なテーブル構造を選定するため、実際によく叩かれているクエリを集め、それらの典型的な集計期間を定めて、それぞれの構造に対してパフォーマンス計測を行いました。この手順の詳細及び結果、考察については「頻出クエリ抽出」及び「性能評価の結果」、「性能評価結果についての考察」で述べます。

複数あり得たPARTITION構造

私たちのClickHouseテーブルには、キーとなる日時 DateTime 型のカラムが2つあります。やや込み入った内容になるので、一旦進みたい方は、「統一時刻」と「現地時刻」という意味の違う2種類の日付が必要になった、とご理解ください。

★☆★ 込み入った説明ここから ★☆★

前提知識:

  1. ClickHouseの DateTime 型は時刻をUNIXタイムスタンプで保持する。
  2. UNIXタイムスタンプにはタイムゾーン情報は含まれない。UNIXタイムスタンプとタイムゾーンの組み合わせで、はじめて「ある国・地域のタイムゾーンでの時刻」を表現できる。

一つは正当なUNIXタイムスタンプを記録するカラムで、従ってすべての行が同じタイムゾーンで記録されるものです ( 説明のため「統一時刻」と呼びます ) 。

もう一つがトリッキーで、海外での配信成果の集計をその国・地域でのタイムゾーンに沿って行うために、「タイムゾーンをUTCとして取得すると現地での時刻を得られるUNIXタイムスタンプ」を記録するカラムです ( 説明のため「現地時刻」と呼びます ) 。例えば日本時間 (JST, UTC+9) であれば、通常のUNIXタイムスタンプに9時間分、 32400 (秒) を加えたものを記録します。こうしておくことで、UTCとして取得したときに、9時間進んだ時刻を取得できます。 ( 注: 本当は行ごとにタイムゾーン情報を保持できれば最も望ましかったのですが、そういった機能はなかった上、たとえあったとしても「現地時刻」の取得で計算が必要になるため、「統一時刻」と同等のクエリ性能を発揮しにくいため、 DateTime 型で2種類の時刻を保持するのが最も高速に要求を実現できると判断され、このような構成となりました。 )

★☆★ 込み入った説明ここまで ★☆★

私たちは日本時間での時間 (hour) 単位で集計してデータベースへの投入を行っていたため、再集計のことだけを考えれば、日本時間での日単位でパーティショニングしてしまえばよいのですが、その後に行われるバッチ類のSELECTクエリでは各国・地域のタイムゾーンでの日単位で行っているものも多く、これらについては日本時間での日単位でのパーティショニングのみであると、パーティションの中からは該当するものを発見できなくなり、すべてのパーティションのインデックスを参照しに行くことになってしまいます。

このオーバーヘッドがどの程度かを見積もるため、私たちは日本時間の日単位のみのパーティショニングと、日本時間の日単位と現地時間の日単位の組による二次元 (注: 見た目上は二次元ではあるものの、日本時間と現地時間の差分は一定の範囲に限定されているので、パーティション数の増え方は線形オーダーです。) のパーティショニングの両方を試そうと考えました。

頻出クエリ抽出

さて、パフォーマンステストのためには妥当なベンチマークが必要です。しかしこのベンチマークの用意は当初想定した以上に厄介なものでした。

クエリをいくつか用意すれば良い、と漠然と考えていましたが、実際にどういうクエリが速いと嬉しいかとなると、単純に全件取ってくるとか、特定の1列だけを取得してくるとか、集約単位 (group by) の細かさはどうするかとか、自由度が高すぎてわからなくなってしまいました。

そこで、実際に使われているクエリを16個程度 (注: 数は勘で決めた) 集め、それらの結果がどの程度改善するかを見ることでベンチマークとする、という方針を立て、クエリを集め始めました。このテーブルを使っているチームに「こういうクエリを使っているとか、こういうクエリが早くなると嬉しいとか、教えてください」と呼びかけ、協力を求めました。

しかしここでも課題が生じました。エンジニアに質問するとたいていバッチ処理のためのクエリが返ってきますが、実際に飛んできているクエリを見てみると、ビジネス側の社員も社内のRedashを経由してそれなりの数のクエリを投げていることがわかりました。こういったクエリもできるだけベンチマークに組み込みたくなりました。

そして最終的には、クエリログ (注: 公式ドキュメント: https://clickhouse.tech/docs/en/operations/system-tables/query_log/) からよく飛んできている、あるいは資源を多く消費しているクエリを集めることにしました。

しかし、クエリを集めると言っても、通常テンプレートとなるクエリが存在し、実際に投入されるクエリはその中の文字列や IN 節の括弧内、数値など、様々な部分が変化します。この部分を同じに扱ってやらないと同じクエリとみなすことができず、回数のカウントが漏れてしまいます。そこで私たちは次のようなクエリを作り、これを調査しました。

SELECT
    replaceRegexpAll(
        replaceRegexpAll(
            replaceRegexpAll(
                replaceRegexpAll(
                    replaceRegexpAll(query, '(--.*?)?\n', ''),  -- コメント・改行除去
                    '\\s+', ' '),                               -- 連続する空白文字を同一視
                '\\s*\\d+\\s*', '0'),                           -- 数を同一視
            '0(,0)*', ' 0 '),                                   -- 数・数列を同一視
        '\'.*?\'', '\'\'') AS query_template,                   -- 文字列を同一視
    any(query) as query_sample,
    count(*) AS execution_count
FROM
    system.query_log
WHERE
    is_initial_query                -- ユーザーが直接投入したクエリを対象とする
    AND
    NOT match(query, '.*INSERT.*')  -- INSERTクエリは除外する (INSERT ... SELECT は使っていない)
GROUP BY
    query_template
ORDER BY
    execution_count DESC
LIMIT 64
FORMAT Vertical

replaceRegexpAll によって、実際に実行されたクエリをそのテンプレートのようなものに変換して、この数を数えることで実行回数を調べました。

実際にはこのあと、バッチかヒトかを推測するためや使われなくなった古いクエリを除外するためにクエリの実行周期を調べたり、実行時間やメモリ消費量などの指標も加えて取得するようにするなども行いましたが、割愛します。

性能評価の結果

性能評価にあたって、テーブル構造以外に一つ注視すべき設定項目がありました。分散テーブルのマージテーブルに対しては ClickHouse v19.7.3.9 時点では誤った結果が返却されるため使えなかった、 distributed_aggregation_memory_efficient (注: 公式ドキュメント: https://clickhouse.tech/docs/en/sql-reference/statements/select/group-by/#select-group-by-in-external-memory) という設定項目です。(長いので、以下 DAME と略します。)

分散テーブルのマージテーブルという構成をやめ、パーティションを使うことにした結果、この設定項目も使えるようになったので、これを有効化した場合にそれぞれの構造でどの程度パフォーマンスが向上するかも加えて評価することにしました。

よって性能評価は以下の5種類の比較となりました。

  1. base(旧構成)
  2. single(新構成、日本時間のみのパーティショニング)
  3. single_DAME(新構成、日本時間のみのパーティショニング + DAME )
  4. twin(新構成、二次元のパーティショニング)
  5. twin_DAME(新構成、二次元のパーティショニング + DAME )

また、集めたクエリは以下の13個となりました。

  • No.3(頻度3位、現地時間で細かい単位で集約する、大量の行を返す重いクエリ)
  • No.5(頻度5位、現地時間で細かい単位で集約するが、結果行数は少ないクエリ)
  • No.7(頻度7位、現地時間で細かい単位で集約する、大量の行を返す重いクエリ)
  • No.8(頻度8位、日本時間で「直近24時間」を粗い単位で集約する軽いはずのクエリ)
  • No.9(頻度9位、日本時間で「直近28日間」を粗い単位で集約するちょい重いクエリ)
  • No.100(人間がたまに叩く、重いが速いと嬉しいクエリその0)
  • No.110(人間がたまに叩く、重いが速いと嬉しいクエリその1)
  • No.19(頻度19位、日本時間で、決められた4時間の間隔のデータを無差別に合計する監視用クエリ)
    • このクエリでは、旧構造についてのみ、日本時間での1日分のデータのみを集約していることを利用して、全期間テーブルではなくその日の日毎テーブルを直接参照するという最適化を行っている。
    • またこのクエリでは PREWHERE での最適化が行われておらず、インデックスで範囲を絞れないようになっていたため、実質的に意味のないベンチマークとなってしまった。
    • しかしながら、No.919にて最適化されたものの結果も得ているのでこちらを信用すれば良い。(呼び出し箇所は後で書き換えました。)
  • No.29(頻度29位、日本時間で、1ヶ月間のデータを荒い単位で集約するクエリ)
  • No.903(頻度3位を筆者が最適化したもの)
  • No.905(頻度5位を筆者が最適化したもの)
  • No.907(頻度7位を筆者が最適化したもの)
  • No.919(頻度19位を筆者が最適化したもの)

ベンチマークの計測方法は次のとおりです:

  • それぞれのテーブル構造に対して、若いナンバーから順に一つずつ実行していきます。これを9回繰り返します。
  • 他のクエリと重複する範囲をSELECTするなどによって有利不利が生じないよう、最初の2回の結果は捨てます。(注: この間に、データがOSによってキャッシュされることを期待します。)
  • その後の7回から、「最速値」及び「期待される値」を計算します。「期待される値」には、7回のうち遅い2回を除いた5回の平均を使います。(注: クラスターそれぞれの計算資源やネットワークなどを再現することが難しかったため、本番環境上でベンチマークを実行した都合上、運悪く他の重いクエリと同時に動作した場合に不利になってしまうのを回避しようという意図です。)
  • 実行エラー(すべてメモリ制限超過と確認済み)になった実行の実行時間及びメモリ消費量は、例外処理が面倒だったのでそれぞれ 999,999 ms , 999,999,999,999 Byte に設定しました。
  • パフォーマンス計測の際には、クラスター上でのクエリの実行についてのみ興味があるため、 FORMAT Null を指定することで結果を捨てています。
  • 結果の集計では、実行時間やメモリ消費量の統計をあとから楽に計算できるように、それぞれのクエリの先頭に定型コメントを付与した上で、あとからまとめてクエリログを参照しました。このようにしたことで、集計の計算も含めてクエリログに対するクエリ内で完結させることができました。

ベンチマーク結果は次のとおりです:

  • 左側は、大雑把に緑色が最良、薄いグレーが次点(1.2倍以内)、赤が最悪、白はその他です。中央のグラフは左側の対数プロット、右側のグラフは最良を1としたときの相対値です。
  • 上から、最速実行時間、期待実行時間、最小メモリ消費量、期待メモリ消費量です。

テーブル構造のSELECT性能比較

性能評価結果についての考察

実行速度上は、全体的に twin_DAME が多くの最善を叩き出しており、最速を逃した6つのうち4つはDAMEでないほうに僅差で負けているのみで、残った7番は全体的に僅差であり、19番はもともと勝ち目のない比較を行っているため考慮する必要がありません。

次にメモリ消費ですが、横並びから19番、905番、919番にてやや twin 系が多くのメモリを消費する傾向が見て取れます。

このことから、テーブル構造としては二次元の構造を採用することとしました。

ここからは蛇足ですが、No.3の旧構造では結局全ての実行がメモリエラーで落ちてしまいました。一応本番で何度かは成功しているはずのクエリなので、定常的なメモリ負荷があったのかもしれません。

またNo.3からNo.9までは無視できない確率で(少なくとも9回に1回以上は)メモリ不足で失敗してしまうことがありましたが、クエリを書き直すことである程度症状は緩和できています。(それでもNo.905では8倍近く時間を使ってしまっていますが…)

新規に構築した構造の詳細

ということで、旧来の「分散テーブルのマージテーブル」という構造は、「パーティション付きのReplicatedSummingMergeTree」という構造に置き換えられることになりました。

DDL上は、日毎テーブルの名前から日付部分を取り去って全期間テーブルのように改め、以下の Partitioning key の指定を加えるのみです。しかし、私たちの場合は後述する移行手順のために、直接古いマージテーブルを上書きすることはせず、別に新しい名前で新しいテーブルを作り、それのみを含むマージテーブルを新しく作って古いマージテーブルと差し替える ( RENAME TABLE ) という方法を取りました。

この部分をマージテーブルではなくビューテーブルを使うことも検討しましたが、 PREWHERE が使えなくなるなどマージテーブルの場合と比べて互換性を維持できなかったために棄却されました。

結局、マージテーブルの部分がパーティションに変化しただけであるため、基本的には旧来の全期間テーブル向けにかかれているクエリであれば書き換えなく動作します。(書き換えることでさらに速くなる可能性はありますが…)

CREATE TABLE our_database.shard_reports ON CLUSTER our_cluster
ENGINE = ReplicatedSummingMergeTree(...)
-- ここから
PARTITION BY (
    toYYYYMMDD(現地時刻みたいな日時カラム, 'UTC'),
    toYYYYMMDD(普通の日時カラム, 'Asia/Tokyo')
)
-- ここまで
ORDER BY ...

新規に構築した構造への移行手順

ディスク容量に余裕があったので、新しい構造でテーブルを作り、そこへINSERTの速度をプロダクトに影響が出ない範囲に抑えつつデータを流し込むことで基本的には対応できました。

しかし、ある程度時間のたった範囲のデータについては既に確定しているので特に考慮することなく入れ直すだけで済みますが、直近のデータは常に古いテーブルのほうへ流入してくるので対応にやや苦慮しました。

また、参照しているクエリをすべて把握しているわけでもなかったため、ユーザーからは同じテーブル名のデータが差し替わることが期待されました。

そのため、一度古いテーブルへのデータの流入を止め、直近分のデータを新しいテーブルに入れてから、互換性のためにマージテーブルを差し替え、その後新しいテーブルに向けてデータの流入を再開する必要がありました。この手順を時系列で表すと次のようになります。

概略 詳細・遷移条件
A: 確認用の数値を取得 D, G あたりで確認するため、過去分の各列の合計等を取得

(直近分は B のあとに行うが、過去分は分量が多く実行に時間がかかってしまうので、先に行っておくとよい)

B: Flinkを停止 Flinkを停止し、ClickHouseへのデータ流入を止める。

【ClickHouseへの流入が止まったら goto C】

C: バッチを停止 本当は長時間かかる可能性があるクエリは全部止めたいところ、 E 向けの布石
D: 直近データ移行 すべてのreplicaについてreplicationが完了していることを確認したのち、直近分のデータを旧テーブルから新テーブルにINSERT

【数値の一致を見たら goto E】

E: 全期間テーブル置き換え RENAME TABLE によって行う

【入れ替え完了が確認されたら goto F】

F: Flinkを再開 新しいテーブルの方にINSERTするように変更するのを忘れない(忘れるとだいぶ面倒なことに)
G: みまもる 大丈夫そうならCで止めたバッチを戻す

様子がおかしかったら臨機応変に対応

テーブル構造更新の手順

この手順により、実際には無事「臨機応変に頑張る」ことなく移行を完了することができました。

おわりに

今回は、2017年に弊社ブログで紹介した内容を振り返り、そこで紹介されていたClickHouseのテーブルについて構造上の課題を指摘し、これをより良い構造で再構築した過程を、順を追って説明しました。

特にメモリ使用量やクエリの応答速度で悩まれている方は、パーティションを用いた構造に修正されることをおすすめします。

もし「次回」があれば、ClickHouseクラスターのバージョンアップ( v19.7.3.9 to v19.16.14.65)についての記事になるかと思います。

おまけ

text_log について

text_log は ClickHouse v19.14 以降で使える機能で、 /var/log/clickhouse-server/clickhouse-server.log に出ていたClickHouseのログをデータベース上のテーブルにも保存することができます。

特に、「何時頃のこういうエラーログ」という条件さえわかっていればすばやくログにアクセスできる、どういうエラーがどういうタイミングで発生しているのかの調査など、テーブルとして存在していることによる恩恵は大きいと感じました。

ただし、 system.query_log と同様にストレージ容量を食いつぶす可能性もあるので、パーティショニングや容量の監視は常に行う必要があります。

公式ドキュメント: https://clickhouse.tech/docs/en/operations/system-tables/text_log/

設定の書き方等参考PR: https://github.com/ClickHouse/ClickHouse/pull/6164/files

Date
Author
エンジニアの視点から、様々な技術、サービス開発秘話、イベントをご紹介していきます。 ジーニーエンジニアチーム
Tag

こんにちは、R&D本部基盤技術開発部の内木です。

私は現在ジーニーで主にGenieeDSPの入札部分のロジック実装などを行っています。今回はDSPの入札ロジックに欠かせないCTR(Click Through Rate)予測について、またそれを支えるデータ分析・処理環境についてご紹介します。

CTR予測とは

DSPは広告主に広告を出す機会を提供するプラットフォームです。広告主は費用を使って様々なメディアに広告を出すことで、商品購入など何らかの効果を目指します。弊社を含め多くのDSP事業者では以下で決まるCTRを予測することがDSPにとって重要な機能の一つとなっています。

CTR <- (広告が表示されて、クリックされた回数) / (広告が表示された回数)

CTRを予測するには、どのメディアに出すかはもちろんですが、メディアの中での広告の位置、出される広告コンテンツの内容、広告を見ているユーザーの特性・OS情報など、多様な情報を考慮する必要があります。より良いCTR予測を行うには日々変わる広告パフォーマンスの傾向を考慮した予測モデルで日々改善・修正していく必要があります。
ジーニーの広告表示回数は約800億/月の規模です。この規模のログを様々な軸でデータの傾向を頻繁に確認するために、弊社ではデータ処理・分析環境を整備しています。

データ処理・分析環境

ジーニーではCTR予測モデルをはじめとして、大規模なデータ処理・分析の一部をGCP上で行っています。GCPでデータを処理する大きな理由の一つとして、BigQueryが自由に使えることが挙げられます。

BigQueryでは数TBのデータでも基本的な集計処理なら早くて数秒、ウインドウ関数やJavascriptで記述できるUDFをはじめとした負荷のかかるデータ処理でも多くの場合数分もすれば結果が返ってきます。本番環境でこのような負荷のかかる処理が本当に必要かは慎重に考える必要がありますが、こういった環境はアドホックな処理・調査の効率や頻度などデータ全体に対して大きく影響してきます。

CTR予測モデル作成をBigQuery上で完結させることはできませんが、機械学習には欠かせないデータの前処理・調査で使われています。今のアーキテクチャでは前処理されたデータはGCS(Google Cloud Storage)を通してGCE(Google Compute Engine)に送られ、予測モデルの学習を経て作成されたモデルは再度GCSを通して広告の配信サーバーに渡され、日々新しい予測モデルが更新されていきます。

終わりに

今回はCTRについての説明やBigQueryをはじめとしたデータ処理基盤など、弊社のデータ分析の一例を説明させていただきました。
ジーニーではデータ基盤の運用方法から機械学習などを利用したプロダクトのロジック改善方法に到るまで、データ活用をより盛んにしていくため開発環境をまだまだ改善していきたいと考えています。今回ご紹介した内容に興味を持っていただければ幸いです。

Date
Author
エンジニアの視点から、様々な技術、サービス開発秘話、イベントをご紹介していきます。 ジーニーエンジニアチーム
Tag

はじめまして。ClickHouse Meetup TokyoでジーニーのLTを担当しました、R&D本部 基盤技術開発部の犬伏です。

前回の記事: 「ClickHouse MeetupTokyo」イベントレポート

今回の「ClickHouse Meetup Tokyo」エンジニアレポートでは、LT発表者の視点からイベントを振り返ります。

次回の記事:「ClickHouseの構成を考え直してみた」では、Meetupの後、過去に弊社で構築された構成をどのように修正したかを概説します。


目次

  • ClickHouseとは
  • Meetupの所感
  • 弊社LTの発表スライド
  • 弊社LTでの質疑応答まとめ
  • まとめ、今後について

ClickHouseとは

ClickHouseは露Yandex社が開発するオープンソースの列指向のDBMSであり、行指向のDBMS(例えばMySQL)とは得意分野の異なるDBMSです。

今回の本題は別のところにあるので、ClickHouseそのものについての説明は省略します。

ClickHouseそのものの詳細については以下のリンク先を参照ください:

Meetupの所感

Meetupを開催したことによるメリット

  • LT発表用のスライドを作成する過程で、
    • 現状のシステムについて、開発上のこれまでの経緯や現状の構成などへの理解が深まった
    • ClickHouseそのもののドキュメントを再読することで、忘れていた機能や追加された新機能に気付けた
  • ClickHouse開発者の方々に現状の構成や課題に対してアドバイスをもらえた

Meetupの様子は前回の記事: 「ClickHouse Meetup Tokyo」イベントレポート をご覧ください。

MeetupのYouTube配信: https://www.youtube.com/watch?v=728Yywcd5ys
ClickHouse開発チームの発表スライド: https://github.com/ClickHouse/clickhouse-presentations/tree/master/meetup34

弊社LTの発表スライド

弊社LTでの質疑応答まとめ

※ 注意:回答で例示される挙動は、 ClickHouse 19.7.3.9 の挙動を元にしていますので、新しいバージョンでは挙動が異なる可能性があります。(Yandex社のAlexeyさんによる回答を除く)

質問1. クエリの静的解析について「共通部分式削除」とあったが、同じサブクエリもキャッシュされるのか?

  • 回答 by Alexey (Yandex)
    • No.
    • Usual expressions are cached. Similar expressions will be executed only once.
    • But sub-queries are not.
  • 補足
    • スライドでの私の意図は、例えば下に示すクエリAで、 SUM(x) が一度のみ計算されるというものでした。
    • しかし、サブクエリに関してはキャッシュされないため、同じ意味の表現であっても書いた回数だけ計算され時間がかかってしまうようです。

クエリA:

SELECT
    SUM(a) / SUM(x),
    SUM(b) + SUM(x)
FROM default.test

質問2.冗長構成にして欲しいと言われているのですが、どんなところを気をつければいいか?

  • 回答
    • replication(データ複製)をしてくれるテーブルエンジン(table engine)が用意されています。
    • ZooKeeperをコーディネータとして利用することで、テーブルとして同期を取ってくれます。

質問3. INSERT後にそれが反映されるタイミングはサーバーごとに異なる?

  • 回答
    • はい、ただし正常な設定では1秒程度以下の遅れです。(Replicatedなテーブルを想定して回答)
  • 補足
    • Replicatedなテーブルの場合には、一度ZooKeeperを経由するため、反映は同期しません。
    • DistributedテーブルにINSERTした場合、データがそれぞれのshardに分散されるため、INSERTが完了したと応答した時点と、SELECTの対象になるタイミングは厳密には一致しません。
    • 当然INSERTが詰まったりすればその限りではないが、スループット内の範囲であれば1秒も見ておけば十分
      • INSERTが詰まる場合の例:大量のデータのINSERT、頻繁過ぎるINSERT、一度に多くのパーティションにINSERTされるようなINSERT、貧弱すぎるレプリカ間の接続など

質問4. ClickHouse自体の監視はどのように行っているのか。内部のmetricsをexportしているのか?

質問5. 監査用のログ(誰がログインして、誰がどういう操作をして、という情報)は取得可能ですか?

質問6. 2年前のジーニーの記事の構成をもとに分散テーブルを使ってデータ分散(sharding)をしている状況で、前日のテーブルの内容を差し替えたい。どうすればいいか?

  • 説明
    • サーバーが1台の時にはうまく行くが、複数サーバーでshardingを始めたらつらくなった。
    • 弊社の記事の構成では、分散テーブルを使っていなかったので、テーブルの差し替え方法が説明されていなかった。
  • 回答
    • Distributedテーブルに INSERT すれば勝手にデータが分散されるので、これを利用します。
    • 手順は次の通り:
      • 昨日分の一時テーブル tmp_reports_YYYYMMDD と、これを参照する Distributed テーブルtmp_dist_reports_YYYYMMDD を作る
        • この際、トップレベルのMergeテーブルの正規表現に tmp_dist_reports_YYYYMMDDテーブルがマッチしないように注意すること(さもないと昨日分のデータが重複することになる)
      • tmp_dist_reports_YYYYMMDD に対して更新するデータの INSERT を行う(裏で勝手に sharding される)
      • すべてのサーバー上の tmp_reports_YYYYMMDD について sharding と replication が済むのを待つ
      • RENAME TABLE reports_YYYYMMDD TO trash_reports_YYYYMMDD, tmp_reports_YYYYMMDD TO reports_YYYYMMDD ON CLUSTER <cluster_name>をすると、全 shard のテーブルが入れ替わり、データの差し替えが完了する
    • ただし、過去の弊社の記事による構成は運用上望ましくないため、次の記事「ClickHouseの構成を考え直してみた」にて詳述する構成自体の改善方法に従って構築をやり直すことをおすすめします。

質問7. 日毎にテーブルを持つ構成の問題点

  • 説明
    • 2年前の弊社の記事の構成を見て、同じように日別のテーブルを持って同じように失敗している。どう解決すればよいか。
  • 回答
    • 例えば、次のような運用上の不都合が発生します:
      • 数年分のデータ(つまり数千個のReplicatedテーブル)を保持していると、数日に1回程度の頻度で XID overflow によりクエリが失敗するようになります。
        • このため、数日に一度、XID overflow が発生する前にサーバーを再起動して ZooKeeper XID をリセットするといった運用が必要になります。
      • SELECTの際に、Mergeテーブルがまずサーバー内で数千のDistributedテーブルへのSELECTに分解されるため、他のshardへのSELECTがすべてのDistributedテーブルから独立して行われ、結果connection が不必要に大量に乱立することとなり、ConnectionPool が枯渇し、Cannot schedule a taskに至ることがあります。
        • こうなったクエリは応答不能( KILL QUERY で殺せない)となることがあり、サーバー再起動でしか対処できなくなります。
        • 特に ConnectionPool すべてを確保した状態で応答不能になると、他のクエリ実行も不可能になります。
      • テーブルがたくさん存在するため、列の追加や削除などのテーブル構成に対する操作が非常な手間を伴います。
    • 次の記事「ClickHouseの構成を考え直してみた」にて詳述する構成自体の改善方法に従って構築をやり直すことをおすすめします。

質問8. 自社では1クラスターあたり少ない台数でClickHouseを動かしているが、最大どのぐらいまでスケールするのか

  • 回答 by Alexey (Yandex)
    • How big can ClickHouse cluster be?
    • 600 servers, but this is not the biggest cluster.
    • A Chinese company uses more than 1000 servers.
    • ClickHouse can scale.

質問9. 障害後の復旧の自動化など、運用上の工夫等あれば

  • 説明
    • 運用していて、Replica か ZooKeeper cluster が死んで、CREATE TABLE ができなかった
      • ZooKeeper cluster がダウンしたら Replicated Table は readonly になる
  • 回答
    • 以下の公式ドキュメントにまとまっている。
    • Replica が死んだ場合の回復方法
      • 新しいサーバーにClickHouseをインストールし、Replicatedテーブルを正しいZooKeeperパスとともに作成し、ZooKeeper及び既存のクラスターと接続すれば、自動的にreplicationが行われ、いずれ追いつきます。
    • ZooKeeper cluster が死んだ場合の回復方法
      • ClickHouse replicated tables will be readonly mode. INSERT cannot be performed.
      • ZooKeeper itself has also a replication.

質問10. 中国で1000台の ClickHouse cluster があるとのことだが、そのクラスターの ZooKeeper はどうなっているのか?

  • 回答 by Alexey (Yandex)
    • That company does not use ZooKeeper.
    • Yandex uses 3 ZooKeeper servers and this is the recommended configuration.
    • How to better configure ZooKeeper:
      • Use SSDs.
      • 10M nodes -> typical amount of memory is 128GB of RAM.

まとめ、今後について

  • 今回は、(著者が社内のClickHouseの構成変更を終えたため、まずは)Meetupの振り返りを投稿しました。
  • 次回の記事:「ClickHouseの構成を考え直してみた」では、Meetupの後、過去に弊社で構築された構成をどのように修正したかを概説します。
  • 次回の記事は2020年3月中に公開予定です。
  • 予定目次は次のとおりです:
    • 2017年に弊社ブログで紹介した内容の振り返り
      • 紹介した構造の説明
      • 紹介した構造の問題点指摘
    • 新規に構築した構造の決定過程
      • 複数在り得たPARTITION構造
      • distributed_aggregation_memory_efficientの設定
        • 注意:過去に紹介した構造(Merge of Distributed)にこの設定を使用すると、ClickHouse 19.7.3.9では期待しない動作をしますのでご注意ください!!
      • 頻出クエリ抽出
      • 性能評価の結果
    • 新規に構築した構造の説明
    • 新規に構築した構造への移行手順
    • おまけ
      • 19.7.3.9以降の重要な更新について
      • PREWHEREについて
Date
Author
エンジニアの視点から、様々な技術、サービス開発秘話、イベントをご紹介していきます。 ジーニーエンジニアチーム

こんにちは。R&D本部の萩原です。
Yandex社が中心となって開発しているClickHouseの日本初Meetupイベントがジーニーオフィスで11/14(木)に開催されます。
※過去のClickHouseの記事はこちら

ClickHouseとは?

ClickHouseはYandex社が開発しているオープンソースの優れた列指向データベース管理システムで、ジーニーでも利用しています。
従来のアプローチに比べて数十〜数百倍の処理速度で動作し、SQLクエリを使用してリアルタイムで数十億行・数十GBのデータを集計・解析できます。
加えて、スケーラビリティや耐障害性も備えており、複数のデータセンターにまたがってデプロイすることもできます。

列指向DBといった意味ではRedshift等は気軽に触ったことある人はいるとおもいますが
ClickHouseは日本では使われているケース使ってみたケースはあまりないと思っています。

 

ClickHouse Meetup Tokyo 2019

当日は以下の内容を予定しています。

1.Yandex社によるClickHouseのイントロダクション
2.GenieeによるClickHouseの導入事例
3.参加者によるライトニングトーク
4.Yandex社によるClickHouseの将来展望について
5.懇親会

今回のmeetupでは、事例やLTを通じてClickHouseの知見や体験を共有できると思いますのでお気軽にご参加ください!
また、事例等のLT参加メンバーもお待ちしています!

登録はconnpassよりどうぞ

Date
Author
エンジニアの視点から、様々な技術、サービス開発秘話、イベントをご紹介していきます。 ジーニーエンジニアチーム

こんにちは。事業開発本部Quality Assuranceチームです。

以前GENIEEエンジニアブログで「アドフラウドに対する技術的な取り組み」というタイトルでアドフラウドに対するGENIEEのシステム的な取り組みをご紹介させていただきました。
昨今様々なメディアやテレビなどで取り上げられているように、アドフラウド(不正広告)によるインターネット広告の不透明性が問題となっております。
以前はシステム的な取り組みのご紹介がメインとなりましたが、今回は主にサプライサイドの総合的な対策を中心にご紹介させていただきます。

GENIEEでは現在アドフラウドに対する取り組みを大きく3つの軸に分けて行っております。
・ブランドセーフティの確保(メディアの品質)
・不正実装対策(広告枠の品質)
・Traffic Qualityの担保(トラフィックの品質)

それぞれの品質を担保するための対策についてご紹介してまいります。

 

1.ブランドセーフティの確保

アドフラウド対策の1つ目の軸は「ブランドセーフティの確保」です。
GENIEEでは従来、新規媒体社の登録時の法務による審査や成人コンテンツや報酬付与型のメディアに対する広告案件及び広告事業者の配信の制限、JIAAより共有いただくNGメディアリストへの配信制限等を行っておりました。
しかし、ブランド価値を重視する広告主の意向により沿った広告配信を実現するため、媒体審査基準のさらなる精緻化を行い、審査フローを再整備いたしました。
新規登録されるウェブサイトに関して、不正実装と同様に第三者機関に委託して目視チェックを行っております。
また、すでに広告配信している既存のウェブサイトにつきましても、定期的に抽出し目視チェックを行うことで再審査するフローを実行しております。

 

2.不正実装対策への取り組み

アドフラウド対策の2つ目の軸は「不正実装の取り締まり強化」です。
GENIEEでは「広告実装ガイドライン」を施行することで不正実装の基準を具体的に明確化しております。
それに基づき、ガイドライン違反となる実装を自社開発の独自ツールで検出し、さらに第三者機関に委託して目視チェックを実施しております。媒体社側に是正勧告をしても是正されない場合には配信停止措置も行っております。
今後はツールにおける不正判定精度を上げていくとともに、検知頻度を増やしていき対策強化を計ってまいります。

 

3.Traffic Qualityの担保

3つ目の軸は「Traffic Qualityの担保」です。Traffic Qualityとは簡単に言えばそのメディアに対するアクセスの品質です。低下させる要素としてはボット等の不正なアクセス、なりすましといったものが挙げられます。
GENIEEでは以前からAds.txtの設置率のモニタリングや異常なCTRが見られた枠の目視確認等の対策を行ってまいりましたが、アドフラウド対策の文脈でよく語られるような以下の課題にGENIEEも突き当たりました。

■主な課題点
・ エンジニアの稼働が調査に大きく必要となる
・多様化したデータパターンを把握しきれない
・データ分析者の先入観による取りこぼしの懸念
・膨大なトラフィック量に対して分析が追い付かない

上記を解消するため、GENIEEでは従来の対応に加えて「Forensiq(公式サイト)」というアドベリフィケーションツールを併用し、Traffic Qualityの品質を計測及び改善対策を行っております。
「Forensiq」はMedia Rating Council※1やTrustworthy Accountability Group※2といったアドフラウド対策団体に認定されたアドベリフィケーションツールです。対応スピードの向上やさらなる透明化を目指すために第三者の視点のアドベリフィケーションツールを導入しています。

Forensiqには複数のデータポイント(分析軸のようなもの)があり、それぞれに対し必要な対策を弊社で判断し実施しております。実態の目視確認、ブラック・ホワイトドメイン配信機能や配信可能事業者の制限、各メディアに是正勧告など対策は多岐にわたりますが、検知、対処方法について不足があれば、エンジニアの協力を得ながら計測用の独自ツールを開発し、対策用の新しい機能を開発しながら進めています。

※1 アメリカのメディア調査会社の監査や認定審査を行う業界団体
※2 アドフラウド対策、ブランドセーフティ領域で世界最高水準の認定団体

 

終わりに

今回は現状GENIEEが行っている取り組みの一部をご紹介させていただきました。
しかしこれらが完璧な対策だとは考えておりません。アドフラウド対策は一度作って完成するものではなく、継続的な分析と改善を行い続けることでより強化していくべきだと考えております。
GENIEEでは日々新しい技術や機能が開発されています。今後新たな広告技術が開発されていけば、それらをターゲットにした新たな不正技術もまた生まれうることでしょう。
広告主にとってもメディアにとっても、より透明性の高いアドプラットフォームであるために、これからもQuality Assuranceチームはアドフラウドと闘い続けてまいります。

以上、アドフラウド対策の概要でございました。
ご覧くださりありがとうございました。

Date
Author
ジーニー広報

はじめに

こんにちは、R&D本部アドプラットフォーム開発部の加瀬です。

2018年も残すところあと1ヶ月となりましたが、みなさんいかがお過ごしでしょうか。
振り返ってみると「平成最後の年」ということもあってか、2018年も世間は話題に事欠かない年だったかと思います。

さてさて、アドテクの視点で2018年を振り返ってみると、テレビ番組などでも取り上げられていたように、アドフラウドがメイントピックとして取り上げられることが多かったかと思います。

アドフラウドを取り巻く状況

不正な広告掲載・配信に関しては昔からぼちぼちあり、例えば

  • 見えないページを設置したり
  • ボットに巡回させてトラフィックを水増しさせたり
  • 強制的にリダイレクトさせてみたり

いろいろありますが、先述した2018年の状況に伴い広く認知されるようになった気がします。

また、日本のデスクトップ(PC)におけるアドフラウド率が81%だとか、年100億円の被害だとか、どうも日本ではアドフラウドとなじみが深いのは数字としても出ているようです。
(数字の実際のところ、という観点はありますが。。。)

■参考:

ご多分に漏れず、弊社でもサプライ・デマンド問わず、アドフラウド・不正広告の被害に遭遇することがあります。

黙って指を咥えているわけではありません。

みなさんは「NO MORE 映画泥棒」をご存知でしょうか。
映画上映前のCMで、劇場内での映画の撮影・録音が犯罪である、ということを啓発するためのあれです。
こちらからキャッチコピーを一部拝借して、「アドフラウド・不正広告許さないぞ」と言う旗の下にアドフラウド・不正広告の調査・検知に日々勤しんでおります。

ということで、弊社での取り組みを簡単に頭出しの部分ではありますが、

  • メディア・ユーザー向けの取り組み
  • 広告主向けの取り組み

をご紹介します。

メディア・ユーザー向けの取り組み

メディア・ユーザー向けの技術的な取り組みとして、リダイレクト広告への対策を説明します。
遭遇した方もいらしゃるかもしれませんが、リダイレクト広告はWebページを閲覧中に強制的に意図しないページへの遷移をさせるものを指します。

そこで対策として

  • DSPから返却される広告タグの定期的な自動チェック
  • ブラウザ側での(ほぼ)リアルタイムチェック

を行なっています。

広告タグの定期的なチェック

悲しいことに、DSPからレスポンスされる広告タグが悪意のないものだとは言い切れなかったりします。
そこで

  • トラフィックの一部のレスポンスに含まれる広告タグの取得と保存
  • headless chromeを用いて上記のレンダリングを行い、リダイレクトのチェック

ということをシステム的に行なっています。
これにより、リアルタイムではありませんが強制リダイレクトが起きたDSPとその広告タグを特定し、停止作業を行うことにより被害を最小限に防ぐことができるようになります。

ブラウザ側での(ほぼ)リアルタイムチェック

なるべくリアルタイムで起こっているものを早くキャッチしたい、という要望に答えて「JavaScriptで強制リダイレクトの検知ができないか」という取り組みを行なっています。
JavaScriptにはページ離脱をしたときに発生するbeforeunloadなどのイベントがあり、それをevent listenerでハンドリングすることで検知できないかを試みました。
が、一筋縄ではいきませんでした。

ページ離脱にはいくつかパターンがあり、一例を挙げると

  • リンクのクリック
  • URL直接入力による遷移
  • 戻る/進むボタンによる遷移
  • ページのリロード

など「ユーザーの能動的なアクションによって発生するページ離脱をどのように判断するか」という点が課題としてありました。

結局、

  • click、history、focus、blurなどのイベントハンドリングを愚直に行いユーザーのアクションと切り分ける
  • 上記をすり抜けてきたものに関してログを書き、バッチで集計する

という結論にいたりました。

ある程度の誤検知は防げるようになったのですが、まだ一部把握しきれていないパターンがあるみたいなので、今後の課題として取り組みたいと考えています。

広告主向けの技術的取り組み

広告主向けの取り組みとして、テレビ番組でも取り上げられた「隠し裏広告」のほかに、名前が似ていますが「隠し広告」への対策も紹介させていただきます。

隠し広告の配信検知

実は昔からよくある手法でして、0x0サイズのiframeの中に別のウェブページを書き出したり、広告だけのページを作りそれを書き出したりするやり方が多いです。

この時

  • リファラーの偽造ぽいこと
  • 配信ページの偽造ぽいこと
  • トラフィックの不正水増し

が簡単にできてしまうというわけです。

著作権問題のあるサイトや、昔のまとめサイトでもよく見かけたお馴染みの手法だったりします。
手法の性質上、ユーザーの目に触れるとバレてしまうので、明らかにおかしなサイズのiframeの中に書き出します。
そのためJavaScriptでwindowサイズを取得し、適当に閾値を決めればある程度対応できる残念な手法だったりもします。

隠し裏広告の配信検知

隠し裏広告は、古くからあるポップアンダーと呼ばれる広告の配信方法の延長です。
結局のところ仕組みとしては

  1. 広告タグが呼び出される
  2. 広告タグのクリックURLを取得する
  3. ページ上のaタグのクリックイベントとして
    • location.href = 2のリンクを登録
    • window.open(正しいリンク)を登録

することにより

  • 前面に本来のリンク先
  • 裏面(もともとのwindow)に広告のリンク先

を表示させるものでした。

隠し裏広告は、ポップアンダーにおける広告のリンク先を別のメディアのURLにすることで

  • 前面に本来のリンク先
  • 裏面に別のメディアのURL

を表示することにより、トラフィックの融通や広告の不正表示を実現します。
つまり「ページが前面にあるかどうか」という点がポイントとなってきます。

都合のいいことにJavaScriptではdocument.visibilityStateで現在のページが「見える状態かどうか」を判定することができます。

なので

  • 広告タグの書き出し
  • document.visibilityStateで現在の状態を判定
  • hiddenの場合だとオークションに行かない、もしくは何かしらのビーコンを書き出す

とすることで、ある程度の正しくない状態における表示の検知/停止が可能になると考えています。

終わりに

以上、

  • アドフラウドを取り巻く現状
  • 弊社での取り組み

を簡単ではありますが紹介させていただきました。

改めて振り返ってみるとアドフラウドにも色々な種類があり、各々に対する対策もそれに従って生まれてきている状況だということが分かるかと思います。

現状はバナー広告のクリック課金によるものが多いのですが、今後、特に動画のような高単価かつ視聴による課金を求めるようなものに関しては、不正なインプレッションが発生するだけで更に大きな被害になることが予想されるので、引き続き検証と対策を行なっていきます。

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

Date
Author
エンジニアの視点から、様々な技術、サービス開発秘話、イベントをご紹介していきます。 ジーニーエンジニアチーム

はじめに

こんにちは、R&D本部アドプラットフォーム開発部の村岡です。

九州工業大学の先端情報工学専攻を予定通り修了してジーニーに17卒入社し、現在は主にGenieeSSPの開発を行っています。

以前こちらの記事を書きましたが、今回もMySQL関連の記事となります。

GenieeSSPについて

GenieeSSPは、広告配信のレスポンスタイムを短くするために、数十万の広告枠の配信設定をすべてインメモリで保持しています。

全広告枠の配信設定はMySQLに保存されています。配信設定を変更する、つまりDBのデータを変更する方法は、現在の運用では4つあります。

  • 営業担当や、広告運用チームなどが操作画面を使って更新する。
  • 操作画面では対応できない場合などに、エンジニアの運用チームが手作業で更新する。
  • 配信パラメータ最適化のためのバッチが更新する。
  • リリース時などにエンジニアが権限をもらって更新する(平時は更新する権限がない)。

また、GenieeSSPは広告枠単位で配信設定をキャッシュしており、広告枠に紐づく何かしらの情報が変更となった場合は、その広告枠の情報をすべて再取得しキャッシュを更新します。

以上の事情から、GenieeSSP配信サーバが使用するMySQLのテーブルにはトリガーが設定されており、あるテーブルのレコードが変更された場合に、どの広告枠が変更となったか、わかるようになっています。

数十台あるGenieeSSP配信サーバの全てが、全広告枠の配信情報をインメモリキャッシュとして保持しており、各サーバが定期的にrefresh_counterテーブルのカウンターが更新されているか調べるクエリを投げ、どの広告枠を更新すべきか調べています。

GenieeSSPの課題

このようなキャッシュ更新の仕組みでは、ある広告枠の配信設定がひとつだけ変更となった際に、紐づく配信設定全てを再取得しなおさなければならない、という問題があります。 配信サーバが使用するテーブルは60程度あり、一つの広告枠キャッシュを作成するために数百のクエリを投げなければなりません。

このキャッシュ更新の処理が重く、バッチによる大量更新が行われた際には、最悪2時間程度の間、配信設定が配信サーバに反映されないままになってしまう、という深刻な問題がありました。

今回、DBに配信設定が保存されてから、配信サーバに反映されるまで5分以内にする、という目標のもと、GenieeSSPの長年の課題であったこの配信設定反映遅すぎ問題を解決しました。

実装案

設計段階で出た実装案について、各案についてどのように実装しようとしていたか、なぜその案が採用されなかったか、最終的に選ばれた実装案はなぜ採用されたかについて説明します。

実装案を考えるときに重要だったポイントは、

  • テストが行えるような方法であること
  • 既存のテーブルのトリガーを使用しないような方法であること
  • 既存の運用方法で配信設定の反映が行われること

でした。

テストには、GenieeSSPが定期的にJSON形式でファイルに書き出す、全広告枠の配信情報が使えます。 これは、GenieeSSPがクラッシュしたとき、JSONファイルがないと起動に二時間かかってしまうため、JSONから配信情報をロードすることで起動時間を短縮しています。 高速化後の実装と古い実装で、このJSONファイルを出力させ比較すれば容易にテストが行えます。

案1:updated時間を見る

この実装案は、一つの広告枠の情報を取るのに数百のクエリを投げたくないなら、テーブルデータまるごと落としてしまえ、というかなり力技な案です。

まず、前準備として、DBの各テーブルに更新時間を記録するカラムを追加します。カラムの更新時間は自動で更新されるように、以下のように定義します。

---------------------
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
---------------------

GenieeSSPが起動するとき、配信に必要な約60のテーブルデータを全て取得します。 テーブルデータを全てメモリに載せても10GBも使用しないため、今後、データが増えることを考えてもメモリ使用量としては全く問題になりません。

そして、キャッシュした各テーブルデータをもとに広告枠情報を構築するようにします。

キャッシュ更新は、以下のようなクエリを定期的に投げて、各テーブルに追加したupdated_atカラムを見るようにします。

---------------------
SELECT * FROM table WHERE updated_at > "最後のポーリング時刻";
---------------------

updated_atカラムの時刻が、以前更新したときの時刻から更新されていれば、データが変更されたとわかります。 更新されたレコードから、キャッシュの再構築が必要な広告枠だけ、再構築を行うようにします。

この実装案は仕組み自体はとてもシンプルですが、採用されずお蔵入りとなりました。 理由は、テーブルのレコード削除がわからない、という致命的な問題があったためです。

案2:操作画面変更時に配信サーバに通知する

この実装案は、操作画面で配信設定が変更されたら、配信画面側でJSONファイルを生成し、RedisかAerospikeに保存する、という案です。

GenieeSSPは、定期的に保存場所をポーリングし、更新された広告枠についてJSONをダウンロードしデシリアライズしてキャッシュを更新します。

GenieeSSP側の変更はほぼ必要なく、操作画面側でJSONを作る処理を追加する必要があります。

この実装案が没となった理由は、操作画面以外からDBを更新された場合に対応できない、操作画面のコードの保守が大変になってしまうためです。

案3:MySQLのBinlogを使う

この実装案は、案1をベースに、DB更新の検知をMySQLが出力するbinary log(binlog)を見るようにする、というものです。

まず、起動時に約60のテーブルデータ全てを、通常のSELECTクエリで取得します。 その後、MySQLサーバにスレーブとして接続し送られてくるbinlogを処理し、インメモリのテーブルデータを更新していきます。 MySQLにクエリを投げるのは起動時のみ、あとは送られてくるbinlogを処理するだけです。

binlogをハンドリングするためのライブラリとして、MySQL Labsで公開されているlibbinlogeventsを使用します。

案1と同様に、更新されたレコードから、キャッシュの再構築が必要な広告枠を計算し、キャッシュの再構築を行うようにします。

この実装案は、experimentalなライブラリであり、社内社外ともに実績があまりないlibbinlogeventsを使用するため、本当にうまくいくのか疑問ではありましたが、以下の点でメリットがあり採用に至りました。

  • 他の案と異なり既存の運用にも対応できること
  • 配信設定の反映時間は最速が狙えること
  • 変更がpushされるため、ポーリングするより効率的
  • 何より純粋にbinlogを使う実装が面白そう

実装

GenieeSSP配信サーバが、experimentalなライブラリを使用してMySQLサーバにスレーブとして接続するのはあまりにもリスキーです。何かあったときに、配信サーバが全滅しかねません。 GenieeSSPは幸いにも、JSONファイルから配信設定をロードする機能があるため、これを利用することにします。

具体的には、MySQLサーバにスレーブとして接続しbinlogを処理、JSONファイルを生成する配信キャッシュ作成サーバを開発します。

DBのレコードが更新されると、以下のような流れで配信設定が反映されるように実装を行います。

現状では、更新されていない広告枠も含む、全ての広告枠情報をJSONに出力しています。 これにより、テストが容易になる、実装が容易になる利点があります。 ただし、配信キャッシュ作成サーバから、JSONを各GenieeSSP配信サーバに転送するときはサイズが大きいため時間がかかってしまいます。

Zstandardで圧縮することでファイルサイズを98%削減できましたが、プロダクション環境ではZstandardが新しすぎるためにパッケージがない・バージョンが古く、自前でビルドしなければならなかったため、一旦LZ4で圧縮しています。 LZ4の方が少し処理時間は長く、ファイルサイズを94%削減できています。

配信キャッシュ作成サーバ

クラスをJSONでシリアライズする部分など、流用できる/流用したいコードが多くあること、libbinlogeventsがC++だったことなどから、配信キャッシュ作成サーバもC++17で開発しました。

LL言語のほうが良いんじゃないか、とも言われましたが、

  • libbinlogeventsのバインディングを書くことがめんどくさい
  • 大量にメモリを使うのでマークスイープ系のGC走ると止まる時間長そう
  • シリアライズ部分とか並列化したいけどプロセス並列やりたくない
  • そもそも速度が最重要なのでC++でいいじゃん

という理由でC++です。 個人的にはRustを使いたかった気持ちもありますが、Rustは別の仕事や趣味で使うことにします。

処理フロー

約60あるテーブルデータを保持するクラスを手で書くのは流石に大変なので(一度手で書こうとして止められた)、コードジェネレータを作りました(後述)。

GenieeSSPから流用したコードは”広告枠の情報を再構築、再シリアライズ”する部分となります。 当然ですが、GenieeSSPが投げていたSQLクエリは流用できないので、全てC++で書き直して実装しました。

C++で書き直したので何をしているのかわかりづらい…と思うかもしれませんが、元のSQLクエリが既に意味不明な部分も多かったため、全体としてそれほど読みにくくなってはいないように思います…が、SQLクエリのままのほうがやはり簡潔で良いと思います。

コードの生成

キャッシュ生成に必要なテーブルのテーブル名、及び、必要なカラム名などを記述したJSONファイルをもとに、MySQLサーバにSHOW CREATE TABLEクエリを投げてカラムの型情報、カラムインデックスなどを取得し、C++のコードを生成するコードジェネレータが必要となり作りました。

コードジェネレータを作ることで、あとからデバッグのための関数追加、コードの修正などが容易に行えるようになり開発がスムーズに進みます。

また、binlogからカラムデータ取得、MySQLにクエリを投げてデータを取得するコードは、JSONファイルにテーブル名、カラム名を追加しコードジェネレータを叩くだけで全て生成されるため、メンテナンスも容易になります。

今回はJinja2テンプレートエンジンを使いましたが、扱いやすく、テンプレート中で制御構文など使えて簡潔にテンプレートを書くことができました。

binlogのハンドリング

大真面目に実装していけばMySQLが作れます。

今回はその必要はないので、以下のイベントのみハンドリングします。

  • TABLE_MAP_EVENT
  • XID_EVENT
  • UPDATE_ROWS_EVENT
  • WRITE_ROWS_EVENT
  • DELETE_ROWS_EVENT

TABLE_MAP_EVENTは、ROWベースのbinlogにおいて、全ての行操作イベントの前に表定義とその後のイベントの値をマップするイベントです。 このイベントを見ることで、DB名、テーブル名、カラム定義などがわかります。

XID_EVENTは、テーブルの変更をするトランザクション処理がCOMMITされると発生するイベントです。 このイベントを見て、中途半端なところでbinlogを処理しないようにする必要があります。

UPDATE_ROWS_EVENTWRITE_ROWS_EVENTDELETE_ROWS_EVENTはそれぞれ、レコードがUPDATEINSERTDELETE時に発生するイベントです。

おそらく、QUERY_EVENTをハンドリングすることでテーブルのALTERなどもわかるはずですが、試したことはないです。 配信キャッシュ作成サーバでは、テーブルをALTERするときは新しいテーブル定義を使ってコード生成しなおし、再リリースすることにしています。 テーブルのALTERはめったに行わないため、この運用で問題ありません。

スレーブとしてMySQLサーバに接続したあと、binlogのポジションを設定する必要があります。 binlogポジションはSHOW MASTER STATUSコマンドを投げることでわかります。 SELECTクエリでテーブルデータを取得したあとは、binlogによる更新となるのでbinlogポジションはちゃんと設定してあげなければデータの整合性がとれなくなります。

今回は、MySQLサーバにSTOP SLAVEして、テーブルデータとbinlogポジションを取得、MySQLサーバにスレーブとして接続してからSTART SLAVEするようにしています。 そのため、配信キャッシュ作成サーバ専用のMySQLサーバを動かしています。

libbinlogevents ver1.0.2を使用しましたが、このライブラリには重大な問題があります。 それは、

  • Decimal型のカラムの値を正しく取得できないこと
  • DateTime型のカラムの値を正しく取得できないことがあること

です。

これらの問題は、libbinlogeventsにそれらのデータを処理するコードがないことが原因です。 この問題を修正するには、MySQLのソースコードを漁って、MySQLがbinaryフォーマットからdoubleDateTimeに変換している部分のコードを移植してやる必要があります。

どちらもすぐに処理を行っているコードは見つかります。 MySQL本体とべったり書かれているわけではないので、移植もわりとやりやすいと思います。

特にDecimalについては、内部でどういう風に扱われているのか全く知らなかったため、MySQLのソースコードとコメントを読むのは楽しかったです。

テスト

テスト環境に配信キャッシュ作成サーバ専用のMySQLサーバを置きます。 このサーバは本番サーバとレプリケーションするようにしておき、このMySQLサーバに古いGenieeSSPと配信キャッシュ作成サーバを接続し、一定時間ごとにレプリケーションを止めて、生成されたJSONを比較します。

JSONの比較のためのツールをPythonで書きましたが、やたらとメモリ食いで40〜50GB程度使っていました。 しかもGCが走ると数十秒平気で止まるので、なかなかテストが進みません…仕方ないですね。

2〜3週間程度、自動でテストが動くようにして、毎日差分をチェック、問題があれば調べて修正を繰り返しました。

最後の一週間は問題のある差は出なかったため、プロダクション環境に一台だけリリースして一週間程度様子を見てから全台リリースを行いました。

リリース後

配信設定反映まで、最悪2時間かかっていましたがリリース後は約3分程度となっています。 3分の内訳は、

  • Binlog処理・JSONシリアライズ…1秒
  • 全広告枠分のJSONをRamdiskに書き出し…5秒
  • LZ4で圧縮…30秒
  • 転送…30秒
  • 配信サーバのJSONロード…平時10秒程度
  • 残りは、ファイルの転送はcronで毎分行われているためそのディレイと、配信サーバが新しいJSONファイルを見つけるまでのディレイ

となっています。

binlog周りで問題が発生しても、MySQLに通常のクエリを投げてテーブルデータを全件取得するのにかかる時間は1分程度なので、毎回全件取得で更新できるようにもしています。 この場合は更新に少し時間がかかるようになりますが、5分以内更新は達成できます。

上図はリリース後のMySQLサーバの負荷です。データの転送量、CPU負荷が格段に下がっています。

MySQL Enterprise Monitorで見られる、受け付けたクエリの統計です。 リリース後は、赤線のSELECTクエリが圧倒的に減っています。

上図は配信キャッシュ作成サーバの負荷です。配信キャッシュ作成サーバのCPU負荷は高くありません。 定期的に走るバッチによる大量更新でもそれほど負荷は上がらず、更新できています。

ただし、テーブルの全データに加え、数十万の広告枠のシリアライズ済みのJSONデータを保持しているため、メモリ使用量は多く25GB程度使っています。 25GBに、数GBのJSONファイルをRamdiskに保存するため、場合によってはメモリ64GBのサーバでもカツカツだったりします。

ネットワーク帯域もかなり消費していますが、これは今後、差分更新の仕組みなどを入れることでかなり改善すると思われます。

おわりに

9月頃に設計を開始し、libbinlogeventsが使えるかの検証、コーディング、テスト、ドキュメントなども書いたりしていたら、リリースが12月になってしまいました。

とりあえずリリースしてから、二週間経っていますが、大きな障害は起きておらず快調に動いています。

お客様等からの声はまだほとんど届いていないのでわかりませんが、今後開発を行っていき、広告配信のパラメータを高頻度に更新していける環境ができあがったと思っています。 今まで、この性能の問題から更新頻度が低く抑えられていたものなど頻度を上げてパラメータを最適化していければ、メディア様の収益もあげていけるでしょう。

とりあえず、二ヶ月程度様子を見て、テーブルのトリガー削除や、旧GenieeSSPの不要コードの完全削除などを行う予定です。 その後は、差分更新の仕組みを入れてより高速に配信設定を反映できるようにしていきたいと思っています。

正直、タイトルの”binlogを使って”は高速化のキモではなく、MySQLにクエリを投げる回数を減らしてC++で処理するようになったら速くなった、という感じではあります。 ただ、MySQLのbinlogを使う、という経験はなかなかないと思うので、これはこれで楽しい経験と思っています。

libbinlogeventsを使う上で、以下の2つの記事には大変助けられました。この場を借りてお礼申し上げます。ありがとうございました。

qiita.com


www.slideshare.net

また、開発時、リリース時、モニタリングの仕組み構築など私のメンターの伊藤さんには大変助けて頂きました。この場を借りてお礼申し上げます。ありがとうございました。

Date
Author
エンジニアの視点から、様々な技術、サービス開発秘話、イベントをご紹介していきます。 ジーニーエンジニアチーム

こんにちは、人事の見並です。前職で6年システム開発の現場に携わり、現職ではキャリアチェンジをして4年人事をしています。今回は、ジーニーのエンジニアが登壇した勉強会を2つレポートします。

はじめに

勉強会のレポートをする前に、ジーニーの事業全体を簡単にご紹介いたします。

ジーニーの事業全体図

アド・プラットフォーム事業

創業から事業の根幹を作っていったのが、広告配信を行っているアド・プラットフォーム事業です。1秒間に数十万回ものリクエストをリアルタイムに捌き、最適な広告配信を実現しています。

マーケティングオートメーション事業

マーケティングオートメーション事業は昨年度から開始した新規事業です。多様化するマーケティング活動を最適な形で自動化し、データドリブンな取り組みを実現させるためのプラットフォームを目指しています。

これらの両プロダクトをインフラからアプリケーションレイヤーまで、60名以上のエンジニアが内製で構築しています。

アド・プラットフォーム事業の登壇レポート

LINE社が定期的に開催している技術者向けミートアップ「LINE Developer Meetup」へ、CTOの篠塚が登壇しました。「広告技術」をテーマとした回に、Supership社やLINE社の方々と共に、アドテクノロジー領域でのコアな取り組みを発表しました。

篠塚は「世界分散配信システムとレポーティングシステム刷新のお話」と題し、一度大きな作り直しを経験した後、現在はアジアを中心に国内外で大規模なトラフィックを捌くまでに成長したGenieeSSPのアーキテクチャ>やその変遷、直近取り組んでいるチャレンジなどをお話ししました。


海外に置いているサーバとの通信やレプリケーションの実現方法、事業の急拡大に伴う技術課題へ取り組んだ事例などに対して質問も多くいただきました。

マーケティングオートメーション事業の登壇レポート

オプト社にて開催された「マーケティング事業の開発現場でリアルに使われるJS事情」と題した勉強会にて、マーケティングオートメーション開発部の若手エンジニア2名が登壇しました。

近年活用が広がっているAngular.js , Vue.jsといったJavaScriptフレームワークの実際の活用事例を発表する形で、ウィルゲート社やオプト社の方々と登壇させていただきました。

「MAJINを支えるフロントエンド技術」というタイトルで、MAJINのアプリケーション開発で活用をしているVue.jsの開発事例を発表しました。

現在のアーキテクチャ>に至るまでの変遷や背景、選定理由など開発現場で様々な課題に直面したエンジニアとしてのナレッジを共有できたように思います。


今後も積極的に発表いたします!

社内で取り組んできた事例や新しく取り入れた技術など、今後も積極的に発表する機会を作っていきたいと考えていますので、ぜひ共同開催などお声がけいただけると嬉しいです!

Date
Author
エンジニアの視点から、様々な技術、サービス開発秘話、イベントをご紹介していきます。 ジーニーエンジニアチーム

こんにちは、人事の見並です。今回はエンジニアブログの方に、ジーニーのエンジニアが登壇した2つの勉強会レポートを掲載いたしましたのでお知らせいたします。

登壇にて使用したスライドも記事中に埋め込んでおりますので、是非ご覧ください。

エンジニアブログの記事はコチラから。

Date
Author
社風やジーニーでの働き方などをご紹介していきます。 ジーニー人事チーム

こんにちは。R&D本部マーケティングオートメーション開発部の杉野です。

今回はMAJINのHTTPS対応についてお話していこうと思います。

HTTPS化のトレンド

昨今はWebのHTTPS化が強いトレンドとなっており、企業が運営するWebサイトのHTTPS化が進んできております。 最近のブラウザベンダの施策により、WebサイトをHTTPS化しない場合、以下のようなデメリットが発生することとなります。

  • ChromeやFirefoxといった大手ブラウザは、HTTPSでないページにある入力フォームに対して警告を表示する*1*2
  • GoogleはHTTPSで暗号化したサイトの掲載順位を引き上げることを公表*3している
  • HTTPSでないWebサイトからは、HTTPSで暗号化されたサイトのリファラを取得できない
    • リファラはお客様のページ遷移を知り広告効果などを測定するために用いられています
  • 次世代の高速通信規格であるHTTP/2は、現在対応している全てのブラウザでHTTPSの場合のみをサポートする

もちろんこれらのデメリットがあることは別としても、MAJINはお客様の情報を参考にマーケティングの最適化を行う都合上、暗号化を通さずに通信するべきでない内容を取り扱っております。 そのため、通信内容を暗号化しておくことは重要な責務だと認識して対応を行っています。

MAJINタグにおけるHTTPS設定

証明書

MAJINの各種機能を利用するために使用するMAJINタグは、デフォルトでHTTPSで通信するように設定されています。 MAJINのHTTPS通信に使われている証明書はブラウザで確認することができますので見てみましょう。

ご覧の通りこの証明書の発行元はAmazonとなっております。

MAJINのサービスはAWS上で動いており、この証明書はAWSのサービスの1つである証明書取得サービスで取得・設定したものです。 当然ながら有効な証明書となっていますので、MAJINタグはHTTPSによる通信で取得することが可能です。

正しく強度が出るHTTPSの設定

HTTPSは単純に証明書を設置してデフォルトの設定で運用するだけでは、セキュリティ上の問題が残る場合があります。

例えばプロトコルのSSL3.0を有効にしたままでは、POODLEという攻撃手法のターゲットとなります*4

また、RC4などの仕様上存在する弱い暗号を有効にしたままでは、暗号を解読され通信内容を盗み見られる可能性もあります*5

このような設定は知識がないと正しく行うことが難しいのですが、 Firefoxの開発元であるMozillaが、サーバ毎に最適な設定を行えるツールを公開している他、 Qualys SSL Labsでは、HTTPSの設定をしたページのセキュリティレベルを診断するツールを公開して います。

こちらの診断ツールでMAJINタグのセキュリティレベルを調査した結果が以下となります。

十分なレベルを確保していることがおわかりいただけるかと思います。

HTTPS化が必須なサービス

また最近では、HTTPS化が前提の先進的サービスも増えてきています。

最初に説明した通り、高速通信規格であるHTTP/2はHTTPSでなければ有効化できません。 また、MAJINで提供しているサービスの1つで使用しているWebPushも、そのようなサービスの1つです。

WebPushは、スマートフォンアプリで使用されている「Push通知」を、Webブラウザでもで行えるようにするものです。 アプリ開発なしにPush通知を送信できるということで、新機能ながらお客様に好評を頂いているのですが、こちらの機能は導入に際してページのHTTPS化が必須となっています。上記のとおりMAJINはHTTPS化してあるため問題ないのですが、導入先のページもHTTPS化する必要があります。

元々HTTPS化してあるサイトであるか、もしくはHTTPS化の予定があるサイトであれば問題なく導入でき、こちらの方がオプションが豊富なのですが、 中にはどうしても導入サイトのHTTPS化はできないが、WebPushを導入したいというお客様もいらっしゃいます。

そういった場合には、MAJINでWebPush登録専用のHTTPS化されたページを設定し、お客様のサイトからはこのページに誘導するリンクを設置することで、MAJINが管理するページでWebPush登録を代行する、という回避策を取っています。

まとめ

MAJINでは、お客様の情報を守るため、および先進的なサービスを提供するために、積極的なHTTPSの導入を行っております。情報セキュリティ分野は日々情報が更新されるため、現在MAJINに施している設定も半年後には古いものになっているかもしれません。

そのような中でも最新の対策を適用し、安心して情報を任せていただけるようなサービスを提供し続けられるよう、日々努力しています。

Date
Author
エンジニアの視点から、様々な技術、サービス開発秘話、イベントをご紹介していきます。 ジーニーエンジニアチーム

こんにちは。R&D本部経営情報システム開発部の友安です。

経営情報システム開発部では、社内システム・プロダクト横断でのお金周りの開発や監査対応、社内のコミュニケーション効率化など、多岐にわたり社内の問題解決に努めています。

Redash

ジーニーでは広告配信成果の分析、定形のデータ抽出作業の自動化にOSSのBIツールであるRedashを使っています。

既に各所で紹介されていますが、Redashは様々なデータソースに対するクエリを作成し、それらと実行結果を共有できるWebアプリケーションです。以前の記事で紹介したclickhouseもデータソースとして使えます。クエリの実行結果をチャートやピボットテーブルにするビジュアライゼーションの機能も豊富です。

導入によって、データ分析の促進や定形のデータ抽出作業の効率化といった効果が出ています。

また、クエリにコメントをつける機能でクエリの意図(なぜこのようなクエリを書いたか・このテーブルは何かなど)を記載することによって、他チームの業務やテーブル設計のナレッジ共有にも繋がっています。

潜在的な要望への気づき

Redashの存在が社内でじわじわと広がると、「実はこれを一括で見られると嬉しい」とか「こういうデータを裏で持っているなら見たかった」といった要望が次々と出てきました。

今はクエリを書く非エンジニアのメンバーも現れてくれて、データを欲しい人がデータを出すということが増えました。

カスタマイズ

利用が増えると、「こうだったら良いのに」という要望が徐々に出てくるようになりました。 RedashはOSSなので、自分たちで実装することにしました。
実装した機能を以下で少し紹介します。

スケジューリング強化

Redashには、クエリの実行結果の更新スケジュールを設定する機能があり大変便利なのですが、 より細かいスケジューリングをしたいという要望が出てきました。 Redashの更新スケジュールは、毎日X時更新や7日間ごとといった単位で設定します。 しかし、週2回更新してモニタリングしたいなど、 Redashの既存のスケジューリング機能では対応できない要望がありました。 そのような細かいスケジューリングに対応するため、月・木曜日更新や月初更新などの設定ができるようにしました。

この機能は本家でプルリク が出ていたのですが、開発が止まっていました。

そちらを元に実装しました。

クエリの実行結果フィルタの全選択/解除

Redashにはクエリの実行結果をフィルターする機能があります。
フィルタする値を選択する際に、全選択/解除のオプションを使えるようにしました。 やや小さめの改修ですが、このような細かいこともやっているという紹介でした。

[WIP] Slack通知

最後に、先日作り始めた機能を紹介します。 弊社では、コミュニケーションツールにslackを採用しています。

多くのメンバーが頻繁にslackを利用しているため、slackとRedashが連携できれば、よりデータ活用を促進できる可能性がありました。

公式のslackbotがありますが、弊社では閉域網でRedashサーバを運用しているため、slackbot→ Redashサーバへのリクエストが届きません。

そこで、Redashでデータ更新があったときにRedashサーバからslackチャンネルに通知する設定をするSubscribe機能を実装することにしました。

導入の経緯

私は月初になると「経理作業用のクエリを実行してはせっせとエクセルに貼り付けてメールで各所に送る」という作業を引き継ぎで行っていました。この作業をツールに任せたいというのがきっかけでした。

分析のためのデータ抽出依頼も頻繁に発生していたので、その解決にも繋がるのではと思いました(こちらの方がインパクトは大きい)。自分のマシンでマネージャーにデモをすると導入を即決。やるぞと思っているうちに、既にマネージャーが構築して使い始めていました。

おわりに

Redashは Angular、Python Flaskで開発されているSPAです。私は業務ではPHPで社内システムの開発をしていることが多いので、普段業務で使うものとは異なる技術に触れて刺激になりました。

個人的には、SPA開発のキャッチアップが出来た点が収穫でした。

Redashには大変お世話になっているので、社内で好評かつ汎用性の高い機能についてはプルリクを出して本家にコントリビュートしたいところです。

Date
Author
エンジニアの視点から、様々な技術、サービス開発秘話、イベントをご紹介していきます。 ジーニーエンジニアチーム

はじめに

こんにちは、R&D本部アドプラットフォーム開発部の村岡です。

私は、ジーニーでGenieeSSPの開発を主に行っています。

今回、私が入社前のインターンで行った開発内容について紹介したいと思います。

GenieeSSP

SSPは、ブラウザなどからのリクエストを処理し、レスポンスとして広告を返します。 当然ですが、メディアごとにサーバを用意するわけではないため、複数のメディアから来る膨大な広告リクエストを、短時間のうちに処理し広告を返す必要があります。

リクエストがきてから、レスポンスを返せるまでの時間は短ければ短いほどよいのですが、膨大なリクエスト数となるため、システムのパフォーマンスが非常に重要になってきます。

GenieeSSPはそういったSSP特有のパフォーマンス要求のため、C++で開発されており、現在16台のサーバで全リクエストを処理しています。

GenieeSSPでは、広告枠の情報などはMySQLに保存されていますが、広告リクエストごとにDBに枠情報を問い合わせていたのでは、広告を返すまでのレスポンスタイムが長くなってしまうため、全ての枠情報はオンメモリで保持されています。 これらの広告枠情報は、更新のあった枠に対してだけ一定時間ごとにDBにクエリを投げ、メモリ上の枠情報を更新するようになっています。

また、GenieeSSPはlibmysqlclientを直接使用してクエリを投げています。

課題

私がインターンとして入ったとき、GenieeSSPは1プロセスで使用するMySQLコネクション数が多すぎるという問題を抱えていました。

システムの機能拡張を繰り返した結果、MySQLからデータをフェッチする順序が複雑に絡まり、一本のMySQLコネクションでクエリを投げることが困難な状態にありました。

GenieeSSPでは、プリペアドステートメントを使用して全てのクエリを投げています。

この場合、MySQLでデータをフェッチするためには下図の順序でライブラリ関数を呼び出す必要があります。 この順序を守らないと、CR_COMMANDS_OUT_OF_SYNCエラーとなってしまいます。

GenieeSSPにおいて、プロセスが大量のMySQLコネクションを使用する原因は、mysql_stmt_fetch関数を呼び出した後、別のMySQLコネクションとプリペアドステートメントでmysql_stmt_execute関数を呼び出すことがあるためです。

一本のMySQLコネクションで、CR_COMMANDS_OUT_OF_SYNCエラーを回避するためには、上図のようにmysql_stmt_execute関数を呼び出したあとはMYSQL_NO_DATAとなるまでmysql_stmt_fetch関数を繰り返し呼び出す必要があります。

調査

ライブラリ関数の呼び出し順序を調査するため、下記のコードを使います。

———————————

static int
mysql_stmt_execute0(MYSQL_STMT *stmt, const char *func, int line)
{
    std::cerr << func <<  ":" << line << " execute" << std::endl;
    return mysql_stmt_execute(stmt);
}
#define mysql_stmt_execute(stmt) mysql_stmt_execute0((stmt), __func__, __LINE__)

static int
mysql_stmt_fetch0(MYSQL_STMT *stmt, const char *func, int line)
{
    std::cerr << func <<  ":" << line << " fetch";
    int ret = mysql_stmt_fetch(stmt);
    if (ret == MYSQL_NO_DATA)
    {
        std::cerr << " done";
    }
    std::cerr << std::endl;
    return ret;
}
#define mysql_stmt_fetch(stmt) mysql_stmt_fetch0((stmt), __func__, __LINE__)

———————————
このコードを挿入し、テスト環境で実行すると標準エラー出力にmysql_stmt_execute関数とmysql_stmt_fetch関数を呼び出した関数名と 呼び出し箇所のソースコードの行数が出力されます。 この出力を元に、呼び出し順序が正しいか調査します、が、呼び出し順が間違っていれば実行途中でエラーとなるため、直前の出力を調べれば原因は大体わかります。

原因がわかった後は、正しいライブラリ関数の呼び出し順序となるようにソースコードを修正するだけです。

修正後

修正後、副次的な効果として全ての広告枠情報を取得する時間が2割減となりました。

また、コネクション数が減ったため、MySQLサーバも安定して動作するようになったような気がします。

おわりに

今回、色々あってMySQL接続数を減らすことになりましたが、日々機能拡張が続くソフトウェアをメンテナンスしていくのは難しく、時間が取れず改善課題は溜まる一方です。

当記事を読んで、弊社とSSP開発に興味を持って頂ければ幸いです。

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

Date
Author
エンジニアの視点から、様々な技術、サービス開発秘話、イベントをご紹介していきます。 ジーニーエンジニアチーム

こんにちは、人事の見並です。
ジーニーは、社員の35%がエンジニアで、SSPやDSP、マーケティングオートメーションといったプロダクトを全て独自開発しています。
今回は、そんなジーニーの心臓部とも言える開発現場の日常ををお届けします。

執務エリアの全体感はこんな様子です。間仕切りなどなく、オープンでフラットなフロアでエンジニアリングに皆さん勤しんでいます。
写真の中でプカプカ浮いている風船は、ここ半年くらいで入られた方々の名前が書かれています。(こちらは、入社歓迎の気持ちを込めて、1つひとつ人事が準備しています)
新卒・中途ともに、続々とジョインされているのが風船からもわかります。

それでは早速、最近どんなシステム開発を進めているのかレポートします!

まずはGenieeSSPの島から

まずは、ジーニーの基幹プロダクトであるGenieeSSPを開発している島へやってまいりました。
GenieeSSPは、様々なオンラインメディアやアプリへ広告を配信し、そのマネタイズをサポートするプロダクトです。多くのトラフィックを捌くバックエンド側と、メディアの方々に提供するWEBシステム側とに大別されます。

当たり前ですが、私が入社した頃(4年前)と比べると、大きくシステム環境が変わっています。使っている言語のバージョンアップ準備が進んでいたり、提供するものも、WEBシステム、広告配信のバックエンド、アプリ向けのSDKと多岐に渡り、業務を通してエンジニアリングの幅が広げられそうな環境となっていました。

開発言語やミドルウェアなど、「こんなのも使っているんですか?」と色々聞いていたら、あれやこれやと周りのエンジニアの方々が回答してくれて、和気藹々とした雰囲気でした。集中していたところお邪魔いたしました。

続いてGenieeDSPの島へ

次は、GenieeDSPを開発している島へやってまいりました。
GenieeDSPは、GenieeSSPが扱う広告枠を中心に、届けたいユーザーをターゲティングし、広告を配信するプロダクトです。
こちらも多くのリクエストを受け付けて広告を配信するバックエンド側と、広告主の方々に提供するWEBシステム側とに大別されます。

GenieeDSPは、私が入社した後、2014年3月にできたプロダクトです。当時は今よりずっと少ない人数のエンジニアで、とても泥臭く立ち上げが行われていました。
今では、GenieeSSPに続く収益柱へと成長してきており、昔を思うと本当に作り上げてきたエンジニアの方々に拍手です。
また、WEBシステム側はサーバサイドの開発言語をPHPからJavaに移行し、UI/UXの改善も社内で進行していて、ますますこれからが楽しみなプロダクトです。

最後はMAJINの島へ

最後は、ジーニーで最も新しいプロダクトであるマーケティングオートメーションツールのMAJINを開発している島へやってまいりました。
MAJINは、集客施策から販促活動、顧客管理までを一気通貫で担い、データもひと繋ぎに活用してマーケティング活動に必要な機能をオールインワンで提供しています。
新機能の開発スピードも非常に早く、LINE連携機能の追加や、機械学習を用いた新機能の研究開発も進んでいます。

これまで取り組んできたアドテクノロジーからマーケティングテクノロジーへ拡張した、新たなプロダクトということもあり、使っている技術環境も毛色が異なります。
モダンな技術を使いながらも、私達の基幹事業であるアドテクノロジー分野とのデータ連携もしっかりと図っています。
生まれてまだ1年の若いプロダクトですが、こちらもUI/UXをブラッシュアップしたり、目に見えない基盤部分もガンガン改善していっています。
最近は、エンジニア発信で気づいたら機能が追加されていたり、改善がなされていたりと、自社内の出来事ながらビックリすることがあります。

お客様へ提供しているプロダクト開発部門以外に、事業サイドやコーポレートサイドとプロダクトを繋ぐ経営情報システム開発の部門や、プロダクトへ横断的に関わるアーキテクチャやインフラを担う基盤技術開発の部門などがあり、みんなで一丸となって事業の成長を支えています。

その他、執務エリアの隣にはこんなスペースも設けています。

休憩エリアです。ちょっと気分を変えて開発をしたり、コーヒーを飲みながら息抜きをしたりしています。

研究開発の一環としてVRエリアも!私は生まれて初めてここでVRを体験しましたが、なかなか衝撃的でした。

よりリアルな開発現場を感じていただけるよう、先月からエンジニアブログを開始しています。

GENIEEエンジニアブログ

是非こちらも見てみてください!

いかがでしたか?
もっと具体的な開発言語や使っているツールなども知りたいと思った方、ジーニーの開発にご興味のわいた方は、是非こちらから!

ジーニー採用サイト

弊社へ遊びにきていただけましたら、ざっくばらんにお話しできますので、是非気軽にいらしてください!

Date
Author
社風やジーニーでの働き方などをご紹介していきます。 ジーニー人事チーム

こんにちは。R&D本部・経営情報システム部の友安です。

経営情報システム部では社内システム・プロダクト横断でのお金周りの開発や監査対応、
社内のコミュニケーション効率化など、多岐にわたり社内の問題解決に努めています。

社内の権限管理

組織が大きくなってくると色々と統制が求められるようになります。 統制の1つに権限管理の強化が挙げられます。 しかし管理の強化には運用コストが伴います。そのため、より小さな運用コストで実現することは重要なテーマになります。

今回は商用版MySQLの権限管理に便利なPAM認証プラグインの機能を紹介します。

MySQL×LDAP認証

やりたかったこと

  • 運用時やリリース時に利用していた共用のMySQLユーザを廃止し、DB上の権限をメンバーごとに管理する。
  • それを低い運用コストで実現したい。
  • 弊社はサーバへのアクセス権限などをLDAP*1で管理していたので、MySQL上の権限もLDAPで一元的に管理したかった。

MySQL PAM認証プラグイン

商用版MySQLの機能にPAM認証プラグインがあります。

PAM認証プラグインを用いると、MySQLログイン時の認証をLDAPに委譲し、更にLDAPのグループとMySQLユーザのマッピングを指定できます。

これにより、LDAPのdb_adminというグループに所属するエントリがMySQLログインした際は、強力な権限を持つMySQLユーザになる、といったことができます。

既にLDAPを運用している組織では、多重管理による運用コスト増大を回避しつつMySQL上の権限を制御出来ます。

設定例

想定するLDAPのグループとマッピング先のMySQLユーザに付与したい権限は以下とします。
今回の例ではシンプルな3グループで設定します。

LDAPグループ 所属メンバー DB上の権限 MySQLユーザ
db_admin DBA ALL db_admine_user
operation 運用チーム データ更新 operation_user
engineer 全エンジニア readonly engineer_user

pam設定ファイルの作成

以下の形式でPAM認証プラグインが使用するpam設定ファイルを作成します。

----------------------------------------------------------------
#%PAM-1.0
auth required {認証モジュール}
account required {認証モジュール}
----------------------------------------------------------------

使用するLDAPクライアントはpbis-openです。

/etc/pam.d以下にpam設定ファイルを作成します。ファイル名はmysqlとします。

/etc/pam.d/MySQL

----------------------------------------------------------------
#%PAM-1.0
auth required pam_lsass.so
account required pam_lsass.so
----------------------------------------------------------------

MySQL側の設定

  • PAM認証プラグインをインストール
----------------------------------------------------------------
install plugin authentication_pam soname 'authentication_pam.so';

----------------------------------------------------------------
  • MySQLユーザを作成、権限設定
----------------------------------------------------------------
-- プロキシされるユーザの作成
CREATE USER 'db_admin_user'@'192.168.%.%' IDENTIFIED BY 'hoge1';
CREATE USER 'operation_user'@'192.168.%.%' IDENTIFIED BY 'hoge2';
CREATE USER 'engineer_user'@'192.168.%.%' IDENTIFIED BY 'hoge3';

-- 各ユーザに権限付与
GRANT ALL ON *.* TO 'db_admin_user'@'192.168.%.%';
GRANT SELECT,UPDATE,INSERT,DELETE ON *.* TO 'operation_user'@'192.168.%.%';
GRANT SELECT ON *.* TO 'engineer_user'@'192.168.%.%';

-- プロキシ用の匿名ユーザを作成
-- AS以降の文字列(authentiation_string)は {pam設定ファイル名}, {LDAPグループ}={MySQLユーザ} の形式で記述する 
CREATE USER ''@'192.168.%.%' IDENTIFIED WITH authentication_pam AS 'mysql, db_admin=db_admin_user, operation=operation_user, engineer=engineer_user';

-- プロキシ権限を付与
GRANT PROXY ON 'db_admin_user'@'192.168.%.%' TO ''@'192.168.%.%';
GRANT PROXY ON 'operation_user'@'192.168.%.%' TO ''@'192.168.%.%';
GRANT PROXY ON 'engineer_user'@'192.168.%.%' TO ''@'192.168.%.%';
----------------------------------------------------------------

ここまでの設定で、LDAPのユーザ名でログイン時に次のような流れで認証されるようになります。

  1. ユーザ名をLDAPユーザにしてログイン。
  2. mysql.userテーブル上に合致するユーザ名が見つからない。
  3. 合致するユーザ名が無い場合、匿名ユーザにマッチングされる。
  4. 匿名ユーザの認証にPAM認証プラグインが設定されているため、PAM認証が行われる。
  5. 認証に成功すると、匿名ユーザはLDAPグループごとにマッピングしたMySQLユーザをプロキシする。

上記のLDAPグループに複数所属している場合、記述した順でマッピングされる。

例だと db_admin > operation > engineerの優先度でマップされるので、 db_adminとengineerに所属するユーザはdb_admin_userとなる

ログイン動作確認

  • ログイン
----------------------------------------------------------------
mysql -u {ldap_user} -p
----------------------------------------------------------------
  • ログイン後
----------------------------------------------------------------
SELECT user(),current_user();
----------------------------------------------------------------

上記のクエリ実行でuser()にLDAPのユーザ名が、current_user()にLDAPグループに対応したMySQLユーザ名が表示されます。

踏み台ホストで権限を変える

MySQLサーバにアクセスする踏み台ホストを変えることで、マッピングするユーザを変えることもできます。

これにより、192.168.0.1からのアクセス時はreadonlyなユーザにマッピングし、SELECTして調査したいだけの時は、不要な権限を持たないMySQLユーザを使ってもらうといった運用も可能です。

192.168.0.1からアクセスした際はreadonlyなユーザにマッピングするように設定

----------------------------------------------------------------
CREATE USER 'engineer_readonly'@'192.168.0.1' IDENTIFIED BY 'hogehoge';
GRANT SELECT ON *.* TO 'engineer_readonly'@'192.168.0.1';
-- エンジニア全員が所属するengineerグループにengineer_readonlyユーザをマッピング。これにより192.168.0.1からアクセスした際は全エンジニアがreadonly権限のユーザになる
CREATE USER ''@'192.168.0.1' IDENTIFIED WITH authentication_pam AS 'mysql, engineer=engineer_readonly';
GRANT PROXY ON 'engineer_readonly'@'bar' TO ''@'192.168.0.1';
----------------------------------------------------------------

監査ログ

MySQLの監査ログプラグイン(こちらも商用版MySQL)と組み合わせると、?LDAPグループとマッピングされるMySQLユーザを指定したフィルタを作ることで、簡単にLDAP認証でログインした全ユーザをロギング対象にすることができます。*2

----------------------------------------------------------------

注意

MySQL 5.7.12以前のバージョンでは監査ログプラグインの挙動にバグがあり、LDAP認証でログインした全てのユーザをロギング対象にするには、 全LDAPエントリ×アクセス元となり得る全てのhostを指定するフィルタを作成しなければいけません。*3

----------------------------------------------------------------

まとめ

商用版MySQLの機能であるPAM認証プラグインを利用し、ログイン認証をLDAPに委譲することで、 低運用コストでのデータベース権限管理を実現する方法を紹介しました。

特にLDAPを運用している組織にとって有用な方法になり得ます。
実運用ではリリーススピードを緩めないような権限委譲など、組織の設計が重要になると思います。

*1:ネットワーク機器やユーザーなどの情報を管理するディレクトリサービスへ接続するためのプロトコルhttp://www.atmarkit.co.jp/aig/06network/ldap.html

*2:MySQL :: MySQL 5.7 Reference Manual :: 6.4.5 MySQL Enterprise Audit

*3:18 Changes in MySQL 5.7.12 (2016-04-11, General Availability)

Date
Author
エンジニアの視点から、様々な技術、サービス開発秘話、イベントをご紹介していきます。 ジーニーエンジニアチーム

ジーニーの心臓部隊!エンジニアたちによる、エンジニアブログを本日スタートしました!

GENIEEエンジニアブログ → こちら

初回は、CTO×編集長対談です。ぜひご覧ください。

* * *

GENIEEエンジニアブログ編集長を務めることになりました、R&D本部マーケティングオートメーション開発部の張です。ジーニーへは、2016年4月に入社して、主にフロントエンドを担当しています。初回ということで、CTOの篠塚とブログを始めるきっかけや、ジーニーの開発部門の紹介をさせていただきます。

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

ブログを始めるワケ

張:
ジーニーは、結構、面白い技術を使っていて、色々発信したいと思っていたんです。でも、ブログもなく、あまり社外に伝える機会がないな、と思っていました。
篠塚(CTO):
そうですね。R&D本部全体では、「誇れるものをつくろう」といつも言っているので、「誇れるものをつくったら、自慢しなきゃ!」と。それを発信していきたいというのが、このブログを始めた1つのきっかけですね。
あと、自分で勉強して、プロダクト開発に反映させて、「よかったね」と。それはそれで良いんですけど、エンジニアのキャリアやスキルアップを考えると、社内外の場でお互いに共有したり、発信したりしていくのも大事。そこでいろんな人からフィードバックを得て、ブラッシュアップしていく方が成長できると思うんですよ。
張:
このブログを通して、優秀なエンジニアの方たちにジーニーの技術を知ってもらいたいし、それによって、友を呼びたいですね。
篠塚:
ジーニーは、あまり中のことを語らない人たちが多いけれど、アドテク自体がマニアックなので、おもしろい内容が書けるんじゃないかと思うんです。アドテクノロジーやマーケティングテクノロジーについて、興味を持ってくれるエンジニアが増えたらいいなと。そして、仲間が増えたら嬉しいですね。

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

篠塚:
R&D本部には、「アド・プラットフォーム開発部」「マーケティングオートメーション開発部」「基盤技術開発部」「経営情報システム開発部」の4つの部があり、それぞれ25人・15人・10人・10人で合計60人ぐらいいます。
張:
メンバーのバックグラウンドは、コンピュータサイエンス系が多いですが、文系の人もいれば、美大(アート)出身者もいて、面白い人が集まっています。ちなみに僕は経済学部でした。
篠塚:
張さん、文系だったんだ!びっくり。
新卒も8割くらいはコンピュータサイエンス出身ですね。理工系も少しいます。
張:
中途では、1/3ぐらいがアドテク出身で、SI出身も多いですね。あとは、ゲーム系とか、Webサービス系とか、いろんなバックグラウンドの人がいると思います。
篠塚:
総じて、尖っている人が多い気がしますね。みんなそれぞれ特長的。
あと、女性は現状2名しかいません!
張:
特にアドテク業界は、女性が少ないんですかね?!
篠塚:
アドテク女子って、本当に少ない(笑)
張:
ジーニーは、女性も働きやすい環境ではあると思うんですけど。
篠塚:
男性も育休取っていますし。エンジニアではないですが、女性社員で産休育休をとって復帰している前例もあります。女性の感性がもっとプロダクトに入ると、いいと思うんですよね。

仕事の仕方や雰囲気

張:
僕は、何でも屋ですね。以前は、フロントエンドとバックエンドが分かれていましたが、最近は、一緒に仕事をするようになってきています。タスクに対して、みんなで力を合わせてやるフルスタックというイメージです。
篠塚:
そうですね、インフラは分かれていますが、フルスタックを目指しています。
もちろん、人によって得意領域はあるけれど、他の人の業務を知らないと、最適な設計ができないと思います。
これまで規模が小さい頃は、とにかくスピード重視で役割を分けてやってきました。しかし、規模も大きくなり、今後、ジーニープラットフォームとして連携していくことを考えると、技術的な切り分けではなく、タスクの解決方法に合わせて分担していく方がいいと考えています。そのためには、エンジニア1人ひとりが広範囲のことを知らなければいけないですね。
張:
もちろん初めから全部できる、というのは難しいですが、例えば、データベースは何を使っているかとか、どういう構成で何を処理しているかなど、プロダクトを知らないと、フロントでもアプリケーションでもモノづくりはできません。お互いを知らないと、チームとして動けないんですよね。
篠塚:
この体制は、特長的かもしれませんね。
張:
最近は、チームを越えてお互いを知るようになってきたと思います。もっともっと、壁をなくしていきたいですね。そのための1つの方法として、週1回社内勉強会をやっています。こちらのチームでは新技術を使って開発しているのに、隣のチームはよくわからない、というのではもったいないですから。プログラミング業界でよく言われるような“車輪の再発明”とかしたくないからね。知識が共有できれば、ジーニーの開発パワーはもっと上がってくると思うんです。
篠塚:
勉強会のほかには、月に一度、R&Dの全体会があります。メンバー持ち回りでLTをやっているんですけど、わりと面白いですよ。飲み会とかより、ずっと盛り上がるんです。この前のLTでは、あるメンバーが「モテたいから髪色を変えたい」ということで、似合う色を判定するためだけの仕組みをわざわざAI使って作ってみましたって発表がありました。みんな大爆笑でしたよ。「変えればいいじゃん!」って。そんな雰囲気の部門です(笑)

ジーニーならではのこと

篠塚:
普段よく目に触れるWebサービスは、UIを提供するのが一般的ですが、アドテクやマーケティングオートメーションでは、見える部分がほとんどありません。表には出ませんが、例えば、潜在顧客に1通メールを出したり、1つバナー広告が表示されたりするまでには、いろいろなドラマがあるんですよ!データを集めて、次も出すべきかどうかというスコアリングや分析をして、良かったのか悪かったのか、その結果をフィードバックする仕組みは、顧客のマーケティング施策全体や業績に大きく関わってきます。目に見える部分はほんのわずかですが、裏側ではけっこう壮大なことをやっているんですよ。
張:
MAJIN(マーケティングオートメーションツール)も、目に見えないところで結構頑張っているんですよ。MAJINは、フロントもバックもなかなか複雑で、データベースの開発とかにも手を出そうとしています。最先端の論文で研究して技術を実装してみたり、普通ならあまり使わない言語を使ってみたり、いろいろ試して、改善して、結果どんどん良くなっています。ジーニーの場合、実験的な試みや失敗も許容してくれる環境があるので、最先端の技術にチャレンジできるのがいいですね。
篠塚:
他社が採用していないような技術も、積極的に研究したり、実験的に採用したり、産学連携したり。いろいろやっていることを活かして、オープンソースへの貢献もしていきたいですね。
張:
新しい技術の開発は、他社だとあまり裁量権がもらえないことが多いと思うんです。僕の場合、どんどん勉強して、実際に試せるというのは、ジーニーに入って良かったことの1つだと思います。
篠塚:
営業のみんなが頑張ってくれているから、エンジニアにチャレンジできる余裕が生まれるので、ありがたいですね。いい循環が生まれていると思います。
あと、ジーニーの特徴として、データ量(トラフィック)が多いことがあります。1日のデータ処理量は、だいたい15テラくらいあるんですが、それを効率よく24時間365日動かし続けなければいけない環境って、わりと珍しいと思うんですよ。
張:
MAJINの方では、機械学習も強化していますし、AI活用も進んでくるので、そういうチャレンジをしたい方にもいい環境だと思います。

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

篠塚:
世の中にまだ解決できてない課題があって、それを解決すると、少し世界がよくなります。なぜ、いろいろな課題が解決できないかというと、アイデアがないわけではなくて、いろいろな困難があって越えられないんですよ。
顧客の本質的な課題にしっかり応えるために、必要な技術を見極めて、ベストなものを真面目につくる。有り体のものを使うのではなく、自分たちにしかできないものを自分たちの手でつくるんです。課題自体がどんどん複雑になっているので、必然的に最先端の技術を取り入れていかなければならないわけです。そうやって、1つずつ課題を解決していくと、世の中が良くなっていくんです。それが僕たちのミッションだし、それができると「誇れるものがつくれた」ということになります。
張:
その通りです。困難があって、他の人が解決できなかったことを、僕らがエンジニアリングで解決できたら達成感がありますよね。
篠塚:
MAだったら、どこまでオートメーションにすると、本当にマーケターが楽になるかとか、人では思いつかない(できない)成果が得られるかとか。そういった壁を一つひとつ解決していくことが大事だと思っています。
張:
こんな僕たちの学びとか気づきとか、勉強会や研究について、このブログで発信していきたいと思っています。
よろしくお願いします!

* * *

今後、定期的に有用な情報を公開して参りますので、お楽しみに!

GENIEEエンジニアブログ → こちら

はじめに

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

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

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

現象

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

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

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

この時の状況でいうと、

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

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

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

困った時はプロファイラ

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

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

ジーニーでは速度上のボトルネックになりそうな箇所を特定するためによく利用しているツールになります。(詳細は[こちら](https://perf.wiki.kernel.org/index.php/Main_Page))

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

“`
$sudo perf top

7.55% adserver [.] backend::doPreAdSelect(geniee::lot&, std::vector<geniee::banner, std::allocator >&)
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 > 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_irq`がCPU使用率の上位に現れることはないということがわかりました。

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

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

_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 >::_M_emplace_back_aux(geniee::banner&&)
| |
| |–1.55%– 0x7fd18d0a1000
| | 0x0

“`

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

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

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

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

すると、

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

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

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

tasksetコマンドは、コマンドやプログラムを特定のコアで実行することを可能にするコマンドです。(詳細は[こちら](https://linux.die.net/man/1/taskset))

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

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

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

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

終わりに

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

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

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

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

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

Date
Author
エンジニアの視点から、様々な技術、サービス開発秘話、イベントをご紹介していきます。 ジーニーエンジニアチーム

はじめに

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

業務では弊社のマーケティングオートメーションプラットフォーム「[MAJIN](https://ma-jin.jp/)」のフロントエンド開発・保守、基盤の改善、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ではRDBMSのMySQLの他に、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を選んだ理由

  • 一部のバックエンドとAPIがPython 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に反映されます。一見簡単そうな仕組みですが、データフローを正確に定義することによって大規模な開発に莫大な威力を発揮できます。

<p data-height=”265″ data-theme-id=”0″ data-slug-hash=”GEYGgX” data-default-tab=”html,result” data-user=”cheungchifung” data-embed-version=”2″ data-pen-title=”Example of two-way binding” class=”codepen”>See the Pen <a href=”https://codepen.io/cheungchifung/pen/GEYGgX/”>Example of two-way binding</a> by Cheung Chifung (<a href=”https://codepen.io/cheungchifung”>@cheungchifung</a>) on <a href=”https://codepen.io”>CodePen</a>.</p>
<script async src=”https://production-assets.codepen.io/assets/embed/ei.js”></script>

“`html
<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の実装はいくつもあります。[参考](https://teropa.info/blog/2015/03/02/change-and-its-detection-in-javascript-frameworks.html)

    • – 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の公式の[比較記事](https://jp.vuejs.org/v2/guide/comparison.html)によると、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の公式サイトに[デモ](https://vuejs.org/v2/examples/select2.html)があります。多くのUIコンポーネントはこちらの方法で対応しました。Vue.jsには様々なUIのライブラリが存在していますが、MAJINに合うものはなかなか見つからず、従来のUIライブラリをこの方法で流用し、Vue.jsに対応しました。

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

`plugins/i18n.js`

“`javascript
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形式のyamlのi18nの定義ファイルをそのままVue.jsで使い回すことです。
冒頭の`i18n/campaigin.js.yml`は今回使うi18nファイルの名前であり、その中の `i18n` は `webpack.config.js`で設定したパスです。

`webpack.config.js(抜粋)`

“`javascript
resolve: {
“alias”: {
“i18n”: __dirname + “/path/to/translations”,
}
}
“`

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

“`javascript
module: {
loaders: [

{
test: /\.(yaml|yml)$/,
loader: “json!yaml”
},

],
}
“`

そしてエンドポイントファイルに
“`javascript
Vue.use(I18n)
“`
を追加すれば、ViewModelで使えます。

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

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

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

Date
Author
エンジニアの視点から、様々な技術、サービス開発秘話、イベントをご紹介していきます。 ジーニーエンジニアチーム

はじめに

みなさん始めまして、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に並列でクエリを投げる

レポートAPIがscalaの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

以上。

Date
Author
エンジニアの視点から、様々な技術、サービス開発秘話、イベントをご紹介していきます。 ジーニーエンジニアチーム

こんにちは、人事担当の見並です。

ジーニーでは、ビジネスサイド・エンジニアサイド問わず、社内の勉強会開催が増えてきています。今回はその中で、R&D本部 マーケティングオートメーション開発部の勉強会へ潜入をしてきました。

ライトニングトークではなくロングトークなLT

マーケティングオートメーション開発部は、ジーニーが2016年7月に立ち上げたMAJINのプロダクト開発を担う部門です。
MAJINはPC向けのサービスだけではなく、スマートフォンアプリへプッシュ通知機能などを組み込むことができる、SDK(Software Development Kit)も提供しています。

今回の勉強会テーマは、そのSDK開発におけるこれまでの様々な知見や気付きを共有するものでした。

勉強会は毎週持ち回りで発表者が変わるのですが、今回の発表者はMAJINのSDK開発を担当する大内(中央の女性)というエンジニアです。
現在は弊社内でもGenieeSSPのSDK開発を担当するエンジニアが複数名体制になるなど、アプリ開発に携わるエンジニアが増えましたが、大内の入社した頃はまだ人数が少なく、その頃に抱えていた悩みや、時系列で変わっていったことなどを詳しく話してくれました。

技術的なトピックスとしては、通常のWEB開発と異なり多種多様なデバイスやバージョンに対応するための仕組みや、開発時の注意点、アプリディベロッパーのエンジニアに組み込んでもらうツールだからこその難しさなどを赤裸々に語る発表となりました。

また、技術面以外にも、マネージャーへの要望や御礼、自戒を込めた頑張る宣言なども盛り込まれ、大いに盛り上がりました。

形式としてもインタラクティブな対話の多い勉強会となり、仲間たちからも大いに質問や容赦ないツッコミも沢山ありましたが、その結果、他部門から参加していたGenieeSSPのエンジニアから新しいMTGのお誘いが生まれるなど、社内の新たな連携も誕生する結果となりました。

技術はもちろん、お互いのことをもっと知れる場を作りたかった

マーケティングオートメーション開発部が行っている勉強会は、エンジニアの張が発起人としてスタートしました。

彼は日本の大学院を卒業後、スマートフォンアプリのゲーム開発会社を経て2016年4月にジーニーへジョインしました。

チームでの機能開発を牽引したり、新しい技術に取り組むスピードの速さが周囲のメンバーから評価され、2016年度のGeniee Value体現者へ贈られる、「BEST GENIEEIST」のSpeedとTeamworkの代表に選出されています。

勉強会を終えた後、開催の背景など少しインタビューをさせてもらいました。

– この勉強会をスタートするきっかけなどはあったのでしょうか?

MAJINの開発は非常に短期間で、かつジーニーでの社歴が長い方から入社したばかりの方まで、様々なエンジニアが集まってプロジェクトがスタートしました。

そのため、チームメンバーがそれぞれがどの分野に詳しいのか、興味関心が高いのかなどが分からないという状態があり、最初はそのスキルやナレッジを共有し、理解し合うことを目的に勉強会を開始しました。

– もうすぐ半年近くになると思うのですが、開始してみていかがでしたか?

MAJINの開発はジーニーの中でも新規プロダクトということもあり、比較的新しい技術を柔軟に取り入れています。勉強会では、新しい技術に関するトピック等をお互いに持ち寄ってディスカッションしたり、理解を深めていくことができて良かったと思います。

また、技術面以外にも、今回のSDK開発を担当していた大内のように、外からは見えづらい、携わる分野独自の悩みをお互いに共有することによって、プロジェクトやチーム等の枠組みを越えて手を取り合うキッカケが生まれているのも嬉しいですね。

– 今後も勉強会は継続していく予定ですか?

もうすぐ僕の総括パートの順番が回ってくるので、そこで振り返りを行って、参加してくれたみんなからもフィードバックをもらう予定です。
それを受けて、勉強会をより良く、もっと広げていけたらと思っています。

– 楽しみですね、ぜひまたお邪魔させてください!本日はありがとうございました!

こんな環境で一緒に学び合い、新しいことにどんどん取り組んでいただけるエンジニアの方を、キャリア採用・新卒採用どちらも広く募集しています。
ぜひ気軽な気持ちでオフィスへ遊びにいらしてください!

【 採用サイトはコチラ 】

【 Wantedlyページはこちら 】

Date
Author
社風やジーニーでの働き方などをご紹介していきます。 ジーニー人事チーム

こんにちは、元・システムエンジニア、現・人事の見並です。
最近は「昔はコードを書いていました」というのも憚られるくらいコーディングの感覚が薄れているので久しぶりにQiitaへ投稿してみました。

さて、本題に入りまして、今回はジーニーが早稲田大学と取り組んでいる産学連携に関して、対象研究室の学生から成果発表がありました。
元・システムエンジニアという名の下お邪魔してきましたので、頑張ってレポートをいたします。

冒頭の挨拶は、新卒入社1年目のエンジニア・堀田さん。ジーニー側のプロジェクト窓口として牽引し、今回の社内発表会も彼が企画をしてくれました。

CTR向上を目的としたオンライン広告のWEBページにおける配置位置の推定

本日の発表は2つ。まずはジーニーの広告配信をしたログを材料に研究をしていただいたテーマからです。

こちらはジーニーが広告配信をした際に取得しているログデータを分析してもらうというもの。
数週間分の一部を切り出したデータ量ですが、15億回以上の広告配信をした記録や200万回以上のクリックがされた記録などがインプットデータとして用いられています。

これらを広告サイズや表示する位置、広告の種類カテゴリなど複数の観点から分析を行います。
Gradient Boosting Decision Tree(GBDT)やEMアルゴリズムなどを用いた手法が先行研究としてあり、その上で新たな手法を用いた推定方法を提案していただきました。

社内のエンジニアも普段自分たちが作っているプロダクトに直結する話なので真剣です。
途中何度も「どのように定義した数値?」「前提条件を発表のように設定した背景は?」など活発に質問が上がりました。

これまでジーニーで用いていなかったモデルを使う示唆もあり、「このやり方は面白い」「このやり方でやるなら、分析するデータにこんな要素も付加して結果を見てみたい」など、次に繋がるディスカッションが生まれました。

単語判定用辞書の自動拡張

続いて、こちらはジーニーが提供している独自技術「GAURL」への活用を目論む研究です。

「GAURL」とは、サイトコンテンツ上のキーワードをURL単位で自動解析し、配信する広告を出し分ける仕組みです。
サイトコンテンツを判別するためには、基準として突き合わせる膨大な「単語辞書」が必要であり、この作成や拡張を人手で運用し続けるのは大変。そこで、この辞書拡張の大部分をテクノロジーによって自動化してしまおうというのが本研究のテーマです。

アプローチとしてはジーニーが既に保持している単語辞書が一定規模あるため、カテゴライズを精査してWord2Vecなどの手法を用い、単語ベクトル学習させたり、交差検証でその妥当性を図った結果を発表いただきました。

まだ全ての単語カテゴリに適用できるわけではありませんが、実施したカテゴリでは新たに拡張するための単語を拾い上げることに成功しました。一方で、発表内容からヒントを受けて、精度をより上げる手法の提案が社内のエンジニアから上がり、まだまだ発展させられるテーマであることが分かりました。

今後はジーニーで持っているナレッジをより密に共有しながら、実用化へと近付けていく次のステップへ進んでいきます。

研究開発と自社プロダクト

ベンチャー企業が研究開発へ取り組むメリットは、実用化へのスピード感であったり、新鮮な実データを用いてリアルなビジネス環境でPDCAを回せることにあります。

ジーニーではキャリア採用・新卒採用ともにテクノロジーで企業競争力を高めていただけるエンジニアを募集しています。ぜひ気軽な気持ちでオフィスへ遊びにいらしてください!

【採用サイト】
【Wantedly】

Date
Author
社風やジーニーでの働き方などをご紹介していきます。 ジーニー人事チーム

 

こんにちは、人事の藤本です。
今回は、先日のバレンタインデーに開催した、ジーニーのエンジニア向け勉強会についてご紹介します

ジーニーは、プロダクトを自社開発していることもあり、従業員数の約4割がエンジニアです。最先端のアドテクノロジーを活用していくには、エンジニアのスキルアップ・キャリアアップは欠かせません。ジーニーでは、定期的にエンジニア向けの勉強会を行うなど、様々な取り組みを行っています。
今回の勉強会では、Google Mapの日本版立ち上げやTwitterでのプロダクト開発に携わってこられた上田 学氏をお招きし、エンジニアのキャリアについて語っていただきました。

 

ラウンジには、米国IT業界で活躍する上田さんのお話が直接聞けるとあって、エンジニアが大集合。上田さんが、キャリアについて熱く語り始めました。
上田さんによると、エンジニアのキャリアには大きくは2つの軸があるとのこと。

上田さん:
1. 複雑度を上げていく
複雑度が上がるというのは、大きく3段階あると思っています。
最初は、小さなバク修正や、依頼された機能を作るといった、定義されている問題を解決していくこと。次は、現実のスペックに当てはまらない問題を解いていくということ。そして最後は、問題の定義が曖昧で、自分で問題を定義して解決していくということ。これを少しずつ上げていくのは1つの軸だと思います。

2.影響範囲を広げていく
次に、エンジニアとしてキャリアを伸ばしていく上で「影響範囲を広げる」という軸があると思います。まずは、開発者が自分一人で言われたものを作れるというのが第一段階。次に、他の人が読みやすいコードを書くことや、自分のチームの人が使うフレームワークを作ってそれで作業効率を上げるというのが次の段階。例えば、通信ライブラリやWEBのフレームワークというような、自分のチームの人達だけでなく、会社全体の人が使うようなコードや、他チームのプロダクトでも使われるようなものを作ると、さらにエンジニアとしての価値が上がると思います。そして最後は、世界中で使われるオープンソース等、他の会社のエンジニアがそれを使うことで、より高いレベルの仕事ができたり、無駄を省いたりできるものを作ること。会社の範囲を飛び出すことが、エンジニアとしてできる一番大きな仕事だと思います。

 

ジーニーのエンジニアから出される質問にも、1つひとつ丁寧に答えて頂きました。

質問:
実力が高い人達と一緒に仕事をしたと思うのですが、何を盗みましたか?

上田さん:
自分よりできる人はいっぱいいると思っています。ただ「これを盗んだ!」とすぐに思いつくのはあまりなくて、自分の価値を発揮できる所を見つけ出して、そこをずっと伸ばしてきました。例えば、私はメールのシステム開発の専門家としてずっとやっていて、当時、MIMEパーサーを書ける人は私しかいなくて、誰にもない知識を持っていました。自分なりのカラーを見つけて他の人にはできない事を作っていっていました。

質問:
大変だったり、不安だったり、辛い時はどのように乗り越えていましたか?

上田さん:
これは他の人に教えて貰った事ですが、何か不安に感じている時は、何が不安なのかを書き出します。そうすると、意外と問題になっているのは10個くらいと多くはないんです。そして、まず今やらなければならないこと、明日以降でもできることに分けて、今やらなければならないことからやっていきます。そうすると自然と3個~5個くらいまで減るので、落ち着いて処理することができます。

 

講演の後は、懇親会。ビール片手に、最後までコアな話をして頂きました!

ジーニーでは、今後もエンジニアのスキル・キャリアアップに繋がる取り組みを積極的に行っていきます。

上田学さん、今回は本当にありがとうございました!

 

こんな人達と一緒に働いてみたい!という方は是非コチラからエントリーをお待ちしています!

Date
Author
社風やジーニーでの働き方などをご紹介していきます。 ジーニー人事チーム
Back to top