フロムスクラッチ開発者ブログ

from scratch Engineers' Blog

Akka Stream&クリーンアーキテクチャによるバッチ処理開発のススメ

はじめに

こんにちは。 フロムスクラッチでエンジニアをしている、関口と申します。 普段は、b→dashのログ基盤周りを中心に担当しています。

さて、弊社が運営するマーケティングクラウドサービス「b→dash」では Web サーバ周りを Rails で開発する一方で、 バッチ処理やツール群を golang、Java、Scala を用途や開発者のスキルに合わせて適宜選択して採用しています。 私自身は昨年の夏に Scala で実装されたバッチアプリケーションとしてのログ基盤を引き継ぎ、前Qは累積した負債を解決するためにリプレイスを計画し、その前段階とも言える新規のバッチ処理を Akka-Stream をバッチフレームワークとして採用し開発したので、そのアプリケーション開発での経験を少し書いていきたいと思います。

よろしくお願いします。

そもそもの課題について

ログ処理基盤

開発したアプリケーションは弊社が提供するレコメンド機能が生み出すレコメンド結果をログデータから取り込み Hive 、 Presto から参照可能な Orc ファイルのデータに変換するバッチアプリケーションです。

前身としてクライアントのサイトに仕込んだタグからログデータを飛ばして集積する Web ログの処理基盤があり、レコメンドログは概ね仕組みとしてはこれと同じ仕組みで出力されますが、配信メール、LINE、SMS といった他の種別のログと同様に Web とは処理系統を素にする事が決まっていました。

ところが、この Web ログの処理系統そのものが初期の想定を超えてアプリケーションの処理効率を落としており、そのリプレイスに先んじてレコメンドを新規のフレームワークでスケールしようという魂胆がありました。

課題概要

という訳で解決を目指した課題は以下の通り。 パフォーマンスに直接関わる課題が2点、保守の生産性に関わる課題が1点です。

  1. 出力データのデータ構造の悪さ
  2. そもそもの変換・集計処理が直列に実行される事による処理効率の悪さ
  3. 当初想定から要件が変更になった過程での大きなリファクタリングを避けた結果としての内部構造の見通しの悪さ

これらの課題のために、毎時に実行されるバッチ処理が月間 1000 万 PV に及ぶ某クライアントのアカウントでは非キャンペーン時でも毎時 60 分を超えてしまい翌日のための集計処理のための要件に深刻な影響を与えていました。

「1. 出力データのデータ構造の悪さ」がボトルネックとしては特に深刻で、主要なアカウントでは差分を集計した後の既存データとのマージ処理だけで1回のバッチ処理の三分の一に及び、根本的にはここを解決しなければ非機能要件をクリアできない事は明白でした。

「2. 変換・集計処理が直列に実行される事による処理効率の悪さ」はこれはこれで改善の余地が大きく、機能追加の過程で少し Future1 を利用した非同期処理に書き換えてやるだけで最大級のクライアントに至っては1回のバッチ処理にかかる非キャンペーン時の所要時間がざっくり10分程度軽減できたため、複数段階の集計処理を十分安全に非同期化する事ができれば大幅なパフォーマンス改善に繋がりそうです。

「3. 大きなリファクタリングを避けた結果としての内部構造の見通しの悪さ」は機能追加や問題発生時の検証作業に当たれる人間を限定してしまっており、ログ基盤周りの発展を遅延させていました。設計レベルのスケーラビリティが低く当初想定のアカウント、データビュー (サブアカウントに近い概念) といった大きなくくりを超えたスケールに対応できずためこれも解決すべき課題と言えます。

つまりは ?

  1. 出力データの構造を Orc ファイルという形式に合わせて再検討し
  2. アプリケーションの設計を十分抽象化してスケーラビリティを高め
  3. 集計過程を安全に非同期化する

パフォーマンス改善の必要があったというわけです。

Akka-Stream とは ?

採用にあたって

課題を解決するために利用を検討したバッチフレームワークが Akka-Stream です。 その他、同じ Scala を使うにしても Hadoop や Spark といったフレームワークも候補として検討はしましたが、レコメンドログの処理自体に必要な要件としては EMR を使った分散処理とするのはコストとして過剰になると判断しました。

Akka-Stream 自体はそもそも Spark の実装に用いられた Java / Scala 用のリアクティブストリームフレームワークです。経緯的にはその後 Akka-Stream の低レイヤーを実現していた Netty に Spark 側は置きかわり、一方 Akka-Stream はというとその低レイヤーの実装を Spray.io に変更しています。2

