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

Geniee’s BLOG

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

こんにちは、R&D本部 2021年新卒エンジニアの筒井と渡邉です。
今回は、新卒向けのエンジニア研修「bootcamp2021」について研修を受講した新卒と運営が対談で振り返ります。
進行は、R&D本部 2020年度卒エンジニアの高橋さんです。

Contents

1 「bootcamp」とは?

2 【対談】新卒から見たbootcamp2021

3 【対談】運営から見たbootcamp2021

4 【対談】来年度に向けて

1 「bootcamp」とは?

ジーニーでは、新卒エンジニアに向けて、「bootcamp」と呼ばれる技術的な研修を行います。ここでは、1ヶ月半ほどかけて様々な技術に触れながら、業務に入るまでの準備を行います。

多種多様なバックグラウンドを持った新卒が、技術力を底上げできることがbootcampの特徴です。

▼研修内容の例
  • Git
  • UNIXコマンド
  • プロダクトマネジメント
  • ネットワークと仮想技術
  • MySQL
  • CSS
  • LEMP
  • アルゴリズムとデータ構造
  • JavaScript
  • デバッグ
  • Go
  • クラウド
  • サーバー作成
  • 開発ルール
  • セキュリティ
  • テスト
  • コードレビュー
左から 21卒 渡邊さん、20卒 東さん、21卒 筒井さん、20卒 牛丸さん

2 【対談】新卒から見たbootcamp2021

 ―― まずは、bootcamp2021を受講した感想を聞かせてください。

21卒 筒井:昨今の情勢を踏まえて、リモート形式と対面形式が併用されていたのですが、その中でもスムーズに運営を進めてくださったのが良かったです。
リモート環境だとどうしても会話がしづらくなりますが、少人数のグループに分けて課題に取り組むなどの工夫があり、新卒同士でコミュニケーションが取りやすかったですね。

21卒 渡邉:自分は単純に楽しかったですね。大学での研究のように一つを極めるのではなく、新しい知識をどんどん取り入れていくというのが、とても新鮮でした。
自分が興味のある分野だけではなく、網羅的に知識を得られたのも良かったです。

21卒 筒井:自分は大学時代にWeb開発の知識をあまり学んでこなかったので、bootcampで足りない知識を補えました。研修で学んだ知識をそのまま業務で活かすことができているので、とても有意義な時間だったと思います。

―― 特に、どんな知識を得られましたか?

21卒 筒井:開発の経験が少なかったため、ほぼ全てが真新しい内容でした。UNIXコマンドやGitの使い方など基礎的なところから、Dockerやクラウドなど実践的なところまで知ることができました。

21卒 渡邉:同感です。自分はUNIXコマンドなど基礎的な分野で分からないことが多かったのですが、質問できる先輩や同期が多くいたので、基礎固めに役立ちました。
大学での研究とは違い、みんなが揃って同じ課題に取り組んでいたので、質問しやすいというのも良かったです。

―― bootcampで、特に実業務に役立っている内容などはありますか?

21卒 筒井UNIXコマンドやGitの知識が特に役立っていると思います。
今まではGUIに頼りきっていたのですが、コマンド操作に慣れることで作業の効率化に繋がりました。

また、Gitのbranchを切る、という基礎的な知識がとても助かっていますね。今まで個人での開発は`git push origin main`しかしてこなかったので(笑)。
今ではしっかりとbranchを切って、レビューを受けて、修正をして……という過程を経ることで、安心して自分のコードをマージすることができています。

21卒 渡邉:自分はDockerとGo言語の知識が役立っていますね。配属されたプロダクトでまさに使用している技術ですので、日々業務に活かしています。

20卒 東:役立っているのは嬉しいですね。bootcampの目的の一つとして “実際にプロダクトで使われている技術を教える” ということがあり、その目的が達成されていることを知れてよかったです。

20卒 牛丸:基礎的なところをしっかり学ぶことで、何も知らない状態では起こってしまうようなミスを一定防ぐことができていると感じています。研修が全体の最低限の技術レベルの引き上げになっていると嬉しいですね。

 ―― では、bootcampで特に面白かった講義はなんですか?

21卒 筒井サーバ作成研修です。Redisのプロトコルに沿ったサーバを作成するという研修でしたが、作成したサーバの速度を同期間で競い合ったのが面白かったです。

