はじめに
本件は昨年(2024年)上半期あたりに私がかかわった案件のお話しです。
データ移行において移行対象レコードを絞るぐらいであれば
エクスポート時点でちょちょっとやれば事済んでしまいます。
しかし、タイトルにもあるようになぜわざわざGoを採用してツールを開発したのかを
体験レポのような形でまとめようと思います。
Goのテクニックなどを記したものではなく、あくまでGoを採用する前後の体験談です。
どなたかの技術選定の一助や暇つぶし用コラムとして読んでいただけますと幸いです。
また、本件のデータ移行やツール要件に関するアレコレは無しでお願いします。
だって皆さんも仕事してればツッコミたい気持ちを抑えてやらないといけない時あるでしょう?
大変申し訳ないのですが手元に厳密なデータが無いので記憶の限りで記載しています・・・
キックオフ時の状態
そもそもシステム更改に係るデータ移行というのが本件のあらましです。
システム更改に伴い既存DBから新DBへデータを移行する計画となっていたのですが、
「なんとなくフィーリングが近い」しか言えないぐらいテーブル構成が異なっていたので
エクスポートされたデータをゴリゴリに加工して既存データを新テーブル構成に適応させる必要がありました。
そのためデータ加工ツールの開発チームがつくられ、とりあえずで自分もアサインされたという経緯でした。
大概の場合はデータ移行で使用するツールは一時的なもの※であり、
本件のツールもほぼ使い捨てのため要件さえ満たせれば何で作っても良いということで
技術選定の段階から関わらせていただくという貴重な経験ができました。
※更改は頻繁に行われるものでもなければ、状況に応じて必ずしも再現性を求められるものではないため、日常的な保守は不要という意味で記載しております。
類似した更改を行うことが事前に明らかになっている場合はその限りではないと思いますのであしからず。
まずは要件とチームメンバーのスキルセットです。
要件
-
既存DBからCSVでエクスポートしたデータを加工すること
直接DBに接続してなんやかんやできればもう少し柔軟に考えられたのですが、
前提として- 1テーブルにつき1ファイルのCSV形式でファイル出力する
- エクスポートしたファイルを加工して新DBのテーブル構成に適した形で出力する
が既に確定で進んでいたためしゃーなしです。
-
新DBのテーブル仕様適応のために何ファイルも読み込んで横断的に処理すること
ツール設計のために旧DBのリレーションと新DBのリレーションの整理やテーブル、カラムの紐づけ(弊チーム内ではマッピングと呼んでいた)をしたところ、
複数ファイルを読み込み、データ加工もした上でひとつのファイル(テーブル)として出力したり、カラムの値によって参照するファイルが異なるといったことが判明しました。
SQLでいえばWHEREとJOINでいい感じにコネコネするようなイメージです。
つまり使い捨てツールではあるものの、複雑な機能を作り込まなければいけませんでした。
-
1ファイルあたり数ギガバイトのファイルを読み込んで処理できること
先述したように、今回のツールは1テーブルを1ファイルとしてエクスポートされたファイルをインプットとして処理をします。
そして前項に記載したとおり複雑な機能も求められます。
そんな中で発覚したのは最も容量の多いファイルは10GB弱あるということでした。
ひょえ〜〜〜〜〜〜〜〜〜
更に最も容量の多いファイルがほぼ全てのテーブルに共通して軸となるテーブルでした。
SQL的に言えば「だいたいいつもJOINするUSERテーブル」みたいなやつです。
-
ツールは外部ネットワークに接続していないWindowsサーバで実行できること
持ち込みには事前の申請が必要であったり、更にはサーバを使用時間が限られていることもあり、その場でのトラブルはなるべく避けたかったです。
(そもそも本番作業でトラブル歓迎な現場なんてどこもないとは思いますが…)
外部ネットワークに接続していないためビルドされた状態のツールを持ち込むか、環境丸ごと持ち込むかの2択でしたが、スムーズな作業を考慮するとビルド済みのツールの方がオペレーションも短縮できるので有力でした。
Dockerで環境を持ち込んで展開も一瞬考えたのですが、そもそも外部ネットワーク繋がってないならコンテナ構築できないじゃん!あたいのバカ!となりすぐにボツとなりました。
また、次の節に記載するメンバー3人ともMacでの開発環境であることも加味すると、クロスコンパイルでOS間の実行環境差異を極力無くして安定して実行させたいという方向性が明確になったのでむしろ良い材料でした。
メンバースキル
経験年数は当時のものですが、自分以外があやふやだったので「だいたいそのぐらい」に思ってください。
- 私
- プログラミング経験自体は15年弱、実務5年目
- 学生時代からこの案件当時までにC、Java、Python、Ruby、Nodeあたりには触れていた
- 普段の業務はバックエンドばかりでJava、Pythonならなんとなく大丈夫そうな感じ
- プレイングマネージャーさん(仮称)
- プログラミング経験自体は15年弱、実務7年目
- 学生時代にC、Java学習済み
おそらく他にもネイティブアプリ系の言語は触れたはず - 開発業務はフロントに傾倒しつつバックもできてPythonなら大丈夫そうな感じ
- データサイエンティストさん(仮称)
- プログラミング経験自体は6年弱?、実務3年目(うる覚え)
- 元々研究職でもっぱらPython畑
- この時点での実務経験はPythonオンリー
さすがデータサイエンティスト
何がともあれ技術検証
日頃Web開発の業務が多いため、ここまで本格的?なツール開発の知見があまりなく、
とりあえず要件に合致しそうなプログラミング言語の列挙から始まりました。
下調べとして慎重に検討に検討を重ね、検討を加速させ…と考えていても机上の空論なので、候補に挙げた各言語でサンプルプロジェクトを作成し、それっぽいダミーファイルを読み込ませてそれっぽい処理を動かしてメモリ使用率や処理時間を計測するという技術検証をすることとしました。
どの言語でも共通して
- 旧DBからエクスポートしたCSVファイルをinit処理としてすべて読み込みメモリへ格納する
あくまで検証なのでダミーファイルは本番データの最も容量の多いファイルの半量である5GB弱と2GB弱の2つを準備した(気がする) - 出力ファイル(テーブル)ごとにメモリ上のデータを呼び出してレコード生成してファイルへ書きこむ
という処理としました。
Python
全員共通してそこそこ書ける言語だったのでとりあえず最初に検証してみました。
案の定ですがメモリ8GBのマシンだと処理落ちしたり、16GBのマシンでも処理完了までに6~8時間ぐらいかかったような覚えがあります。
処理時間もさることながら、16GBのメモリをすべてを食いつぶして張り付いてました。
ダメもとでのお試しだったのでまあそうだよねという結果です。
ただし上記はPythonを純粋にインタプリタとして、生のまま動かした場合の結果です。
ビルドして試してみようかとも考えたのですが同じように1人日潰されてしまっては堪らないので、一旦他の言語を試す方向で舵を切った記憶があります。
少し余談ですが旧DBのリレーションを再現できれば楽に実装できるんじゃないかという考えがよぎり、
CSVファイルを読み込んでSQLiteにぶち込めばお手軽RDB再現できんじゃ~ん
なんて浅はかなことを一瞬試しました。
しかし、インメモリモードではCSVを直接メモリ上におくことと大差なく処理が安定せず、
ファイルモードだと処理時間がかえって増加してしまったためボツとなりました。
Java
「コンパイル言語ならJavaでしょ」とかなり脳筋スタイルな発想から候補に挙げてました。
私自身そこそこの数ギガのファイルを読み込ませて処理をするツールをJavaで開発したことがあり、3人中2人が経験ある言語だしいけんじゃね?と勢いでとりあえずサンプルをさくっと作って実行してみました。
おおよそ平均90秒ぐらいで処理が終わった覚えがあります。
Pythonと違って8GBのマシンでも処理が問題なく完了するうえに2分もかからないという感動で何度も何度も実行した覚えがあります。
ただ、やはり実務経験もなければ学習もしたことがないというメンバーからは若干とっつきにくいという意見をもらい、更なる高みを目指すこととなりました。
Go
私自身も触ったことがないGoを業後と週末に学習するところからスタートしました。
だいぶ序盤の方に「これ書き味Pythonと似てんな」という下馬評どおりの感覚で、
「完全に理解した」のステップまではかなり短期間でスムーズに習得を進められたと思います。
構造体の文字を見た時はC言語をやっていた頃を思い出し少し懐かしかったです。
ここで突き詰めすぎても無限に時間をくってしまうので、これまでと同じようにサンプルを組んで動かしてみたのですがまさかの平均60秒弱という記録をたたき出しました。
実装面で言えば、構造体は移行先テーブルに合わせて出力するCSVのカラム形式を定義したり、
ポインタやマップ、スライスなども駆使してとてもいい感じでした。
何かの間違いじゃないかと何度も何度も無邪気に実行したあの瞬間を今でも鮮明に覚えています。
チームメンバーからは「Javaよりはまあたぶん」という感じのフィードバックをもらった記憶があります。
ここまでくるとアドレナリンが出ているので、見たことのない景色を求め始めました。
Rust
むりでした。むりむり。
GoいけたしRustもサクッと!なんて思ってましたが自分が脳筋感覚人間だったことを思い出しました。
よく言われる「学習コストが高い」は本当だということを身をもって体感しました。
堅牢指向のシステムプログラミング言語恐るべしです。
Rustのライブラリ開発をしている知人に泣きついて解説してもらったり、超簡単(なはず)のサンプルまで組んでもらったのですがさっぱりでした。
チームメンバーにサンプルを展開したところ全員なんやこれ状態で、
技術選定をする人間が説明できないようでは仕事になりませんし、もはや早々に選択肢から外すほかありませんでした。
確かにサンプルの実行速度は爆速だったなぁぐらいの記憶はありますがもはや結果を覚えていません。
一瞬だけPythonにRust製モジュールを組み込むという考えも脳裏をよぎりましたが、
なんとなく性能改善目的で一部をRust製モジュールに置き換えるイメージが強かったので、
今回は既存のPythonソースコードがあるわけでもなく新規開発だったことも有り選択肢からは外していました。
検証後
選定結果と本実装
検証を通して
- 今回処理したいデータ規模だと Python < Java < Go < Rust の順で非機能面が優れていること
- Python経験者が多い中では書き味の観点でGoとの親和性が高いこと
が明確となり、性能と開発効率の両観点からGoを採用することとなりました。
私以外の2名のメンバーも開発が本格化するにつれてGoの書き方に慣れていきましたし、
中盤からは各々が
データサイエンティストさん(仮称)なんかは脳筋で書いていたらおぞましいスパゲッティになるところ、
持ち前のアルゴリズム知識ですばらしいロジックを書いていました。すごい。
開発途中で新DBのテーブル仕様が少し変わったりして気持ちが落ちる時もありましたが、
Goは柔軟に、かつ容易に対応ができたのでこういったところでも開発効率の高さを実感しました。
また、言語別の節にも記載しましたが、
構造体、ポインタ、マップ、スライスというGoの基礎的なもの自体が今回の非機能制約に大きく寄与したと思っています。
Goおそるべし。
ガベージコレクションも完全我流でちょこっとチューニングしたような覚えもありますが、
詳細が思い出せないのできっと大したことはやってなかったんだと思います。
C言語のような性能で、Pythonライクな書き味、JavaっぽいGCチューニング(すみません適当書いてます)は個人的にかなり推したいポイントです。
当時の生成AI活用事情
当時(2024年上半期)はGitHub CopilotとAmazon CodeWhispererがメソッド単位などスポットでの利用が強いと言われていた時期でした。
Cursorがジワジワと伸びてきている中でガバナンスも整いきっておらず、コイツは敵か味方とビクビクしていた頃合いでもありました。
現在(2025年12月初旬時点)こそ様々なサービスやベストプラクティスが日夜更新されているため良い時代になったもんだなと思いますが、
本件ではスポットで各々が信じるサービスを使って一部関数を書かせたりなどはしてました。
私もAmazon CodeWhispererとChatGPTでどっちがいい関数書けるか試したりしてました。
そんな現在ほどコーディングにおける生成AIが普及していない当時、
Go経験者がいない3人チームで複雑なツールを開発できたということは、やはり言語としての評価がかなり高いのではと思います。
素地のない人が生成AIを使ったコーディングをすることのリスクはもちろん当時から承知の上でしたので、スコープを絞って関数を生成させる程度にとどめておいたというのもあります。
総括
Python経験者がいる現場において性能問題への対応としてGoを選択することは大変有効であると感じました。
字面ではGoとRustの特徴は知っていましたが、体感レベルで理解できていなかったのでとても勉強になったなと思います。
ふわっとしたイメージしかないものは触ってみないとわからんというのもあるので、
完全我流の手探りではありつつも開発体験という体感的なものや性能を比較できた検証は無駄ではなかったと思っています。
やはりユースケースとしてサンプルを組み立ててみないことには技術導入のインパクトを実感しづらいので検証は大事ですね。
そもそもDBに直接接続してSQLやシェル、あるいは簡易的なシステムを動かせる状況であればメンバー全員が経験のあるPythonでも問題なかった可能性も考えられます。
今回は「データ移行」のプロジェクトとしてはだいぶ特殊な事例だったのかなとも思いますが、
バッチ処理だと今回のような大容量ファイルの入出力や性能制約が発生することもあるあるなのかなと。
そういった場面ではGoはかなりいい選択肢になりえるのではないかなと思います。
というわけでGoのちょっと特殊な大規模データ移行案件でGo製ツールを開発したお話しでした!
技術選定をする際は、技術の特徴や得手不得手、人間の学習コストのどちらもいい塩梅にしないといけないですね。
ちなみに本件の後にアサインされた案件でも技術選定に携わらせていただいたのですが、
そこでもGoぴったりやん!と提案して採用されるぐらいにはGoにお熱でした。
P.S.
Goもいいんだけど本件から2年近く立つのでそろそろトラウマ克服のためにもRustの門戸を再び叩きたいと思います。