そういった経緯で作られてきたものなので基本的に Spark のような分散処理で実装したいものは Akka-Stream で実装する事は可能であるという前提がありました。アプリケーションの規模感や Spark の枠組みよりも柔軟な処理を書きたい場合の選択になるという予測で、今回の場合は処理内容的にもシンプルなので Spark を利用するメリットは薄めという所感でした。

サクッと説明する

では Akka-Stream 自体は何を可能にしてくれるフレームワークかと言いますと、処理系をストリーミング処理を構成するグラフコンポーネントの集合で表現することを可能にしてくれるものです。詳しくは既に様々なドキュメントが起されているのでそちらに頼りましょう。3

グラフコンポーネントは大別すると以下の三種から成り立ちます。

  • Source
    • 入力部
    • キューや InputStream 、イテレータなど様々な入力装置からメッセージを受けて処理系にメッセージを与える事ができる
  • Sink
    • 出力 (終端) 部
    • グラフの処理を完了させる
    • 結果を出力させるか (Future[A]) させずに完了のみを通知するか (Future[Done]) は任意
  • Flow
    • 中間処理部
    • Source から Sink を繋ぐ処理系統の主軸で、新しい Source や Sink を作成できる
      • Source + Flow = Source
      • Flow + Sink = Sink
      • Flow + Flow = Flow
    • I/O はメッセージに対して1対1に限らず、ブロードキャストやZIP、UNZIP、マージなど分岐、合流が容易に可能。

簡単にレコメンドログ処理に沿って言ってしまえば、 S3 にアウトプットしたアクセスログファイル (gz) をインプットとして Orc ファイルに書き出すもので、 S3 からのインプットを InputStream を入力にした Source 、Orc への書き出し処理を Sink にして、アクセスログの読み込みやバリデーション、変換処理を Flow に連ねる事になります。

どんなアーキテクチャで ?

もちろん ? クリーンアーキテクチャで 4

採用したアーキテクチャは現代ではマイクロサービスやドメイン駆動設計の需要に伴いもはや概念としては定着した感もあるクリーンアーキテクチャです。 5

シンプルなバッチ処理をクリーンアーキテクチャに寄せすぎてしまうのは逆に過剰かなといういう感覚もあったのですが、 弊社の Web サーバ自体 Rails でクリーンアーキテクチャをベースとした設計を実現しているため基礎の相性は悪くありませんでした。

課題の一つである構造の見通しの悪さもビジネスロジックとインフラ系をはっきり区別する事で改善できる自信がありましたし、何より自分自身のキャッチアップの成果を測る機会として、クリーンアーキテクチャを採用しました。

4層構造

  • domain
    • models
    • repositories
    • services
  • application
    • usecases
    • services
  • infrastructure
    • form
    • gateway
    • repositories
    • utils
  • presentation
    • main
    • params
  • (utils)

レイヤはベースとして4層に分け、ビジネスロジックを極力抽象でドメイン層に記述し、具象をアプリケーション層に書いてインフラ層との中立を任せ、データの入出力の実態や実データからモデルへの変換処理などは極力インフラ層に吐き出すという形で記述しました。 presentation 層は今回はバッチ処理のためのパラメータをコマンドラインから渡して起動する事を前提としていたので CLI から実行するためのメイン関数周りの記述を集約しました。(ここに関しては大いに解釈が誤っているかもしれません。。。)

書ける限りの要素をドメイン層とインフラ層に寄せた事で、アプリケーション層とプレゼンテーション層は最低限の処理の取りまとめに集中する事が出来て、ドメイン層では境界付けられたコンテキスト6の外側を意識しないビジネスロジックの記述に、インフラ層ではデータの読み書きの実態を柔軟に書く事が出来た手応えがありました。

Akka Stream をどう使ったの?

主として

Akka-Stream は主にアプリケーション層に記述した Usecase とインフラ層での I/O 処理の記述に利用しました。 Usecase#execute のリターンは Runnable グラフの実行結果である Future です。7

クリーンアーキテクチャでは原則フレームワーク側の都合などをインフラ層で吸収するのが一つの哲学になるため、どこまで Akka-Stream のインタフェースを干渉させていいかはかなり迷ったのですが、そもそも Usecase をグラフ構築の取り纏め役と定めた時点でアプリケーション層に干渉することは明らかでしたので、まずはインフラ層、アプリケーション層までで止めるよう努めてみるのが良いだろうと考えました。

ドメイン層に抽象を置くリポジトリなどは具象を記述するインフラ層的には Source や Flow をリターンしたくなりそこそこに妥協をしてしまった部分がありますが、そういう部分こそアプリケーション層に書き出してしまう方がまとまっただろうなと反省しています。

