🐱

Voicyのアカウント登録直後のチャンネルフォローの速度改善してみた

2022/11/27に公開

はじめに

Voicyでバックエンドエンジニアをしているかげにぃです。
今回、Voicyのアカウント登録時に"チャンネルをまとめてフォローする機能"の速度改善をしてみたので記事にしてみました。
また、Twitterを最近頑張っているのでよかったらフォローお願いします!

この記事の対象

以下の方におすすめな記事です

  • 初心者で速度改善に興味ある方
  • GoのSQLBoilerのraw queryの実行方法を知りたい方
  • Voicyの機能がどのように実装されているか興味がある方

どんな機能?

Voicyではアカウント登録後に以下のような画面でチャンネルをフォローをします。

実際の画面

今まではチャンネルアイコンをタップした際、フォロー・アンフォローのリクエストをするようになっていました。
都度通信するのでもっさりした体験になっていたため、速度改善をすることになりました。

実際行った改善ポイントは?

アプリのエンジニアと協力したポイント

チャンネル毎にフォロー/アンフォローだったのではじめるボタン押下時に一括でフォローするリクエストをするように変更しました。

バックエンド側で処理を見直した点

そもそも単一チャンネルのフォローの仕組みをそのまま流用すると、SQLの実行数がチャンネル数に比例して増える状態なので以下の2点を実施。

  • Insertをまとめて実行(BulkInsert)
  • Updateもできる限りまとめて実行

ただ、SQLBoilerはBulkInsertができない

Voicyでは別のリポジトリでBulkInsertが実装済みだったので共通基盤に移植して使えるようにしました。
(BulkInsertだけで記事が書けるくらい説明が長くなるので今回は割愛します。リクエストがあれば記事化します!)

Voicyとは違う実装方法ですが、以下が参考になると思います。
Qiita SQLBoilerでBulk Insertを実現する方法

SQLだけでできる処理の改善

単一フォローのフォロー数をカウントアップする処理は以下のようにSelectで再集計したカウント取得→Updateのような処理になってました。

	// フォローテーブルから集計
	followerCount, err := orm.Follows(
		orm.TFollowWhere.ChannelID.EQ(int64(id)),
		orm.TFollowWhere.DelFLG.EQ(0),
	).CountG(ctx)
	if err != nil {
		return 0, err
	}
	
	// 集計したフォロー数に+1
	newFollowerCount := followerCount + int64(1)

	// フォロー数を更新
	entity := &orm.Channel{
		ID:            int64(id),
		FollowerCount: uint64(newFollowerCount),
	}
	columns := boil.Whitelist(
		orm.ChannelColumns.FollowerCount,
	)
	_, err := entity.Update(ctx, ex, columns)
  • 上記の処理をクエリだけでできるようにする
  • 複数のチャンネルIDに対してカウントアップさせる

上記の2点の改善後の処理はこんな感じ

	var whereInIDs string
	for i, id := range IDs {
		if i == 0 {
			whereInIDs = strconv.Itoa(int(id))
		} else {
			whereInIDs = whereInIDs + "," + strconv.Itoa(int(id))
		}
	}
	sql := fmt.Sprintf("UPDATE channel SET follower_count = follower_count + 1 WHERE id In (%s)", whereInIDs)
	_, err := ex.ExecContext(ctx, sql)
	if err != nil {
		return err
	}

SQLBoilerでraw queryはこのように実行できます。
※今回の処理ではSQLインジェクションは発生しないのでバインドなどの処理は省略しました

見送った改善ポイント

今回は限られた時間でやったので時間があったら以下を改善する予定です

  • 他の画面の単一フォロー・アンフォローの改善はしなかった
    • 機能追加などで修正が漏れたであろう不要な処理があるので削除
    • 複数チャンネルで改善したフォロー数のUpdate処理を修正
    • テスト実装が不十分なのでテストの追加
  • 処理が複雑になるので論理削除から物理削除に変更したい
    • そもそもフォローのテーブルは論理削除になっていました
    • 1回のリリースでは対応できないので以下の順番で実施
      • アンフォローのAPIの処理を論理削除→物理削除に変更
      • バッチ処理などで論理削除されているデータをDelete
      • フォローのAPIの処理から論理削除(Update)部分を削除
    • SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
  • チャンネルフォロー数の集計をキャッシュに変更したい
    • そもそもリアルタイム性を担保しなくて良いのであればキャッシュにもつように変えることも検討する

最後に

ほんのエッセンス的にですが速度改善についてまとめてみました。
速度改善するには処理速度調査やアーキテクチャなども含めた見直しなど様々な観点が必要になります。
観点などは別の記事でまとめていこうと思うのでぜひフォローお願いいたします。

Voicyテックブログ

Discussion