INTRODUCTION
「大量データを扱う」という前提がなかった
この話は、 pandasの使い方を最初から勉強して データ処理を設計した、 という話ではありません。
もともとあった移行ツールを 引き継いだところから始まりました。
そのツールは、 もともと 1件のデータを投入する ことを前提に作られていました。
ところが、 実際の移行では大量のデータを扱う必要がありました。
大量データをそのまま処理すると、 約2年半かかる。
ここで初めて、 「この処理を大量データ向けに作り直さないといけない」 という問題がはっきりしました。
01
まず、データをまとめて扱う
大量データを扱う場合、 1件ずつ処理するだけでは 全体を把握しづらくなります。
そこで、 CSVなどから読み込んだデータを DataFrame として扱いました。
import pandas as pd
df = pd.read_csv("data.csv")
DataFrameを使うことで、 行と列を持ったデータを 一つのまとまりとして扱えます。
例えば、
ID Longitude Latitude
1 139.7000 35.6000
2 139.7100 35.6100
3 139.7200 35.6200
のようなデータを、 Pythonの中で表形式のデータとして扱えます。
「1件ずつ処理する」から、 「大量のデータを一つの集合として扱う」 という考え方に変えていきました。
02
pandasを使えば速くなる、という話ではない
ここは重要なところです。
pandasを使ったから、 それだけで処理が劇的に高速化した、 という話ではありません。
大量データを扱う上で、
というように、 データ処理全体を分解して考える必要がありました。
pandasは、 その中で 大量データを扱うための土台 として使いました。
03
DataFrameをどうやって処理単位に分けるか
大量データを一つのDataFrameとして持っただけでは、 並列処理にはなりません。
そこで、 データをいくつかの処理単位に分割し、 それぞれを別の処理へ渡すという考え方にしました。
大量データ
↓
DataFrame
↓
処理単位に分割
↓
複数の処理へ渡す
この考え方が、
後の
ProcessPoolExecutor
による並列処理につながっていきます。
つまり、 pandasを使うこと自体が目的ではなく、
大量データを「処理しやすい単位」に 扱えるようにする。
ということが重要でした。
04
データ処理を細かく見る
大量データを処理する場合、 「Pythonの処理」と一言でまとめてしまうと、 どこが遅いのか分かりません。
そこで、 処理をいくつかに分けて考えました。
こうして分けて見ることで、 「どこを改善すれば全体が速くなるのか」 を考えやすくなりました。
実際の性能改善では、 一番時間がかかっている場所を見つけ、 そこに対して改修を行うことが重要でした。
05
CSVの読み込みも無視できない
大量データを扱うようになると、 データを加工する処理だけではなく、 データを読み込む時間 も無視できなくなります。
そこで、 CSVの読み込みについても調べました。
df = pd.read_csv(
"data.csv"
)
さらに、
pandasのCSV読み込みについて
PyArrow
を利用する方法も調査しました。
df = pd.read_csv(
"data.csv",
engine="pyarrow"
)
大量データを扱う場合、 「計算処理を高速化すれば終わり」 ではありません。
読み込み、
加工、
分割、
並列処理、
出力・投入。
全体のどこで時間を使っているのかを 見る必要があります。
06
大量データでは「メモリ」も問題になる
データを大量に扱う場合、 CPUだけを見ていても問題は解決しません。
DataFrameに大量のデータを載せれば、 当然メモリも使用します。
さらに、 データを複数の処理に分割して 並列処理を行う場合、 それぞれの処理がメモリを使用します。
CPUをもっと使いたい
↓
並列数を増やす
↓
メモリ使用量も増える可能性がある
そのため、 「CPU使用率が低いから、 とにかく並列数を増やそう」 という単純な話ではありません。
CPU・メモリ・I/Oをまとめて見る。
この考え方は、
後にLinuxの
vmstat
や
top
を使った性能確認にもつながりました。
07
pandasから並列処理へ
DataFrameで大量データを扱えるようになると、 次に考えたのが、
「このデータをどうやって複数の処理に分けるか」
ということでした。
そこで、
Python標準ライブラリの
concurrent.futures
を使った並列処理を検討しました。
from concurrent.futures import ProcessPoolExecutor
DataFrameを処理単位に分割し、 複数のプロセスで処理することで、 CPUをより有効に使える可能性があります。
ここから先は、 pandasそのものというより、 「大量データをどう並列処理するか」 という話になります。
詳しい部分は、 別の記事で整理します。
Pythonで大量データを並列処理する
concurrent.futures
/
ProcessPoolExecutor
08
最初から「pandasを使おう」と決めていたわけではない
ここも、 この案件を振り返る上では重要なところです。
最初から、
pandasを使って、 DataFrameにして、 ProcessPoolExecutorで並列処理しよう。
と計画していたわけではありません。
そもそも、 仕様書も十分になく、 知見者もいない状態でした。
大量データを流してみた。
遅かった。
何が遅いのか調べた。
データ処理を見直した。
pandasを使ってデータを扱った。
さらに処理を分割した。
並列処理を試した。
そして、 CPUやメモリの状態も確認した。
必要になったものを、 その都度調べて使っていった。
FIELD NOTE
「pandasを使った」よりも重要だったこと
この案件を、 「pandasを使って大量データを処理した」 とだけ説明すると、 かなり簡単な話に見えます。
でも、 実際にはそうではありませんでした。
最初は、 何が問題なのかすら分かっていませんでした。
だから、
データを流す
↓
処理時間を見る
↓
コードを調べる
↓
データの扱い方を変える
↓
テストする
↓
結果を見る
↓
また問題を見つける
↓
また直す
その繰り返しでした。
技術を使ったというより、 問題を見つけるたびに 必要な技術を覚えていった。
そして、 その結果として pandas、 並列処理、 PyArrow、 Linuxのリソース確認などが 一つの処理の中につながっていきました。
SUMMARY
大量データ処理で学んだこと
この案件で、 pandasは大量データを扱うための 重要な道具になりました。
ただし、 これらを最初から全部知っていたわけではありません。
2年半という数字を見て、 「このままでは終わらない」と思った。
そこから、 データ処理を見直し、 pandasを使い、 処理を分割し、 並列化し、 CPUやメモリを確認していきました。
最終的には、 移行処理にリソースを集中できる場合で 約2週間、 運用と並行する場合でも 約2か月で移行できる見込みまで 持っていくことができました。
pandasを使ったことではなく、
大量データをどう分解し、
どう処理し、
どこがボトルネックなのかを
見るようになったこと。
そして、 その先にあったのが Pythonの並列処理でした。