21卒 渡邉LEMP研修が面白かったです。ブログを作成するという課題で、フロントエンドやバックエンドも含めて作成したため、これまでのbootcampの集大成という感じがしました。
個人的にデザインにも興味があり、こだわれたのも楽しかったです。最後に成果物のプレゼンをする際も、発表資料にかなりこだわってしまいました(笑)

20卒 東:渡邉さんの発表は特に良かったことを記憶していますね。全体としてデザインが統一されていました。

―― bootcampの内容で改善すべきと感じたところを教えてください。

21卒 筒井講義によってレベル感が違う点が少し気になりました。全員が課題を午前中に終えられる講義がある一方で、2、3人程度しか基礎課題を終えられない講義もありました。

21卒 渡邉:自分は、個人ではなくチームで1つの開発を進めるような研修があれば良かったと感じました。

20卒 牛丸:チームで行う研修をカリキュラムに盛り込む予定もありましたが、諸事情で実施できませんでした。ただ、次年度に向けて、今年度よりも一段進んだ研修ができるよう、画策しています。

3 【対談】運営から見たbootcamp2021

 ―― 続いて、bootcampの運営をされた皆さんにお聞きします。全体を通してどのような感想をお持ちですか?

20卒 東:研修の計画段階に携わりましたが、なぜ研修を行うかという目的設定や、社内調整など、学びになる部分が多かったです。

20卒 牛丸:そうですね。研修の講師は社内から選出しているのですが、講義レベルに合わせて高い技術力を持つ方に担当いただく必要があり、各チームの状況を確認しながら社内調整をする必要がありました。
その際、社内でどういう決め方がなされて、どのように意思決定されるか、という過程を知ることができました。

―― 運営を行う中で特に気を付けたことや、心がけたことを教えてください。

20卒 東:今年度はリモートで講義を行なうことがあったので、コミュニケーションの促進を心がけました。
具体的には、受講者向けのアンケートでコミュニケーション量に関する設問を用意したり、チームに分かれての作業では少人数に分けてコミュニケーションをとりやすくしたりしました。また、講師の方にもコミュニケーションを促すように依頼しました。

21卒 筒井:確かに、アンケート項目としてコミュニケーションがどの程度取れたか確認する設問があったので、同期と会話をするきっかけになりましたね。

21卒 渡邉:講義では3、4人のチームに分かれて作業をすることが多かったのですが、このチームはどのような基準で分けていたのですか?

20卒 東:アンケートで、次回の講義内容に関する事前知識の有無を回答してもらっていたのですが、その結果を元に、事前知識のない方が偏らないようにチーム分けをしました。

 ―― 大変だったことや、反省点はありますか?

20卒 東反省点としては運営陣だけで何もかもやろうとし過ぎたことですね。
例えば、対面で研修を行う時に、講師の声が聞こえづらいという問題に気づきました。そのため、講師へ必ずマイクを使うように依頼していたのですが、そういった問題は本来、毎回講義を受ける受講者が一番気付きやすい立場にいるはずです。受講者側から要望の発信が自発的に行われるような環境づくりにより注力すべきだったと考えています。

20卒 牛丸:新卒も既に会社の一員ですので、いろいろと任せてしまった方がよかったですね。事前に起こり得る問題を想定して対応するのは難しいですし。
これは先ほどのアンケートの話にも繋がると思っていて、”アンケートの設問に、その設問を用意した意図を付記しておく” ことも必要だったと感じています。

21卒 筒井:確かに、まだ会社の一員という意識が薄く、受け身になるところが多かったです。

20卒 東:もちろん、新卒の方が意見を言える雰囲気作りは、運営がする必要があると思っています。

20卒 牛丸:業務経験が既にあるインターンの方に雰囲気作りを任せるのも良いかもしれないですね。誰かが初めに積極的に意見を出せば、意見の出しやすい雰囲気が作れますからね。

4 【対談】来年度に向けて

 ―― 来年度のbootcampに思うこと/意気込みなどをお聞かせください。

21卒 筒井:人に教えられる力量があるかは自信がありませんが、bootcampが始まるまでにより多くの知識を会得していきたいと思います。

21卒 渡邉:自分もまだまだ未熟なので、先ほど話にあった ”誰かに頼る” ということを大事にしたいですね。