役割として

Akka-Stream が果たした役割はドメイン層からアプリケーション層にかけて記述したビジネスロジックの流れを Flow に取り纏め、 I/O をインフラ層とやりとりする処理系統の制御・構築です。

ドメインサービスやアプリケーションサービスはまず基本的には Value/Entity オブジェクトをインプットとして何らかの結果をアウトプットする手続きモデルです。 Value/Entity オブジェクトそれ自体にサービスを呼び出すような機能は無いため、動線は別のクラスで引いてやる必要があります。その動線が Akka-Stream の Flow であり、動線の構築を担うのがアプリケーション層です。

リリースの後に

後に、と言っても実はまだまだ満たさなければならない機能があり俺たちの戦いはこれからだ!状態なのですが。。。

数値を出せないのが大変心苦しいところなのですが、アプリケーションをステージング環境にリリースして検証作業を実施してみたところ、IPO が Akka-Stream を利用して並列化した事で Web の処理系統と比較して安定してパフォーマンスを発揮できるようになりました。ボトルネックの確認やバグと思しき挙動の調査検証も設計上のフローが明確になった事で分かりやすく、調査ポイントが幾分絞りやすくなったと思っています。

プロジェクトを一から構築し直し再設計を適用、要所要所で用いるライブラリも自分自身で選定してリリースに持ち込めた事で、個人的には粗がありつつも満足度の高いアプリケーションになりました(ほぼ独学の状態からScalaによるログ基盤の開発を任せてくれた開発チームに感謝です...!!) 今後も、ログ基盤としてももb→dash全体としても改善を続けて、その折々でブログの形でご報告できればと思います。 それでは、また。


  1. Future/Promiseについて (ドワンゴさんの研修資料)

  2. Migration Guide 2.1.x to 2.2.x (Akka 公式ドキュメント)

  3. Akka Streams についての基礎概念 (Qiita記事 @xoyo24)

  4. 実装クリーンアーキテクチャ (Qiita記事 @nrslib)

  5. Clean ArchitectureでAPI Serverを構築してみる (Qiita記事 @hirotakan)

  6. 境界付けられたコンテキスト 概念編 - ドメイン駆動設計用語解説 (Qiita記事 @little_hand_s)

  7. charwork社 あくまで個人的にではありますが、 Akka-Stream を使ったアプリケーションの実装については chatwork 社の皆様にご意見を伺う機会があり、 Usecase のリターンを RunnableGraph にするのは一つの手だ、というものを含め様々なアドバイスを頂きました。方針を検討する上で大いに参考にさせて頂きました。この場を借りて改めて謝辞を述べさせて頂きます。

【開催レポ】b→tech Lab. #1 - 詳説ビッグデータアーキテクチャ -

こんにちは!
フロムスクラッチ新卒エンジニアのおんじです。

昨日9/19(水)、弊社主催のエンジニアイベント b→tech Lab.の第1回を開催しました。
今回のブログでは、その様子をお伝えします!

b→tech Lab.は、ビッグデータをメインストリームとするエンジニアが、独自の取り組み事例やナレッジ・ノウハウを共有する、双方向型コミュニティです。
fromscratch.connpass.com
第一回の今回は、詳説ビッグデータアーキテクチャと題して、
ビッグデータを扱う上で避けられない3つの課題について、一部(LT)と二部(Discussion)の二段階構成で、お話しました。

f:id:kota-onji:20180920120834p:plain


【第一部 Lightning Talk】

第一部は、先程お話した3つの課題それぞれについて、フロムスクラッチが取り組んできた内容を、LT形式でお伝えしました!

「おれのセグメントがこんなに早い訳がない」by 開発チームmgr 原口

1人目は、弊社開発マネージャーの原口が、「パフォーマンスに関するアプローチ」として、セグメント集計のhive処理を高速化するために行ったことhttps://www.slideshare.net/secret/uUU3BgNJaLGSE0、について講演しました。
f:id:kota-onji:20180920201449p:plain
f:id:kota-onji:20180920195810p:plain
集計時間を約3分の1に削減させるために何が重要だったのか、そのエッセンスが詰まった内容になったかと思います!

「b→dashを支える監視システム」by インフラチーム髙須

2人目は、「運用に関するアプローチ」として、弊社2年目でインフラエンジニアの髙須がb→dashの監視システムについて。
https://www.slideshare.net/secret/D1qWqgHfMmWmYm

f:id:kota-onji:20180920200615p:plain
f:id:kota-onji:20180920195258p:plain
1000を超えるサーバ上で稼働しているb→dashのシステムの安定稼働はいかに実現されているのか、実際に導入しているツールの話を交えながらお話しました!

