Screaming Loud

日々是精進

2022年振り返り

去年の振り返りはこちら

yuutookun.hatenablog.com

って載せながら、今年はブログを全く書いてなかったので前回の記事が去年の振り返りになってますね。。

仕事

今年の大きなトピックとしては、大きなコスト削減施策を実施しました。

tech.gunosy.io

tech.gunosy.io

またその結果が評価され、全社MVPとして表彰していただけました。 昨年はエンジニア部門として表彰されましたが、今年は全社MVPとしての表彰で初めてだったのでうれしかったです。

gunosiru.gunosy.co.jp

他にはRailsの一部の画面をモブプロで書き換えるプロジェクトを実施していました。 下記のブログはその一部の話なんですが、そもそも修正対象画面に大量のフォームが存在していて、 チームでも何をする設定なのかを知らないものがいくつもありました。 おかげでドメイン知識をチームみんなにインストールできたというのは大きな収穫でした。

tech.gunosy.io

個人的にはモブプロで一番良かったのは、結合テストでした。 結合テストって網羅的にテストを実行する必要があり、テストが失敗したときに自分の操作を単純にミスったのかほんとにバグが発生しているのか不安になることが多く、 精神的に結構つらい(みんなはどうなんだろう?)のですが、みんなで並列で実施するとテストの失敗した人数的にバグってるかどうかが確認しやすいのが良かったです。

それ以外でいうとチームメンバーにテックブログを率先して書いてくれるようになって、チームでのブログ執筆率が高かったことです。 チームのアウトプットが増えるのは誇らしいですね。

また、リードエンジニアになってから技術1on1という施策をずっと実施しているのですが、 おかげさまでやってほしいという声を頂いて1on1を実施する人数が増えてきました。

あとは結構大変だったことの一つでエンジニアの行動指針を作るというのを実施していたのですがこれがすごく大変でした。 というのも、何をもってできると評価するのかというのはすごく難しく、単純な表層のスキルだけだとすぐ陳腐化してしまうので、 根っこの行動する基準みたいなものがどうあるかを評価したい、でもそれって外から観測できないから評価できないのではないか、 などという堂々巡りが発生していました。

tech.gunosy.io

一応無事?第一弾は作成完了したのですが、改めて評価の難しさを認識しました。

今年も一年お疲れさまでした。

ねこたち

2021年振り返り

去年の振り返りはこちら

yuutookun.hatenablog.com

仕事

今年は広告のシステムを全部見るようになりました。

引き継いだシステムが結構速報とかでスパイクリクエストが多くてアラートが鳴りがちで、以下のような対応とかしてました。 今年は結構ここらへんの対応が多かったですかね。

tech.gunosy.io

去年あたりから始めた失敗共有会の「はにびぶ会」ですが、どんなふうにやってどこらへんが困ってるかなどをブログに残しました。 勉強会運営って結構モチベーション維持が大変なので、もっと楽に運営していきたいなぁと思ったり。 こちらは社内勉強会ですが、社外で勉強会開催してるってすごいエネルギーが必要だと思うので、すごいと思います。

tech.gunosy.io

あとはHudiなどのデータレイク周りに手をいれたりしました。 GlueSparkをちゃんと本番にリリース、運用したのは初めてだったのでよい経験でしたね。 ずっとHudiを本番で使ってみたいなぁという気持ちがあったので、使えてよかったなぁと思ってます。

tech.gunosy.io

あとは終盤コロナが明け始めて月1くらいで出社をするようになりました。 会社がWeWorkなので、午後5時以降生ビールが飲み放題なので、コミュニケーションの起点になっててよいなぁと。

また技術1on1なるものをリードエンジニアになってからメンバーとやってるのですが、その範囲が増えてきたりしました。 コロナ前に始めたんですが、リモート環境で会話機会が少ないのをカバーする役割に運良くなりました。

あと、今年は締め会でエンジニアとして表彰されました。 こういう表彰は久しぶりでもあったので新鮮でうれしかったのと、 チームメンバーの頑張りのおかげでもあったと思うので、 さらにチームのチカラを上げていけるように尽力していこうと思います。

プライベート

テニスの頻度がすごい増えました。 昔のテニスを仲間が続々とテニス復帰していてそのおかげでやる頻度が増えました。 上手くはなってるんですが、周りも一緒にうまくなっているので、やっぱりまだまだだなぁと思うことのほうが多いですね。

あと引っ越しをしようとして物件を探してますが、猫2匹を飼える家で今より広い家を見つけるのがすごい大変で苦戦しました。 絶賛探し中です。

まとめ

来年もやっていきます。

f:id:yuutookun:20211106175615j:plain

