
Golangへのマイグレーション!
こんにちは、Anti-Patternの塚本です。
今回のブログは、タイトル通り、あるマイグレーションについて書きたいと思います。
そもそもマイグレーションとは?
プログラミングやソフトウェア開発で、古いプログラミング言語や開発環境で記述・開発されたソフトウェアを、別の言語や新しい環境に適合するよう変換することをマイグレーションという。
http://e-words.jp/w/%E3%83%9E%E3%82%A4%E3%82%B0%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3.html
そのものズバリ!だったので引用してます。
そして、マイグレーション対象はバッチプログラムなのですが、この様な状態でして。
マイグレーション前
- サポートが切れそうなPHP
- ZendFrameworkをカスタマイズした独自フレームワークを使用
- 仕様書なし
リリースから結構年数がたっているので仕方ない部分はあります。
マイグレーション後
- オープンソースのGolang10.x
- フレームワーク未使用。ORMライブラリにgormを使用
- GoDoc
Golangの採用理由ですが、オープンソースというのが大きいです。
他にも色々あるのですが、
- 処理が軽量で早い
- 並行処理の仕組み(goroutine)が標準パッケージ
- ユニットテストの仕組み(testing)が標準パッケージ
- プロファイリングツールの仕組み(pprof)が標準パッケージ
- プログラムの自動整形(gofmt)が標準パッケージで提供
- 生産性の高さ
ベースプログラム
1ヶ月位検証して作ったプログラムの概要がこちらになります。(DBアクセスなど省いてます)
sync.WaitGroupとchannel利用して、並行処理を実装してます。
package main
import ("sync")
// エンドポイント
func main() {
var wg sync.WaitGroup
executionChannel := make(chan struct{}, 2)
processErrorChannel := make(chan bool, 2)
resultStatusChannel := make(chan bool)
doneChannel := make(chan struct{})
targets := []string{"orange","Apple","peach","watermelon"}
go resultReceptionWorker(processErrorChannel, resultStatusChannel, doneChannel)
for _, v := range targets {
wg.Add(1)
executionChannel <- struct{}{}
go processA(&wg, executionChannel, processErrorChannel, v)
}
wg.Wait()
doneChannel <- struct{}{}
if <-resultStatusChannel {
}
close(executionChannel)
close(processErrorChannel)
close(doneChannel)
close(resultStatusChannel)
return
}
// 業務処理処理関数
func processA(wg *sync.WaitGroup, executionChannel chan struct{}, processErrorChannel chan bool, v string) {
processErr := false
defer func() {
processErrorChannel <- processErr
<-executionChannel
wg.Done()
}()
if err := processB(); err != nil {
processErr = true
}
return
}
// 業務処理処理サブ関数
func processB() (err error) {
return
}
// 並行処理結果を受け付け
func resultReceptionWorker(processErrorChannel, resultStatusChannel chan bool, doneChannel chan struct{}) {
allStatus := false
for {
select {
case ng := <-processErrorChannel:
if ng {
allStatus = true
}
case <-doneChannel:
resultStatusChannel <- allStatus
return
default:
}
}
}
はまりどころ
・詰まるgoroutine
Channelに対する適切なバッファサイズの設定、何故かブロックされるChannelへの送受信。色々な方がブログで書かれてますが、仕組みを理解するまでははハマりました。
・荒ぶるCPU
resultReceptionWorkerは全てのgoroutineが終わるまでループします。goroutineの処理時間に比例してここが高負荷になります。pprofで確認してsleepを入れました。
・Stackされていないerror?
github.com/pkg/errorsをimport
errors.WithStack(error)関数でerrorをラップして返却し、Stackをログに出力してます。
まとめ
Golangを使いましたが、良い言語だと思います。処理速度も問題なし。
簡易ステートメントや型推論など、直感的にコーティングできると思いますし、とてもシンプルに感じました。
個人的な意見ですが、ステップ数を減らすためのテクニカルなコード嫌いです。であれば、少し長くてもシンプルな方が可読性が良いですし、他の人にも理解しやすいと思ってます。
最後に、以下のブログの記事にとても共感しました。
非知的なプログラマのためにデザインされている (Designed for non-intelligent programmers)