20卒 東:昨年よりも今年、今年よりも来年と、より良いものにしていきたいですね。その上で、運営を評価する定量的な指標を得られないか考えています。定量的であれば、各組織との合意を取りやすいですからね。

20卒 牛丸:今年度の研修運営では無駄が多かったように感じます。反省をしっかり活かして、研修の改善を進めていきたいです。

一緒に働く仲間募集中!

【ジーニーのリクルートサイトはこちら】
https://geniee.co.jp/recruit/

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

GENIEE SSP開発チームの齋藤です。ジーニーには2019年に新卒入社して、今年度で3年目になります。

SSP開発チームではスクラムを取り入れた開発を始めておよそ一年経ちました。チームでスクラムをどのように取り入れたか、スクラムを取り入れてから何が改善されたか、という点を書いていこうと思います。

齋藤洋平 / 宮城大学(事業構想学部)卒業後、2019年に入社。R&D本部アド・プラットフォーム開発部 SSP開発チーム リーダー。

Contents

1. SSP開発チームの業務と抱えていた課題

2. そもそもスクラムとは

3. スクラムの課題に立ち向かう

4. スクラムを取り入れて改善されたこと

5. まとめ

1. SSP開発チームの業務と抱えていた課題

SSP開発チームでは広告収益を最大化するためのアドプラットフォームであるSSP(Supply Side Platform)の運用や改修、その他収益の全般的な最適化ツールを開発します。

■主な業務

普段はざっくり次のような業務があります。

– 管理画面の開発(React)

– APIやバッチなどサーバーサイドの開発(PHP, Go, Python)

– 広告フォーマットなどのWebクライアントサイドの開発(JavaScript, TypeScript)

– SSPやその周りのシステムの改修(C++)

– その他広告が正常に配信されているかの調査など

■スクラムを取り入れる前のSSP開発チームの課題

スクラムを取り入れる前は、エンジニアの属人化が問題になっていました。いわゆるバックエンド、フロントエンド、というような区切りで開発の担当者が決められ、PMと担当者間で仕様が決まるという体制です。開発を担当した者しか詳細を把握してない場合が多く、問題が起きた時に担当者が既にチームにいない場合は、誰かが一から仕様を調査する必要がありました。

PMと担当者という体制の場合、要件の認識が合っていないことによって開発の遅延が起きることがありました。また、エンジニアの経験や能力次第で開発が遅れてしまうということもしばしばありました。特に最近はほとんどのエンジニアがリモートで勤務しているためコミュニケーションの機会も減りました。これも情報共有においては障害になることがあり、開発の遅れの原因の一つでした。

 2. そもそもスクラムとは

2020年版のスクラムガイドでは、スクラムの定義について次のように述べられています。

> スクラムとは、複雑な問題に対応する適応型のソリューションを通じて、人々、チーム、組織が価値を生み出すための軽量級フレームワークである。

> スクラムのルールは詳細な指示を提供するものではなく、実践者の関係性や相互作用をガイドするものである。

2020-Scrum-Guide-Japanese.pdf スクラムガイド 2020年11月

…このように、抽象的な定義です。

定期的にスクラムガイドは更新されますが、2017年版と比べて2020年版では、スクラムを最小限かつ十分なフレームワークに戻すことを目的とし指示的な表現を減らしてあるようです。「スクラムとは〇〇であるべき」みたいな型に合わせるものではなく、開発チームの体制や業務に合わせて、チームの課題を解決するためにスクラムを取り入れました。

■スクラムを取り入れる

これまでSSP開発チームで抱えていた属人化の課題の解消や、開発の遅延を防ぐ・検知する、コミュニケーションの機会を増やす、といったことを目的にスクラムを取り入れました。機能をどれだけ開発できているか可視化する目的もありました。

■スクラムチーム

スクラムを取り入れてチームの編成が変わりました。PMの要望にチームで応えられるよう、それぞれに得意な領域を持ったメンバーの機能横断型チームとしてスクラムチームを構成しています。

■具体的な運用

AtlassianのJiraというツールを使っています。2週間を1スプリントとし、開始と終了にスプリントプランニング、スプリントレビューを行います。毎日15分から30分程度のデイリースクラムでコミュニケーションをとり、進捗や問題を明らかにしています。