「大規模分散処理を扱うためのクラスタ管理システム」by CTO井戸端

3人目は、弊社CTOの井戸端から、「コスト削減へのアプローチ」として、弊社の分散処理を支えるクラスタ管理システム(通称:Resource Manager)
https://www.slideshare.net/secret/b9RJgRBkClJB73についてお話しました。
f:id:kota-onji:20180920195621p:plain
f:id:kota-onji:20180920200045p:plainf:id:kota-onji:20180920200751p:plain
こちらは、AWS Summit TokyoのStartup Architecture of the year 2018で最優秀賞を受賞したコンテンツということもあり、特にご盛況頂きました!

【第二部 Discussion】

続く第二部では、「パフォーマンス」「運用」「コスト」のテーマごとにテーブルを分けて、軽食を交えた座談会を行いました!
Discussionの時間では、LTの内容に関する質問や、b→dashで使われている他の技術の話もあれば、おぎやはぎのプロモーションに関する裏話など、より深堀ったお話をお楽しみ頂けたかと思います!

おわりに

イベント終了後のアンケートでも「内情を隠さずさらけ出してくれていて、大変満足のいくコンテンツだった。」「内容が良かった。知人にも紹介したい。」とのお声を頂き、
b→tech Lab.初回はなんとか成功と言えるのでは無いかと思っています。

また、第二回b→tech Lab.の開催は11月中旬を予定しております。
今後のb→tech Lab.では、「前処理」「分析基盤」「レコメンデーション」等を扱っていくことを予定しており、また、外部の方のLTや一般公募でのLTも予定しております!

今後のb→tech Lab.に関するご案内は、connpassのフロムスクラッチのイベントページから周知致しますので、こちらのメンバー登録も宜しくお願い致します!
fromscratch.connpass.com

皆様に次回イベントでお会いできることを楽しみにしております!
宜しくお願い致します。

CTFのSQLインジェクションから学ぶセキュリティとハッキング vol.1

 今回のフロムスクラッチ開発ブログでは「CTFのパケット解析から学ぶセキュリティとハッキング」に続いて、SQLインジェクション編の勉強会について書きたいと思います。
 ※CTFパケット解析編のバックナンバーは、
CTFのパケット解析から学ぶセキュリティとハッキング vol.1 - フロムスクラッチ開発者ブログ
からどうぞ!

1.CTFとは?

 CTFとは情報技術に関する問題に対して適切な形で対処することで、「フラグ(Flag)」と呼ばれる得点対象の文字列を取得することによって、得られた得点で勝敗を決める大会です。”Capture The Flag”の頭文字をとってCTFといいます。国内ではSECCON CTFが有名です。(CTFについては、こちらの記事で詳しく触れています。)
 

2.SQLインジェクションとは?

 みなさんは、情報漏洩に対してどれくらい対策を取れていますか?

 Amazon GOなど、全てをデータで管理しようとする昨今の社会の流れがあります。より管理が楽になったり、適切なマーケティングができたりと良い面ばかり取り上げられますが、一方で危険性も孕んでいます。
その危険性の一つである情報漏洩、とりわけSQLインジェクションによる情報漏洩に関して、今回は扱います。

 2011年ソニーグループが7700万人の個人情報を漏洩させてしまいました。
これは、ソニーグループに対する標的型SQLインジェクション攻撃によるものと考えられています。SQLインジェクション攻撃による情報漏えいの中でも最大規模のものであり、SQLインジェクションへの対策の重要性が広く認識された事件でした。

ソニー・ミュージックの日本語サイトにSQLインジェクション攻撃? - ITmedia NEWS

 機密情報、個人情報を少しでも扱うのであれば、SQLインジェクション対策をしておくことがいかに大切かお分かりいただけたのではないでしょうか。
 そこで、今回はSQLインジェクションの仕組みを紐解くことで、対策を考えていきたいと思います。

 SQLインジェクションとは、アプリケーションが想定しないSQL文を実行させることにより、データベースシステムを不正に操作する攻撃方法のことです。
一定規模以上のアプリケーションを作るとき、必要になってくるのがデータベースです。いろいろな種類がありますが、現在も多くのWebアプリケーションのバックエンドで使われているのがリレーショナルデータベースです。
そして、アプリケーションのデータ管理を行う際、SQLを中心にプログラミング言語やフレームワークのAPIを用いてリレーショナルデータベースマネジメントシステム(RDBMS)を操作します。そして、このSQL文を書き換えることによって、データ管理者が予期しないデータの漏洩・改ざん・削除などを行うことができます。

 では、実際にどのように書き換えを行うのか、SQL構文をおさらいしながら見ていきましょう。