Goで重い処理をtimeoutさせる ~その2~

前回はGoでtimeoutさせる処理に関して書きました。

yuutookun.hatenablog.com

しかし、前述の記事で書いているのはレスポンスが返らない場合でした。 多くの場合、レスポンスが必要だと思うので、レスポンスを付与するパターンを紹介します。

functionの返り値が値とエラーの二つになっていますが、ここは type result を変更すれば増やすことができます。 ただ、レスポンスをstructにしておけば、このメソッドで事足りると思います。

流れとしては、

  1. 引数の関数の結果格納用のチャネルを作成
  2. goroutineで引数の関数を実行。結果はチャネルに。
  3. for ループのselectでcontextを監視して
  4. チャネルに結果が入れば、結果を返す
  5. context.Doneになれば、空の結果を返す

という感じです。 responseのresultは汎用性のために interface{} で定義しています。

関数終了前にcloseしたりすると、内部でpanicが発生してしまうので、チャネルのcloseに関しては goroutineの終了と一緒にcloseしてます。

以下実際のコードになります

var FailedGetChannel = errors.New("failed to get Channel")

type result struct {
    value interface{}
    err   error
}

func main() {
    f := func() (interface{}, error) {
        time.Sleep(10 * time.Second)
        return []int{1, 2, 3}, nil
    }
    ctx, cancel := context.WithTimeout(context.Background(), 2 * time.Second)
    defer cancel()
    inter, err := ExecWithContext(ctx, f)
    if inter == nil { // interがnullだとcastでpanicになるため
        fmt.Println(err)
        return
    }
    fmt.Println(inter.([]int), err)
}

// ExecWithContext f()の結果をcontextキャンセルされるまで待つ
func ExecWithContext(ctx context.Context, f func() (interface{}, error)) (interface{}, error) {
    resultCh := make(chan result) // f()の実行結果を入れるresult チャネル

    go func() {
        defer close(resultCh)
        resultCh <- func() result {
            i, e := f()
            return result{value: i, err: e}
        }()
    }()
    return waitResult(ctx, resultCh)
}

// contextがtimeoutになるまでchannelの結果が返るのを待つ
func waitResult(ctx context.Context, ch chan result) (interface{}, error) {
    var i result
    for {
        select {
        case <-ctx.Done():
            return i.value, ctx.Err()
        case i, ok := <-ch:
            if !ok {
                return nil, FailedGetChannel
            }
            return i.value, i.err
        default:
        }
        time.Sleep(1 * time.Millisecond)
    }
}

実際のplaygroundは以下です。

Go Playground - The Go Programming Language

追記

ライブラリとして公開しました

github.com

Goで重い処理をtimeoutさせる

Goで重い処理を書いているとタイムアウトさせたいときがあると思います。

大抵のIOが発生するライブラリだとcontextを引数に加えると、context の終了通知が発生して終了してくれます。

例えば、以下のようにhttp requestであれば、contextにタイムアウト設定することでhttp requestがタイムアウトになるとリクエストをキャンセルしてくれます。

ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "http://www.google.com", bytes.NewReader([]byte("")))
resp, _ := r.client.Do(req)

しかし、ライブラリやその他IOによってはちゃんとcontextの終了が実装されていない場合があります。 通常であれば、contextを渡すだけなはずが終了できない場合、自分でその機構を作ってしまうのも手です。

以下のようにキャンセルさせる関数を作ってみました。

func main(){
       // 重い関数
    heavyFunc := func() {
        time.Sleep(10 * time.Second)
    }
    if err := execWithTimeout(context.Background(), heavyFunc, 2*time.Second); err != nil {
        fmt.Println(err)
    }
}

// ctx 親context
// fn 実行させる重い関数
// timeout timeoutさせる時間
// この関数を通して実行すると、timeoutさせることができる。
// 注意するのは、goroutineで投げっぱなしになるので、一時的に重い処理ではなく常時重い処理の場合はどんどんリークしていくので使うべきではない
func execWithTimeout(ctx context.Context, fn func(), timeout time.Duration) error {
    timeoutCtx, cancel := context.WithTimeout(ctx, timeout)
    defer cancel()


    go func() {
        fn()
        fmt.Println("finish heavy")
        cancel()
    }()
    return ticker(timeoutCtx)
}

// 入力されたcontextが終了するかどうかを監視する関数
func ticker(ctx context.Context) error {
    for {
        time.Sleep(10 * time.Millisecond)
        select {
        case <-ctx.Done():
            fmt.Println("Stop ticker")
            if ctx.Err() == context.Canceled {
                return nil
            }
            return ctx.Err()
        default:
        }
    }
}

以下にplay groundを貼っておきます

The Go Playground