1
/
5

データサイエンティストが実験的プロジェクトに取り組む際の初速を上げる取り組みについて

Photo by Markus Spiske on Unsplash

こんにちは! WantedlyのVisitの推薦基盤チームでインターンをしている@nasa_desuです。
今回はデータサイエンティストの生産性向上の取り組みとして、データサイエンティストが実験的なプロジェクトに取り組む際の初速を上げるための取り組みについて書いていきます。

課題背景

僕の現在所属しているMatching Squadではデータサイエンティストが実験的なプロジェクトに取り組むことや、一ヶ月ぐらいで終わる単発のプロジェクトに取り組むことがあります。
このようなプロジェクトに取り掛かるときにリポジトリを新しく作るのはコストとなってしまいます。具体的なコストとしては次のものがあります。

  • コード、issueが分散すること
  • プロジェクトの前準備段階に時間を取られてしまう

プロジェクトの前準備というのは、具体的にはCIやk8sのセットアップがあります。Wantedlyではデータサイエンティストもk8sを利用しており機械学習関連コードをk8sのpodを立てて実行しています。単発プロジェクトに取り組むたびにリポジトリを作っていると「コードをk8sのpodを立てて実行する」という必要最低限のところまで持っていくのに時間がかかってしまいます。

これらのコストのため、機械学習系の単発プロジェクトでは visit-python という1リポジトリでこれまで複数の単発プロジェクトを管理していました。

.
├── Dockerfile
├── .travis.yml
├── kubernetes
├── projectA
│ └── main.py
└── projectB
└── main.py

しかし、 visit-python では1つのDockerfileしか扱っていないため、変更がプロジェクト内に閉じないケースが有りました。たとえば「mecabのバージョンをプロジェクトAのために変えたところプロジェクトBが壊れてしまった」といったケースです。テストがあればプロジェクトBが壊れていることに気づけるのですが社内の機械学習関連のコードではテストが難しい箇所があるためテストでカバーできていないところが存在します。(ライブラリのバージョンによって精度が下がってしまう、特定のデータのときに壊れる、etc)そしていつの間にかプロジェクトBが壊れている状態が出来上がってしまいます。
また、他の問題としてDockerfileを共有しているのでpythonのversionやmecabのversionなどの影響がプロジェクト内に閉じないため使用しているソフトウェアのバージョンを上げづらいのが問題となり新しく始めるプロジェクトも古い環境で進めなければならないという制約が生じていました。

こ課題を解決するために、各プロジェクトで依存がない状態を実現する必要があります。また、前述の通りリポジトリを作成のコストも払いたくないため、1つのリポジトリ内で複数のDockerfileを扱い、ディレクトリ間の依存を取り払える状態にしようと思いました。
Wantedlyではほとんどのリポジトリが、1リポジトリがk8sの1 namespaceに対応しており複数のDockerfileを1つのリポジトリで扱うとデプロイやk8sでのオペレーションが少し難しくなってしまうという社内特有の事情もありました。

上記のような経緯があり、1つのリポジトリで複数のDockerfileを扱うかつk8s上でもきちんと動かせる状態を作ることでデータサイエンティストが単発プロジェクトに取り組む際の生産性向上を目指しました。

課題解決のためのアプローチ

複数Dockerfileを扱う上で、k8s上ではディレクトリごとにpodを立てるという方針を取りました。
その上でやるべきことは次の2つがありました

  • 複数Dockerfileからdocker imageのbuild, pushが行える
  • CIでのlintやtest

上記のやるべきこを行うことで最終的なディレクトリ構造は次のようになります。

.
├── kubernetes
├── README.md
├── sample_project1
│ └── Dockerfile
├── sample_project2
│ └── Dockerfile
├── .github
└── script

docker imageのbuid, push

やっていることは単純で、Dockerfile を含むディレクトリを探索して、各々のディレクトリでdocker imageのbuild, pushを行っています。docekr image のbuildとpushに関しては次のようなスクリプトをCIで動かすことで対応しました。

ci_build_push() {
dirname=$(echo $1 | sed 's/\.\///')

cd $dirname

docker build -t "visit-machine-learning-${dirname}:${TRAVIS_COMMIT}" .
docker push "visit-machine-learning-${dirname}:${TRAVIS_COMMIT}"
cd ../
}

directories=$(find . -name Dockerfile -maxdepth 2)

for dir in $directories; do
kube_ci_build $(dirname $dir)
done

1回CIが走るとすべてのdocker imageをbuildしてしまうため、Dockerfileが増えるごとにCIの全体時間が遅くなってしまう問題がありますが、ミニマルに実装するため今回は許容しました。

CIでのlintやtest

ここからはプロジェクトごとにDockerfileを分けたときに生じた問題の話になります。
travis ciでは複数の.travis.ymlを読み込むことが出来ず、プロジェクトごとにCIの実行環境を分けることが難しいことが分かりました。また、CIの実行時間短縮のために変更のあったプロジェクトだけlint,testを回したいという思いがありましたが、これもtravisだとCIレベルではサポートされておらずいい感じのスクリプトを書く必要がありました。そのため、他のCIも視野に入れて考えた結果GitHub Actionsが今回のユースケースではマッチしていると判断しました。

GitHub Actionsでは複数のワークフローファイルを読み込んでくれるため、プロジェクトごとにワークフローを作ることが出来、プロジェクトごとにCIの実行環境を分けるのが容易でした。また、「特定のディレクトリやファイルに差分があった場合にCIを実行する」といったことも容易に書けるので、プロジェクトが増えてもCIの時間が伸びることなく開発が進められる事が分かりました。
以下、ワークフローファイルの一部

push:
paths:
- 'project-a/**' # 差分があった場合のみCIを回すことができる。

defaults:
run:
working-directory: ./project-a

jobs:
test_and_lint:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.8]
steps:
... 以下略 テストコマンドなどが並ぶ

まとめ

今回は、データサイエンティストが短期プロジェクトに取り組む際の課題を1つのリポジトリで複数のDockerfileを扱うことで解決しました。課題としては次のようなものがありました。

  • リポジトリを作るのはコストとして高い
  • 複数のプロジェクトを1つのDockefileで扱っていると壊れやすい
  • 社内の事情で複数のDockerfileのk8sへのデプロイ、オペレーションが少しめんどくさい

これらの課題はk8s上ではディレクトリごとにpodを立てるというアプローチで解決し、実験的なプロジェクトを始める際の初速を保ちながらプロジェクト間の依存を切ることが出来ました。
以上、データサイエンティストの生産性向上の取り組みについての話でした。

Wantedly, Inc.'s job postings
15 Likes
15 Likes

Weekly ranking

Show other rankings
If this story triggered your interest, go ahead and visit them to learn more