3. データ操作を行う言語、SQL

 SQLは、ISO(国際標準化機構)で規格化されたデータベースを管理するソフトウェアを操作・制御することが目的のデータベース言語です。

a.SQLの3つの言語

 SQLは、大きく3つの言語に大別することができます。
1つめは、SQL-DDL(Data Definition Language:データ定義言語)です。データベースやテーブルを作成するときに使用します。
2つめは、SQL-DML(Data Management Language:データ操作言語)で、データを操作する時に用いられます。データを操作するとは、例えば、テーブルからデータを取得したり、テーブルにデータを追加したりすることです。
3つめは、SQL-DCL(Data Control Language:データ制御言語)です。データの整合性を保つための機能であるトランザクション制御などに使用します。
それぞれの言語と言語で使われる代表的な構文は以下の通りです。

f:id:ayumi-inukai:20180528121252p:plain

この中で、SQLインジェクションではSQL-DMLを利用します。

b. メタキャラクタ

 「メタキャラクタ」とは、コンピューター言語において、特別な意味を持つ文字のことを指します。単体、もしくは複数の文字が組み合わさって別の意味を持ちます。
SQL文におけるメタキャラクタの代表例は「%(ワイルドカード)」でしょう。
0個以上の任意文字を現し、例えば、以下のSQL文は、名前に「hoge」を含む利用者のみ抽出しています。

SELECT * FROM users WHERE username LIKE '%hoge%';

また、この時の「’(シングルクオート)」もメタキャラクタの一種であり、シングルクオートで挟まれた文字はそのまま文字列として扱われます。
このシングルクオートはSQLインジェクションに多く用いられています。

例えば、以下の2つのSQL文を見てみましょう。

① SELECT * FROM users WHERE username = 'hoge';
② SELECT * FROM users WHERE username = 'hoge' OR 'ab' = 'ab';

①の場合、名前が「hoge」の利用者を抽出します。
一方②の場合、「ab=ab」は常に真であるため、全ての利用者を抽出します。
このような①から②のような書き換えを行うのがSQLインジェクションです。
すなわち、「メタキャラクタ」を用いてSQL文の構造を破壊し、元々あったSQL文を開発者・運営者が意図しないSQL文に改変し、実行させるのがSQLインジェクションの基本原則です。

4. SQLインジェクションでできること

 さて、SQLインジェクションで一体何が可能になるのでしょうか。ここでは、主な被害である、「データ窃取」「データ改ざん・削除」を取り上げます。

a. データ窃取

 任意のSQL文を発行することで、データベース中の機密データを抜き出すことができます。
様々な手法がありますが、ここでは表連結を使ったデータの抜き出しを扱います。検索機能を持つSQL文をUNION句を用いて書き換えを行います。

具体的に見ていきましょう。
次のようなアプリケーションを考えます。

 /***** 脆弱なコード例 *****/     
  
 USE db_users

 SELECT value FROM data WHERE value LIKE '%{$search}%';
           
 /* データベースへの接続解除(省略) */

例えばURL経由でパラメータ"search"にhogeという文字列を渡したときに取得される表はこのようになるとします。

f:id:ayumi-inukai:20180528122309p:plain

このとき、テーブルdata以外に次のようなusersというテーブルがあったとします。

f:id:ayumi-inukai:20180528122446p:plain

パラメータ"search"に

 hoge' UNION SELECT id||':'||name||':'||password FROM users -- 

という文字列を渡すと、次のような表が取得されます。

f:id:ayumi-inukai:20180528122506p:plain

結果として、別のテーブルのデータを取得できます。

b.データ改ざん・削除

 複文を実行できる環境では、DELETE文やUPDATE文を追加することによって、データの改ざんや削除を行うことができます。
例えば、先のデータ窃取で使ったサンプルで、仮に複文が実行可能だとします。パラメータ "search"に';DELETE FROM users;--という文字列を渡すとテーブルusersのデータが削除されます。
仮に、アプリケーションでユーザーのデータが削除されてしまうと、そのユーザーはアクセスできなくなり、非常に困ってしまいます。


 さて、SQLインジェクションの基本情報を確認したところで、例を用いてより具体的にSQLインジェクションの行い方をご説明したいと思います。
続編として掲載いたしますので、ご覧いただけましたら幸いです。



 本記事は、前述のとおりSQLインジェクションの攻撃方法を理解することによって、実際のセキュアなシステム開発に生かそうとするものです。
実際のSQLインジェクションの実行等においては法律などに抵触しないように気をつけてください。