ツールの運用の工夫として、スプリント中の差し込みタスクを記録するためのバックログを事前に作っている点が少し変わっていますが、これは後述します。基本的にはスクラムガイドに従ったフローです。

(工夫としてスプリント中に発生した差し込みを記録するためのバックログを用意している)

3. スクラムの課題に立ち向かう

ガイドラインに従って一般的と思われる形でスクラムを取り入れましたが、スクラムのフレームワークで解決できない課題があります。例えば差し込みタスクは事前のプランニングの外にあるので、こちらへの対応が課題になりました。また、広告フォーマットの仮説検証など、通常の開発とは違う取り組みにも対応が必要でした。

■差し込みタスクへの対応の難しさ

差し込みタスクはスクラムチームの進捗を妨げる障害になるので、基本的には引き受けないようにしますが、どうしても改修や調査をやらねばならない時があります。

差し込みタスクのような予定していない作業が増えると、スプリントで完了させることのできるタスクの量が安定しづらくなり、機能のリリースで価値をもたらすまでの時間が増えてしまいます。

■スプリントごとの差し込みタスクの量を調べてバッファを持つ

スプリントで実行する作業計画を立てる際に、その期間で発生する差し込みタスク分のバックログを作り、差し込みが発生するたびに追加しています。このようにしてスプリント内の差し込みタスクがどの程度あったか記録しておくことで、次回以降のスプリントにバッファを持たせるようにしています。

(差し込みタスクは誰がどれくらい時間を取られたか記録し後から把握できるようにしている)

■遊撃部隊的な動きで対応する

SSP開発チームでは、広告フォーマットの開発やフォーマットのABテストなどの仮説検証を行うこともあります。このような開発は状況に合わせて試行錯誤する作業になるため、どれくらいの開発が必要か事前に予測がしづらいです。

そこで、プランニング時にアサインするタスクを少なめにした遊撃部隊のような要員も含めています。このメンバーはプランニング時点でアサインするタスクをかっちり決めないことで、事前に予測できないタスクに対応します。

このような取り組みで差し込みタスクや仮説検証のタスクに対応可能にし、スクラムの課題に立ち向かう工夫をしています。

4. スクラムを取り入れて改善されたこと

スクラムを取り入れて体制がいくつか改善されました。エンジニア個人の能力の幅を広げる意識と、チームで知識を共有して属人化を防ぐ意識が定着しています。

■チャレンジしやすい環境になった

> スクラムチームは機能横断型で、各スプリントで価値を生み出すために必要なすべてのスキルを備えている。また、自己管理型であり、誰が何を、いつ、どのように行うかをスクラムチーム内で決定する。

2020-Scrum-Guide-Japanese.pdf スクラムガイド 2020年11月

スクラムガイドに従い、機能横断で自己管理型であることを目指しています。

機能横断型のチームになるためには、それぞれ得意な領域を持った他のメンバーとの連携やコミュニケーションが必要です。また、スクラムのメンバーは自己管理型を意識し、作成したバックログの中から自分たちでタスクを選んでいくようにしています。

タスクはリファインメント時に可能な限り分割されています。必要であればペアプログラミングも行いながら開発することで、経験が少ない領域にも小さなタスクから挑戦しやすくなりました。

■属人化の解消をチームで意識するようになった

スクラムを取り入れる前は、いわゆるフロントエンド・バックエンド、といった領域によっておおよその担当者が決まっており、担当者とPMとの間で仕様が決められるという体制でした。これは属人化の原因になっており、ある領域で優秀な一人のエンジニアにばかり任せるという状態が見られました。

スクラムを取り入れてからは、PMからの要望をチームで理解し、バックログに分割しています。スプリントで目標にしたバックログの完了はチームの責任として考え、チーム全体が仕様を理解するための意識が生まれ、属人化の解消に繋がっています。

5. まとめ

僕の所属するSSP開発チームでは、チームや組織・プロダクトの実情に合わせてスクラムを取り入れて運用しています。スクラムを取り入れて改善された点は、エンジニア個人の能力を広げる挑戦がしやすくなったことと、チームで理解して属人化を防ぐ意識ができたことです。開発する機能の遅れにも気が付きやすくなりました。

どうしても発生する差し込みタスクに対しては、一定量の受け入れる準備をしていることや、遊撃要員はある程度自由に動けるようにするような工夫をしています。それ以外はほとんどスクラムガイドに従うようにしており、従来の開発体制と比べて開発フローが改善したと感じています。

