AIチームなんでも担当の安田(@dasoran2)です。世界対戦に勤しんでいます。なにやらluigiが流行っているらしいので、一部カスタマイズをしました。本記事はluigiの本家のコードのざっくりとした(Targetに必要な部分の)概要とやり方についてです。本文中のコードはコメントの削除等いくつか加工しています。luigiについてluigiはspotifyの開発しているワークフローフレームワークです。github.com詳細や使い方などは以前他の方が書いた記事を参照してください。www.m3tech.blogluigiのファイル構成を眺める早速ですがluigiリポジトリの構成を眺めて行きましょう。luigiのリポジトリはざっくり以下のようになっています。luigiのコアconfiguration/luigiはluigiの各タスクへの設定を外部ファイルへ記述することができます。その記述はTOML形式かCFG形式で可能です。configuration以下にはtoml_parserやcfg_parserが存在し、それぞれの設定ファイルをパースするクラスが入っています。yamlを読ませたい!といった要望がないのであればここは触る必要がないでしょう。contrib/ここには、各種ミドルウェアやクラウドを扱うクラスがあります。これらはほとんどTargetです。Targetというのは、処理結果の出力先となります。普段luigiで処理を書く場合、処理ごとにTargetへ処理結果を流し込み、Targetに前の処理のデータがあるかどうかで各タスクの依存解決をします。ここにないミドルウェアや出力先を使いたい場合は自前でTargetを書く必要があります。templates/luigiにはUIが存在します。ここはそれらのコンポーネントなのでUIをカスタマイズしたい場合は触ることになると思います。今回はTargetを対象としているため割愛します。その他luigiのコア機能です。各種抽象クラスや依存解決部分等のフレームワークとしての骨格が存在します。通してluigiのリポジトリはフレームワークのコア機能以外はほとんどTarget実装で構成されていることがわかります。以下ではそのtargetを実装するための解説をしていきます。Targetの概要Targetクラスは非常に簡単な抽象クラスです。pass基本的にはexistsメソッドのみ実装すれば大丈夫です。existsメソッドはタスクの実行が必要か判断するために使う戻り値がTrueなら該当ターゲットをoutputに設定しているtaskは実行済みと判断され実行されない戻り値がFalseなら該当ターゲットをoutputに設定しているtaskはまだ実行されていないと判断され実行されるという挙動をします。複雑なTarget上のTargetを継承したもう少し具体的な抽象Targetクラスを説明します。一つはFileSystemTargetです。以下にインタフェースを示します。open等いくつかのインタフェースが提供されています。初期化のタイミングでファイルのpathを指定し、特定の1ファイルがtargetの出力先となるexistsはそのファイルが存在すればTrue、しなければFalseを返すこのようになります。existsメソッドの実装は以下のようになります。Objectではなく、luigiのtarget.py以下のFileSystemクラスです。ファイルライクなものを扱う場合はこのFileSystemTargetとFileSystemを継承したクラスで実装すると使い勝手がいいです。ローカルファイルS3GCSなどはこのクラスの具象クラスとして実装されています。いくつかのexistsの実装update_idを使用する先ほど紹介したように、ファイルであれば実行結果のファイルが吐かれているかどうかで前段の処理が完了しているか把握するのは悪い手段ではないでしょう。他方、MySQLやPostgreSQLといったものはどう扱えばいいでしょうか。テーブルの有無で表現するのはあまりいい方法とは言えないと思います。PostgreSQLを見てみましょう。Noneこれを確認すると、marker_tableというテーブルに値が入っているかどうかで判断していることがわかります。実データと処理の完了を分けて管理しているようです。この場合、特定の処理を示すユニークなkeyが必要でそれをtarget初期化のタイミングでupdate_idとして渡すような実装が多いようです。そのupdate_idがmarker_tableに存在すれば該当タスクは実行済みであると判断します。このような実装はredisでも同じで以下のように実装されています。github.com余談このような実装は汎用性は高いのですが、日付違いで実施されるbatchや時間違いで実施されるbatchの場合などを考慮するとなかなか取り扱いが難しいです。他方似たようなデータながら面白いのはBigQueryです。self.client.table_exists(self.table)こちらは上のように、シンプルにテーブルの有無で判断しています。BigQueryはRedShiftと違い、日付単位でテーブルを切る運用が主流だと考えられるためこのような実装になっているのだと思います。逆に注意しないといけないのは、BigQueryで1テーブルを更新していくようなTargetの使い方をしたいと考えた場合はluigiデフォルトのTargetクラスでは実現できないということです。まとめ今回はluigiでTargetを自分で書くために必要な全体の概要、特に重要なexistsについていくつかの解説をしました。luigiではcontrib/以下に大量の具体的な実装が存在するため、自分が作りたいものと近いものをいくつかピックアップし参考にするといいと思います。そんな思いを持って一緒に働いてくれるエンジニアを募集しています。AI・機械学習チームの笹川です。エンジニアの岩本です。サーバーサイドの足回りの試行錯誤録エンジニアリンググループの矢崎(id:Saiya)です。月別アーカイブ注目記事はてなブログをはじめよう!m3techさんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。