MicroAd Developers Blog

マイクロアドのエンジニアブログです。インフラ、開発、分析について発信していきます。

【新卒エンジニア向け】マイクロアドエンジニアの技術スタック(広告配信ユニット編)

こんにちは。 マイクロアドシステム開発部RDU(RTB Development Unit)でサーバーサイドエンジニアをしている飛田です。

この度は、主に新卒エンジニアの方や新入社員の方向けに、 数ある開発チームのうちの1つであるRDUで使っている技術について共有させていただきます。

RDUは主に広告配信周りの開発をやっているチームでして、 DSPやSSPなどの広告配信プラットフォームの開発・保守がメインの仕事になります。 以降、RDUのことを「広告配信ユニット」といいます。

広告配信ユニットで働いてみたい方やマイクロアドで使っている技術に興味のある方は参考にしていただければ幸いです。

以降、広告配信ユニット内で使っている技術スタックを紹介していきます。

プログラミング言語

【Scala】

主なプログラミング言語はScalaです。 Scalaを採用した理由は、シンプルで直感的にわかりやすいコードが書ける点です!

Scalaの良さを示すために、 例として、最もカロリーの高い食べ物の名前を求めるプログラムについて考えてみたいと思います。 まず食べ物をモデリングする必要がありますが、以下のようにクラスを定義することができます。

case class Food(name: String, kcal: Int)

Scalaにはケースクラスという概念がありまして、 case classというキーワードを使ってケースクラスを定義することができます。

ケースクラスを定義することで、Food("ラーメン", 600)といういうようにnewキーワードなしでインスタンス化することが可能になります。 また、ケースクラスではtoStringやオブジェクトの同一性に携わるcanEqualなどの便利なメソッドが自動で実装されるようになっています。

Food("ラーメン", 600).toString //  Food(ラーメン,600)
Food("ラーメン", 600) == Food("ラーメン", 600) // true
Food("ラーメン", 500) == Food("ラーメン", 600) // false
Food("ラーメン", 600) == Food("塩ラーメン", 600) // false

上記のように、簡潔にオブジェクトを文字列化したりオブジェクト同士を比較することができます。

以下のコードはFoodクラスのインスタンス化の中から最もカロリーの高い食べ物の名前を取得するプログラムです。

val foods = List(
  Food("ラーメン", 600),
  Food("カレー", 700),
  Food("そば", 400)
)

foods.sortBy(_.kcal).lastOption.map(_.name) // Some(カレー)

foodsFoodクラスのインスタンスをリストとして保持する変数です。 foods.sortBy(_.kcal).lastOption.map(_.name)のところで最もカロリーの高い食べ物の名前を求めています。

sortBy(_.kcal)のところでカロリーの低い順にソートし、lastOptionのところでリストの末尾を取得するという処理を行い、 最後にmap(_.name)のところで、食べ物の名前を取得しています。

foods.sortBy(_.kcal).lastOption.map(_.name)を分解して、パーツ毎に評価しますと以下のようになります。

scala> foods.sortBy(_.kcal)
val res2: List[Food] = List(Food(そば,400), Food(ラーメン,600), Food(カレー,700))
                                                                                                                                                                                                                                        
scala> foods.sortBy(_.kcal).lastOption
val res3: Option[Food] = Some(Food(カレー,700))
                                                                                                                                                                                                                                        
scala> foods.sortBy(_.kcal).lastOption.map(_.name)
val res4: Option[String] = Some(カレー)

いかがでしょうか。 コレクション処理が簡潔に記述できて、上記のようにコード片を綺麗に分解することができて、 直感的にわかりやすくないでしょうか?

Scalaは式を中心にプログラムを組める側面があり、 データの処理フローがわかりやすいかと思います!

また、Scalaは、オブジェクト指向と関数型プログラミングのパラダイムを融合させた言語でして、 オブジェクト指向的な実装と関数型的な実装を織り交ぜることが可能です。

マイクロアドの広告配信ユニット内では、 オブジェクト指向と関数型のいいとこ取りをするような実装を志していまして、 オブジェクト指向と関数型をバランス良く活用しながら実装をすることが多いです。

【Python】

ちょっとしたスクリプトを書く際には、Pythonまたはシェルスクリプトを利用することが多いです。 Redisのデータ移行バッチやログを集計するスクリプトなど利用用途は多岐に渡ります。

設計

アプリケーションを設計する際は、DDDやClean Architectureを参考にしています。

DDDは「ドメイン駆動設計」の略称で、ビジネスドメイン(業務領域)の理解を重視し、ドメインモデルというビジネスドメインに特化したソフトウェアモデルを中心にした設計手法です。 DDDを取り入れることで、チーム内でのコミュニケーションや保守性の向上、開発速度の向上など、様々なメリットが得られます。

広告配信ユニット内ではDDDベースで開発することが前提になっていまして、特にコミュニケーションのしやすさで大きなメリットを感じています。 特に新しくメンバーがジョインした際は、DDDによって、コミュニケーションコストをかなり削減できているという実感があります。