スクラムは組織が価値を生み出すためのフレームワークです。チームや開発項目で抱えている問題に向き合い、スプリントごとの振り返りで開発の体制を継続して改善することが重要でしょう。

ここまでが、GENIEE SSP開発チームでスクラムを取り入れた際の工夫や改善された点の紹介でした。スクラムを取り入れた開発の一例として参考になればと思います。

参考

2020-Scrum-Guide-Japanese.pdf スクラムガイド 2020年11月

一緒に働く仲間募集中!

【ジーニーのリクルートサイトはこちら】

https://geniee.co.jp/recruit/

こんにちは、アド・プラットフォーム開発部マネージャーの杉野です。

2014年に新卒でエンジニアとして入社してキャリアを積み、現在はDOOH(屋外広告)チームでシステム設計やメンバーのタスク管理等を行っています。

今回はGENIEEで運営しているサービスに関する話題として、システム上重要な位置を占める電子メールについて書かせていただきます。

杉野透/東京大学(大学院情報理工学研究科コンピュータ科学専攻)卒業後、2014年に入社。
R&D本部アド・プラットフォーム開発部マネージャー。

Contents

1 システムからメールを送信するということの難しさ

2 SPFとDKIM

3 設定のうっかりミス防止のために

1 システムからメールを送信するということの難しさ

カジュアル・プライベートなコミュニケーションではLINEやWeChat、Messengerなどのチャットツールが主流になってきているとはいえ、業務分野のコミュニケーションではまだまだ電子メールが駆逐されるには時間がかかりそうな昨今、GENIEEのシステムでも電子メールは大変多くの領域で使用されています。具体的に挙げてみますと、マーケティング領域のプロダクトではエンドユーザーさんに送信する種々のお知らせはまだまだメールが主体となっていますし、私が現在所属しているDOOH部門では、広告枠の購入通知やコンテンツ審査の結果など重要な内容がシステムからメールで届くようになっています。

このように業務システム上とても重要なメール送信機能ですが、同時にとても厄介な側面を有しています。皆さんご存知のスパムメール問題です。大手のメールサービス(Gmail、Outlook等)やキャリアメールは、様々な基準でスパムとみなしたメールを容赦なく叩き落とす(迷惑メールボックスにすら入らない)のですが、この判定にひっかかってしまうと、送信側から見ると送信処理自体は成功しているのになぜか送信先にメールが届いていない、という状況に陥ります。

エンドユーザーさんに送られるべきマーケティングメールや、システム利用者様へシステムからの重要な通知メールが届かないとなれば、それは重大なインシデントなので絶対に避けなければなりません。これを避けるためにメール送信部分のプログラムにも確認の必要な箇所はいくつかありますが、特に見落しやすい部分として、DNSでの設定があります。

2 SPFとDKIM

メールの送信元が詐称されていないかを確認するためのDNSの仕組みとして、SPFおよびDKIMがあります。

SPFは、メールの送信元サーバのIPアドレスがどうあるべきかを規定します。例えばDOOHシステムにおけるSPFの設定は以下のようになっています(ドメイン名およびIPアドレスはダミー)。

dooh-geniee.jp. TXT v=spf1 ip4:123.45.67.89 -all

これは、「送信元ドメインがdooh-geniee.jpであるメールアドレスは、送信元のIPアドレスが123.45.67.89であれば受信し、それ以外は全て受信を拒否して欲しい」という設定です。このように設定しておくことで、送信先であるメールサービスに対し、dooh-geniee.jpは(送信元のIPアドレスが123.45.67.89であれば)送信元として信頼できるということを伝えることができます。

DKIMは、メール送信時にメールのヘッダに電子署名を施し、その署名を検証するための公開鍵をDNSのレコードに公開しておくことで、送信先であるメールサービスに対し「電子署名の検証が通ったならば、それは確かに我々が送信したものだ」ということを伝える仕組みです。性質上鍵ペアを事前に作成しておく必要があります。具体的なDNSの設定値は公開鍵をBase64化したものであるので省略しますが、こちらも正しく設定し、メール送信時に対となる署名鍵で署名を施す設定をしておけば、送信元としての信頼性が向上します。

