Skip to content

ISUCON 13 参加した

Published: at 10:00

チーム太郎でやりました。楽しかった。お疲れ様でした。ありがとうございました。

やったこと

Go を使って参加しました。

Tags をインメモリで持つようにした

このコードを init 関数の中で呼び出すようにした。

// グローバル
var tags []*Tag


tagNames := []string{
    "ライブ配信", "ゲーム実況", "生放送", "アドバイス", "初心者歓迎", ...
}
tags := make([]*Tag, len(tagNames))
for idx, name := range tagNames {
	tags[idx] = &Tag{
		ID:   int64(idx) + 1,
		Name: name,
	}
}

themes テーブルを削除して users テーブルへ持っていった

themes テーブルは AUTO INCREMENT で ID が設定されており、users テーブルと同じ件数であった。users テーブルから user 情報を引くことが前提の処理が多かったので、users テーブルに ``dark_mode BOOLEAN NOT NULL を追加して、ID は users の ID を返すようにエンドポイントを修正。

icons テーブルを使わず、username をファイル名にして静的配信

Go では http.ServeContent を使えば If-None-Match に対応したレスポンスにしてくれるので、これを使ってレスポンスを返すようにしました。

S3 サーバーっぽい感じのアプリケーションサーバーを 2 台目のサーバーに設置した。それ以外にデプロイされたアプリケーションサーバーはこの 2 台目に対してリクエストを投げて返ってきたレスポンスをそのまま返すようにした。

package main

const listenPort = 8080
const fallbackImage = "../img/NoImage.jpg"

func main() {
	e := echo.New()

	e.GET("/healthz", func(c echo.Context) error {
		return c.JSON(200, map[string]string{"status": "OK"})
	})

	e.GET("/reset", func(c echo.Context) error {
		resetIcons()
		return c.JSON(http.StatusOK, map[string]string{})
	})

	e.GET("/api/user/:username/icon", func(c echo.Context) error {
		username := c.Param("username")

		path, err := getIconPath(username)
		if err != nil {
			if errors.Is(err, os.ErrNotExist) {
				return c.File(fallbackImage)
			}
			return echo.NewHTTPError(http.StatusInternalServerError, "failed to get user: "+err.Error())
		}

		return c.File(path)
	})

	e.POST("/api/user/:username/icon", func(c echo.Context) error {
		username := c.Param("username")
		name := filepath.Join(iconsPath, username+".jpg")
		f, err := os.Create(name)
		if err != nil {
			return err
		}
		defer f.Close()

		io.Copy(f, c.Request().Body)

		return c.JSON(http.StatusOK, map[string]string{})
	})

	// HTTPサーバ起動
	listenAddr := net.JoinHostPort("", strconv.Itoa(listenPort))
	if err := e.Start(listenAddr); err != nil {
		e.Logger.Errorf("failed to start HTTP server: %v", err)
		os.Exit(1)
	}
}

const iconsPath = "images/icons"

func getIconPath(username string) (string, error) {
	name := filepath.Join(iconsPath, username+".jpg")
	if _, err := os.Stat(name); err != nil {
		return "", err
	}
	return name, nil
}

func resetIcons() {
	os.RemoveAll(iconsPath)
	os.MkdirAll(iconsPath, 0700)
}

moderate の追加

  • 過去に追加された ngwords を見る必要がなかったのでループを削除した。
  • コメントに ngword があった livecomment の ID をスライスに詰め込み、長さが 1 以上あれば DELETE FROM livecommentsUPDATE ranking_livestreamsIN 句を使って実行するように修正した。

その他

  • 複数台構成
  • DB サーバー設定
  • デプロイ周りの修正

感想

  • DNS 周りは対応する時間がなかったので、他の前提の問題を解決するための力不足を感じた。
  • 統計周りの N + 1 のロジックが難しく、解きほぐすロジックを考えるのも難しかった。
  • 1 台で最高 22000 くらいまでスコアは上がったが、最後は複数台構成で fail して終わったので悔しかった。来年も開催されるなら参加したい!