Leverages データ戦略ブログ

インハウスデータ組織のあたまのなか

入社2ヶ月のデータエンジニアの試行錯誤

はじめに

 データ戦略室データエンジニアリンググループの楊と申します。2021年2月に中途入社して2ヶ月が経ちました。前職はSIerでSEを務め、データベースの運用や新ソリューションを創出するためのデモアプリ開発をしていました。データベースの仕事に携わっているなかで、データを取り扱うのが面白いと思い、データエンジニアに転職しました。

 データエンジニアリンググループでは、データ基盤構築やデータ活用方法の設計と整備など、データ収集とデータ蓄積に関わる業務を主に行っています。

 今回は、入社2ヶ月の間に、データエンジニアとして試行錯誤したことをいくつかご紹介します。

ぶつかった壁と試行錯誤

 2ヶ月間、データ基盤の機能追加やデータ抽出など様々な仕事を経験しましたが、そのなかで1番苦戦したのは、データソースのデータとBigQueryに移行したデータを比較して差分を表示するプログラムを作ったことです。

 設定やデータソースの誤りにより、不必要なデータや誤ったデータがBigQueryにインポートされても、何らかのシステムエラーが発生しない限りETL処理は成功と判断されてしまうため、差分比較のプログラムが必要になりました。「Pythonで書けばせいぜい1週間で完成できるのではないか」という軽い気持ちでスタートしたのですが、両方のデータを比較できる状態になるまで1ヶ月もかかりました。

 ここからは、実際にぶつかった三つの壁に、どう対処していったのかを書いていきます。

一つ目の壁(データの型変換問題)

 データソース側にtinyint型で定義されているカラムには1か0が入っているが、EmbulkによってTrue/Falseに変換されるため、BigQueryに入るとboolean型になる。

試行錯誤
 データソースのデータも0=False/1=Trueであれば問題ないのですが、0/1がほかの意味を表し、単純なboolean型ではないケースも考えられるため、数値のままBigQueryに入れるほうが適切だと考えました。インターネットで調べたところ、最初に見つけた解決方法はEmbulkの設定ファイル

select * from table

と記載するのではなく、カラム名を指定して型変換をする書き方にし、

select cast(column as signed) from table

のように記載する方法でした。

 しかし、データベース数やテーブル数が多い中で、都度都度型変換を記載するのはあまりにもコストが大きくスマートではないため、よりシンプルな書き方を探すべきだと考えました。考えていると、Embulkで勝手に型変換されているのであればEmbulkの設定ファイルでうまく数値のまま保つ処理を入れられるのではないかというアイデアを思いつきました。調べると、Embulkの設定ファイルを適切に設定することで今回の問題は解決できそうです。次に考えるべき問題としては、「Embulkの設定ファイルのどこにどのように書くか」になります。

 Embulkで変換してBigQueryに入れるため、embulk-output-bigqueryプラグインのcolumn_optionsで指定すれば問題ないと最初は思ったのですが、期待通りに変換されなかったため、Embulkがデータソースからデータを取得した時点ですでに変換が発生しているのではないかと推測しました。しかし、embulk-input-mysqlのcolumn_optionsに記載するようにしましたがうまくいきません。「もしかして、指定しているデータ型が合っていないのではないか?」と疑いました。調べると、embulk-input-mysqlではsigned型はサポートされておらず、整数に変換したいのであればlongを使うべきらしいです。最終的には、embulk-input-mysqlのcolumn_optionsで

column: {type: long}

のように記載することでtinyint型のカラムが無事に数値のままBigQueryに入りました。

二つ目の壁(jsonデータの取扱問題)

 データソース側にてjson型で定義されているカラムがBigQueryに入ると、jsonの要素が並び替えられ、中身が同じでも要素の並び順の違いによって比較をすると毎回差分として扱われる。

試行錯誤
 Embulkのデフォルト設定では、json型のカラムは全部string型でBigQueryに入るため、値の中で並び替えは発生しないと想定したのですが、残念ながら並び替えられてしまいました。BigQuery側でもjson型として定義しておけばうまくいくのではないかと思ったのですが、調べてみると、BigQueryで直接json型のカラムを定義することができないようです。

 したがって、Embulkがデータを取得した時点でデータソース側の並び順をそのままに保てるようにcolumn_optionsで

column: {type: string}

を指定するかか、Pythonで並び替え処理をするかの2つの選択肢を考えました。column_optionsの指定は期待通りの動作にならなかったので、Pythonでなんとかしようとしたのですが、並び替えられる順序に規則性があるわけではなかったため、全てのカラムで期待通りの動きをするわけではありません。

 結局、よい解決策をみつからないまま、json型以外のカラムで差分がないのであれば、json型のカラムも差分がないであろうと考え、json型のカラムを比較対象から除外しました。

三つ目の壁(日付データのフォーマット問題)

 データソース側では、時刻まで表示するように日付のフォーマットを指定しているが、時刻が00:00:00の場合、DataFrameでは何故か時刻が表示されなくなる。

試行錯誤
 データソースとBigQueryへ入れたデータの比較は全レコード対象ではなく、各テーブルをidカラム等で並び替えして100行ほど取り出して比較しています。全レコードの差分比較には時間がかかりすぎるため、ある程度のレコード数で差分がなければ、そのテーブルはデータソース側のデータとBigQueryのデータに相違がないとみなしています。

 とあるテーブルの古い日付は時刻まで記録されておらず、「YYYY-MM-DD 00:00:00」という形になっています。idカラムの昇順で並び替えると、データソースから取得してきた上位100レコード全てがこの形でした。しかし、このデータをPandasのDataFrameへ格納すると、DataFrame上では時刻部分が消されてしまい、「YYYY-MM-DD」という形になります。BigQueryに入っている日付のデータはタイムスタンプのフォーマットを指定しているため、「YYYY-MM-DD HH:MM:SS」のままです。すると、中身が一緒でも時刻有無で差分有りとして検知されてしまいます。

 調べてみると、Pandasの仕様上、格納するデータが全て「YYYY-MM-DD 00:00:00」である場合に自動的に「YYYY-MM-DD」と時刻部分を削除してしまうようです。DataFrameに入れるデータ全部が「YYYY-MM-DD 00:00:00」であるため、時刻が消されたということは「YYYY-MM-DD 00:00:00」ではないデータが含まれると時刻は保持されるのではないかと推測し、解決方法として、①降順で並び替える、②「YYYY-MM-DD 00:00:00」ではないデータが出てくる行まで取得する、の2つを考えました。降順で並び替えると、データソースからBigQueryへデータをインポートしている間の更新時間差でより差分が出てくるため良策ではないと判断しました。最終的に差分比較を100レコードではなく500レコードまで取得することで、時刻が消されずに比較が可能となり、差分比較の時間も許容範囲に収めることができました。

最後に

 このプログラムを作成する中で思ったことは、コーディング自体は簡単かもしれないが、データを理解し、使える状態にするまでが非常に大変だということです。今回は長い時間をかけて辛い辛いと言いながらようやく完成したのですが、ETL処理やデータの前処理について理解が深まったので、有意義な仕事だと思いました。

 データエンジニアの仕事は私にとって、非常に新鮮で、挑戦的です。データと触れ合いながら、システムの知識やクラウドの知識も活用できるのみならず、学びも多いです。今後もしっかりしたデータ基盤を作りながら、データ活用を色々検討していけたらと思います。