これらの設定は、メール送信自体を外部サービス(Amazon SESやSendGrid等)に委任していても必要となります。外部サービスに委任している場合は、設定が必要なレコードの値や、DKIMのための鍵ペアなどはサービスの方で用意してくれるため、その設定を正しく管理下のDNSに登録すればOKです。

3 設定のうっかり防止のために

これらの設定について問題となってくるのは、特にシステム移行時です。メールを送信するサーバのIPアドレスが変更となった、サービスブランドの刷新のためにドメイン名を変更した等のタイミングで、DNSに登録した情報と実際の状態が乖離を起こすと、それまで送信されていたメールが届かなくなってしまうことになります。このようなことを防止するためにも、「メールが関わるシステムにおいては、ドメイン名やIPアドレスの変更が入る場合は、SPFやDKIMまわりのレコードも確認する」という管理体制を作る必要があるでしょう。

昨今はインフラまわりの設定はクラウドに投げてしまうことも多いですが、今一度、システムの円滑な稼働に必要なインフラ設定をしっかり整理しておくのも、障害を未然に防ぐ観点では大切な仕事です。

正直な話、メール(SMTP)に代わるメッセージプロトコルが早く標準化して欲しいですね。

一緒に働く仲間募集中!

【ジーニーのリクルートサイトはこちら】

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

こんにちは、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/

はじめまして。
昨年10月に入社した新卒エンジニアの鈴木です。現在情報システムグループで社内SEをやっています。
今回は社内SEとしてこの半年間やってきたことを振り返って行こうと思います。
(情報システムチーム 鈴木悠太 2020新卒でジーニーに入社。慶應義塾大学理工学部出身)

Contents

1 社内SEって?
2 直面した問題① 管理すべきものが多い!
3 GASとSlack Incoming Webhookで自動通知飛ばしてみた。
4 直面した問題② タスクがめちゃくちゃ多い!
5 Jira Core導入
6 さいごに

1 社内SEって?

社内SEの主な仕事は以下の3つです。
・社内のIT問合せ対応
・社内の資産管理・調達
・アカウントの管理・設定
社内のITコンシェルジュのような存在をイメージしていただければ分かりやすいかと思います。
一口に「問合せ」と言っても内容が非常に多岐にわたるので最初は仕事を覚えることに必死でした。
具体的にはPCやアダプタの貸出・交換対応やVPN設定、メーリングリストのメンバー管理など、一つひとつ列挙していくとキリがありません。
社内SEは社内全部署がお客様です。円滑なコミュニケーションや他部署への理解が求められます。コミュ障を自負する理系インキャの私でもコミュ力はついてきたかなと思います。
とにかく聞かれたことには適切な応答ができるように日々粛々と業務をこなしています。

2 直面した問題① 管理すべきものが多い!

社内SEが管理するものは非常に多いです。
・PC
・モバイル端末
・周辺機器
・各種アカウント
例えばgithubアカウントの登録やVPN接続などの申請はスプレッドシートで受け付けているのですが、社内SEはこれらのシートを毎日逐一チェックする必要があります。面倒くさがり屋の私としてはこのチェック作業は煩わしい事限り無しでした。

3 GASとSlack Incoming Webhookで自動通知飛ばしてみた。
チェックは人が行うものだからミスや発見遅れが生じる可能性があるよねってことで申請シートに記入されたらslackへ通知が行くような仕組みを作りました。
スプレッドシートとGAS(Google Apps Script)は非常に相性が良いです。シートの情報を自由自在に取得できます。

このようにシートの記入列にユーザ名が記入されたらセルの内容を取得してslackに通知を飛ばしています。(本記事ではサンプルコードを見易くするため、一部マジックナンバーをそのまま記載しています)

Webhook URLはここから取得できます。
以上の工程を経てSlackチャンネルに通知が飛んでくるようになりました

簡単な仕掛けではありますが2つの効果を生み出すことができました。やったぜ。
・毎日シートをチェックする時間をゼロに
・チェック漏れがなくなった
エンジニアブログっぽい感じにしたかったのでコードとか入れて格好つけました。普段はもっと泥臭いことやってます。現在は機器管理台帳と格闘中です。

4 直面した問題② タスクがめちゃくちゃ多い!

