関する取り組みをご紹介するブログです。Airflowデータプラットフォームチームの橋本です。日頃は分析基盤LivesenseAnalytics(LA)の保守・運用を担当しているエンジニアです。最近は専ら、バッチ処理のリファクタリングを懇々と進めていました。今回はその内容をまとめてみます。分析基盤のバッチ処理LAのバッチ処理基盤についてはこのへんを見ていただくとして、私が着手しているのはその中でもairflow-workerと呼称しているバッチ処理の集まりになります。その昔、Airflow導入以前に、cronでバッチ処理を運用していた頃から使い続けているrakeタスクの集まりで、類似の分析基盤を保守しているプロジェクトではよくある感じのバッチ処理集になっています。AirflowはDAGで設定された定時、ないしは任意のタイミング(ユーザーからのキック)で、airflow-workerのrakeタスクを起動します。この時、起動されるrakeタスクのIOは、概ね以下のようになります。ところで、このairflow-worker。cronで運用されていた時代から引き継いだタスクの集まりで、チーム内では追加・修正の頻度が最も多いコードの1つなのですが、その分歴史と修正の積み重ねも厚く、多くの問題を抱えるようになっていました。"development"エラーハンドリングと通知dryrunできない・実装を逐一書かねばならないなどなどです。拡張コストの増加バグの温床化といったデメリットが考えられたので、思い切って大規模なリファクタリングを敢行しました。リファクタリングの方針と改善のポイントまず次の2点を大まかな方針としました。細かい問題点はたくさんあるが、大きな問題を優先して期間内に終わらせる理想形には近づけるが、完璧にしないリファクタリングの作業自体は重要な仕事ですが、理想形を目指し続けると際限がないので、とにかく大きな問題点をザックリ改善し、今後の継続的なコード改善への足がかりにする方針としました。上記のIOの図の通り、定時のバッチ処理は概ね日時やタスク名、スキーマ名などのパラメータを受けてタスクがキックされるテンプレートからクエリを発行したり、日付の範囲をとったAPIに投げるパラメータを作る実際に処理を行うマネージドサービスのAPIを呼び出す、あるいはクエリを投入するの3ステップで構成されています。そこで、バッチ処理のコンポーネントを図のように3つに分割しました。改善のポイントはコンポーネントの構成を整理rakeタスクの処理を別のクラスへ委譲:rakeタスクとLATaskクラスバッチ処理のインターフェースとロジックを分離するロジックに対してユニットテストを導入する余地を作るAPIアクセスをFacadeにまとめる可能な限りAPIアクセスをdryrunできるようにする分割しにくい部分や広域なスコープで共通化された処理等は、思い切ってUtilsとして切り出すとしました。以下、その内容をもう少し具体的に見ていきます。クラスとパッケージの構成クラス構成はザックリ上記の様な感じにしました。モジュールは大きく4つに分けて、以下の構成にしました。APIAPI呼び出しを行うFacadeのモジュールdryrunの機能をここに集約するこのパッケージのクラスは状態を保持しないようにするLATask処理の実装を持つクラスのモジュール親クラスにエラーハンドリングやロギング、環境変数の参照などを集約するrakeタスクrakeタスクは各々のタスクに対応するLATaskクラスへ処理を委譲するLATaskのインスタンスを呼び出し、パラメータを渡して処理を実行するだけUtilsバッチ処理全体で頻発する処理や、分割しきれなかった塊をとりあえず置いておくモジュール状態を保持しない、関数の集まりにしておく以下、rakeタスクからの処理フローを、サンプルのソースコード上で追いかけてみます。endrakeタスク上では引数のチェックすらせず、LATaskクラスに処理を丸投げします。この様にrakeタスクの名前と実装を切り離しておくと、後から実装の差し替えや改名等がしやすくなります。endendLATaskクラスの親クラスには、ロギングやエラーハンドリング、通知、環境変数チェック等、全タスク共通で頻発する処理をシンプルにまとめておきました。これにより、子クラスの実装がかなりスッキリしました。endendLATaskの子クラスに、タスクの実装を書きます。この時、execに渡すプロック内で全てのタスクの処理を記述することにより、ロギング、エラーハンドリングを親クラスの実装に一元化できます。パラメータのチェックを行う場合も、ここに記述します。タスクのロジックをrakeタスクにベタ書きせず、クラスに切り出した事で、各々の関数に対してテストが書けるようになりました。endAPIの親クラスに、dryrunの処理を実装しています。することで、LATaskのロジック本体からdryrunに関連する処理を取り除けるようになり、コードが簡潔になりました。"ap-northeast-1"endcheck_dry_run_thenのブロックで、APIにアクセスしています。dryrunの際にはこのブロックの処理が実行されず、代わりにcheck_dry_run_thenの引数で与えられる文字列が表示されます。他のAPIアクセスについても同様に記述しておくことで、簡単にdryrunが実装できます。endendシステム横断でよく使われる処理をまとめた関数群ですが、なるべく使わないようにしています。リファクタリングを途中までやってみて下記の様なメリットを感じつつあります。全体が一貫性を持った構成、書き方で統一できた(可読性・保守性の向上)テストを書く余地ができたディレクトリ構成でコンポーネントが一目瞭然(もうgrepしない)LATaskクラスにシンプルなロジックを記述するだけでも、概ね全部のタスクにdryrunモードが実装できそうエラーハンドリングとロギングが集約できた当初、課題として挙げていた部分はまずまず解決できそうな見込みです。一方で、まだイマイチな実装が細々とたくさん残ってる本当は思い切って名前を変えて、タスクの分類体系も再編したい想定通りとは言え、分解しきれずUtilsに残ってしまった塊といった点が、課題として見えてきました。まとめサンプルにある通りかなりシンプルな実装ですが、コードの見通しの良さを保ったまま課題としていた事を概ね克服しつつあるので、良かったと感じています。課題の本質を、しっかり見定めることが同様に大切という学びとリファクタリングの経験を得られました。新しいことも奇抜なこともないリファクタリングでしたが、よい学びと経験が得られたと感じています。09:00Tweet関連記事2019-07-03CloudFormationでコンテナビルド用パイプラインをつくるテクノロジカルマーケティング部の橋本です。Machinesをレコメンデーションで使うときの評価推定値計算こんにちは、リブセンスで統計や機械学習関係の仕事をしている...2019-03-05DigdagとEmbulkで行うDB同期の管理データプラットフォームグループの松原です。よるコンテナネイティブなデータパイプラインのワークフロー管理データプラットフォームグループの野本です。Inc.LIVESENSEはてなブログをはじめよう!livesense-blogさんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。