アーキテクチャはClean Architectureを参考にしつつ「三層 + ドメインモデル」を採用することが多いです。 広告配信ユニット内では「三層 + ドメインモデル」の採用事例が多く、枯れた技術になっております。

CI/CD

継続的インテグレーション(CI)ツールとしてはJenkinsを、GitリポジトリにはGitHub Enterpriseを利用しています。 git push起因で自動で単体テストの実行とコンフリクトのチェックがされるようにしています。

Jenkinsで単体テストを実行することにより、 単体テストの実行し忘れを防げたり、テスト結果がローカルPCに依存してしまう問題を解決できます。

継続的デプロイメント(CD)ツールとしてはAnsibleおよびAnsible AWXを利用しています。 デプロイ対象のサーバーは多くて数百台になるケースもありますので、自動でデプロイする仕組みは必須になります。

監視および可視化

可視化ツールとしましては、GrafanaやKibana、 監視ツールとしましては、PrometheusのAlertmanagerやElasticsearchのElastAlertを使用しています。

具体的な利用事例は以下のようになっています。

ツール 用途
Grafana RedisやKafkaなどのデータ基盤の監視、サーバー監視
Kibana アプリケーションのログやビジネス上重要なログの可視化
Alertmanager サーバー監視のアラート
ElastAlert アプリケーションのエラーログや障害のアラート

ミドルウェア

【Docker】

こちらは言わずもがな利用しています。 一部古いアプリケーションはDockerなしで動くようになっていますが、 基本的に新しく作成されたアプリケーションにつきましては、Dockerの上で動くものが多いです。

アプリケーションをDockerコンテナ上で動かすことにより可搬性が向上しますが、 特定のアプリケーションを様々なサーバーで動かすことがありまして、 コンテナのデプロイしやすさや運用しやすさ等にメリットを感じています。

また、Dockerコンテナにより物理サーバーとの結合度合いが低くなり、 Dockerのコンテナ内のOSの設定変更がしやすい等のメリットを感じています。

例えば、コンテナ内のOSのタイムゾーンを変更してアプリケーションの検証をしたり、 コンテナ内にバイナリファイルを配置してアプリケーション側から呼び出したりすることがあります。

これらの作業を直接サーバー内で行いますと、 タイムゾーンの設定が汚染されたり、ディレクトリにファイルが散らかったりすることがあります。

これらの作業をDockerコンテナ内に閉じ込めることにより、運用が楽になっています。

【MySQL】

MySQLは広告配信ユニットで使用している主なRDBMSになります。 広告配信ユニット内ですと、広告配信の制御のためのデータをMySQLに格納しています。

具体例をあげますと、 自動車に興味を持っているユーザーに広告配信するための設定や、 特定のデバイスに広告配信する(スマホには配信するがPCには配信しないなど)などの設定を保持しています。

多くて2~3件のテーブルしかJOINしないような単純なSELECT文を書いたりすることが多いですが、 レコードの多さがパフォーマンス面でボトルネックになるケースもありまして、MySQLのデータを利用する際はパフォーマンスの考慮をする必要があります。

【Redis】

Redisとは高速で動作するキーバリューストアです。 以下のようにキーとバリューを保存できます。

127.0.0.1:6379> SET key_AAA value_BBB
OK
127.0.0.1:6379> GET key_AAA
"value_BBB"

こちらはリアルタイムで蓄積する必要のあるデータを格納するために利用しています。 データ量もかなり多く、Redisクラスタによっては、20億件ほどのキーを保持していたりすることがあります。

【Spark】

こちらは、オープンソースの並列分散処理系ミドルウェアでして、 広告配信ユニットでは、リアルタイムストリーミング処理で利用しています。 ユースケースとしましては、KafkaやRedis上に存在するデータを参照したり、加工したりすることが多いです。

広告を見たユーザーのWebサイトへのアクセス履歴情報や広告配信実績等の大量のデータを Sparkを使ってリアルタイムに処理したりしています。

広告配信ユニットで使っている技術まとめ

以下、広告配信ユニットで使っている技術を思いつく限りリストアップして見ました。 ご参考になれば幸いです!

  • 言語
    • Scala
    • Java
    • Python
    • SQL(MySQL, HiveQL)
  • データベース等
    • MySQL
    • Hive
    • Redis
    • Kafka
  • 設計
    • DDD
    • Clean Architecture
  • ミドルウェア・ライブラリ等
    • Docker, Kubernetes, Rancher
    • Akka
    • Spark
    • Fluentd
  • CI/CD・監視ツール等
    • Jenkins
    • Ansible
    • Kibana
    • Grafana
    • Cacti
    • yamory
  • バージョン管理
    • Git

終わりに

ご覧いただきありがとうございます! マイクロアドでは「広告配信システムをつくりたい!」 「Scalaをガンガン使っていきたい!」 というエンジニアを積極的に募集しています。 ご興味を持たれた方はこちらから是非ご応募下さい!

recruit.microad.co.jp