これには困りました。
社内SEの仕事に抜け・漏れは許されません。毎日様々な依頼や申請を捌きながら機器管理や入退社の定常業務をこなしていく必要があります。そのためにはタスクを正確に管理し、チームメンバー間でも進捗や課題を共有認識として持っておく必要があります。
スピード感を持ちながらもヒューマンエラーを限りなくゼロにしていく努力が求められるわけです。
現在社内のIT問合せは「情シス依頼」という形でSlackコマンドとGoogleフォームから受け付けています。

この仕組み自体は非常に便利でよくできています。しかしスプレッドシートでのタスク管理に難がありました。
・ひとつの依頼内容を1行で表現しているので横に長く見辛い
・タスクのステータス管理が難しい
・そもそもR&DではタスクをJiraで管理している

5 Jira Core導入

こういったタスク管理状況を改善すべくJira Coreというビジネス・運用チーム向けのタスク管理ツールを導入しました。
Jira Coreの特徴は以下の通り。
・カンバンボードでのタスク管理
・カレンダーによる期日の管理
・詳細画面があるのでタスクの全容が見えやすい
・サマリ・統計レポート機能
・UIがイケてるぜ
イケてます。Dopeです。
どのように情シス依頼タスクをJira Coreに反映させたかをかいつまんで説明していこうと思います。
はじめに浮上した問題はSlackコマンドとGoogleフォームの両方から依頼を受け付けていることでした。依頼があれば直接Jira Coreに反映させようとしたのですが全く違う窓口が2つあったのでこれは困難だということになりました。
こういった経緯で管理用のスプレッドシートに記入された内容を取得してJira Coreに飛ばすことに。

ここでもGASを使ってスプレッドシートに記入された依頼内容が自動的にJira Coreへ送られる仕組みを作りました。便利ですねGAS。スプレッドシートからもJira Core側のタスク状況を参照できるようにリンクも挿入しておきました。
スプレッドシートの内容取得は以下のように最終行から未タスク化依頼を探索して取得しています。

次にJira REST APIを叩きました。


先ほどスプレッドシートから取得した情報をJira Coreのフィールド情報として反映されます。

結果としてタスクの全容が非常に分かりやすくJira Core上で表示されるようになりました。とても分かりやすいですね。細長いスプレッドシートの行を凝視する苦行から解放されたわけです。

Jira Coreとスプレッドシートの連携によってタスクが分かりやすく表示され、ストレス無く管理をしながら仕事が進むようになりました。
社内SEが抱える多種多様なタスクを整理することは非常に大切です。ジーニー社員の皆さんが抱える問題を解消していくためにもミスなく迅速な対応を心がけています。

6 さいごに

ジーニーに入社してからこれまでの半年間はあっという間でした。毎日様々な小さいタスクに対応して、その積み重ねの日々だったと思います。
地味で目立たない仕事も多いですが社員の声をダイレクトに聞くことができるのでやりがいも大きいです。これからもジーニー全社員がストレスなく快適に仕事していけるよう努力していこうと思います。
最後にありきたりなことを言うと入社当初なにもできなかった私を助けてくれた情報システムチームメンバーには感謝しています。先輩からのアドバイスやレビューを通して私もなんとか今に至っています。隣人を大切にするカルチャーがジーニーの良いところです。

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

大学生活につまずいていた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
エンジニアの視点から、様々な技術、サービス開発秘話、イベントをご紹介していきます。 ジーニーエンジニアチーム

はじめに

こんにちは、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
エンジニアの視点から、様々な技術、サービス開発秘話、イベントをご紹介していきます。 ジーニーエンジニアチーム

こんにちは。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
エンジニアの視点から、様々な技術、サービス開発秘話、イベントをご紹介していきます。 ジーニーエンジニアチーム

こんにちは。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_adminDBAALLdb_admine_user
operation運用チームデータ更新operation_user
engineer全エンジニアreadonlyengineer_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
エンジニアの視点から、様々な技術、サービス開発秘話、イベントをご紹介していきます。 ジーニーエンジニアチーム

はじめに

こんにちは、ジーニー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本部マーケティングオートメーション開発部の張です。ジーニーへは、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だったら、どこまでオートメーションにすると、本当にマーケターが楽になるかとか、人では思いつかない(できない)成果が得られるかとか。そういった壁を一つひとつ解決していくことが大事だと思っています。

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

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