🚀

BEENOSグループ 社内ISUCONを開催しました

2022/11/17に公開

BEENOSの三上です。
本日は前回のハンズオンに続き、弊社内で開催した社内ISUCON、B-ISUCONについてレポートさせてください。

なお、「ISUCON」は、LINE株式会社の商標または登録商標です。

社内ISUCON開催の目的

BEENOSではコロナ渦前に、BEE-Tech Campと称して、エンジニアの参加する合宿を催していました。(cf. 2019年開催、第3回の様子
2020年以降はコロナの影響でこれをしばらく開催できておらずでした...
が、有志がこれを再開しよう!と声を上げ、第7波が過ぎ去ったか、というタイミングで開催にこぎつけることが出来ました!

今回の合宿では下記2つの目的で、社内ISUCONやるぞー!となりました。

  1. グループ内エンジニアの交流促進(どこでもそうだと思うんですが、グループ内他会社のエンジニアとの交流は若干の難易度がありますよね 🤔)
  2. 実務に "効く" 知識の習得

今回取り組んだ問題について: private-isu

社内ISUCONを行うにあたり、問題は catatsuy/private-isu を使用させて頂きました。
また、競技中のスコア表示のためのポータルサイトは iwashi/private-isu-portal を元に構築しました。
両repositoryの作成者、関係者様に感謝を申し上げます。

https://github.com/catatsuy/private-isu

https://github.com/iwashi/private-isu-portal

当日のタイムテーブル

当日は下記のようなタイムテーブルで競技を行いました。

時刻 内容
(午前) 会場に集合、設営等の準備
~12:30 当日マニュアルの説明、ssh接続、ベンチ試走
13:30 お昼ご飯(麻婆豆腐ご飯 😋 )
17:30 ポータルサイトのスコアフリーズ
18:30 競技終了

今回は競技参加者にISUCON経験者が少なかったため、お昼前にssh接続の確認とベンチ試走まで行っておき、午後は全チームがチューニングに時間を費やせるようにしました。
また、競技時間は5時間で、実際のISUCONの8時間よりは短い形です。

競技者側の事前準備

今回の社内ISUCONでは、競技参加者に公式ISUCONへ参加した経験を持つ方が少なかったです。
そのため、ISUCONの公式LINEスタンプにあるような やれることがない🤤 とならないように、改善のレシピのようなものを事前に学んで頂くために、ハンズオンを二度開催しました。
第1回はこちらで記事にした内容です。

https://zenn.dev/beenos/articles/20220807_isucon-hands-on-report

第2回については、レパートリーを増やすために小さな改善集をいくつか提供したり、公式の ISUCON11 予選問題実践攻略法 を参照して頂きつつ改善に取り組んで頂きました。
その後は、各チームの自主練に繋げて頂き、それぞれで社内ISUCON本番に向けて作戦を練るなどして頂きました。

https://isucon.net/archives/56082639.html

運営側の事前準備

色々と開催に向けて準備を行いましたが、Tech blogとしては2点取り上げさせてください。

  1. 競技用インフラ構築
  2. ポータルサイト構築

それぞれ以下で紹介します。

競技用インフラ構築

まず、サーバーリソースについてですが、チーム毎に計3台をAWS上に用意しました。

  • 競技用インスタンス, c6i.large x 2台
  • ベンチマーカーインスタンス, c6i.xlarge x 1台

本来のISUCONでは競技用インスタンスが3台用意されますが、今回の社内ISUCONでは2台にして自由度を縛っています。
敢えて自由度を絞ることで、選択可能な改善方策について、より学びを深めて頂きたい狙いがありました。

これらのインスタンスを1つのVPCに配置した形でTerraform Moduleとして定義し、チーム分ループすることでサーバーリソースの構築を行っています。

他に特殊な点としては、チーム名をベンチマーカーインスタンスのタグに追加しています。
これは ポータルサイト構築 で後述しますが、スコア投稿の際にタグからチーム名を取得することを想定しています。

参考までにTerraformの一部を掲載します。

サーバーリソース構築用Terraformの一部
EC2インスタンスのTerraform Module
ec2.tf
module "aws_isucon_sg" {
  source = "terraform-aws-modules/security-group/aws"

  vpc_id      = aws_vpc.vpc.id
  name        = "b-isucon"
  description = "Security group for ISUCON hands-on instances"

  ingress_cidr_blocks = [
    "x.x.x.x/x", // 接続を許可するIPを指定
  ]
  ingress_rules = [
    "ssh-tcp",
    "http-80-tcp",
    "https-443-tcp",
    "mysql-tcp",
  ]

  ingress_with_self = [
    {
      rule = "ssh-tcp",
    },
    {
      rule = "http-80-tcp",
    },
    {
      rule = "https-443-tcp",
    },
    {
      rule = "mysql-tcp",
    },
  ]

  egress_rules = [
    "ssh-tcp",
    "http-80-tcp",
    "https-443-tcp",
    "mysql-tcp",
  ]

  egress_with_self = [
    {
      rule = "all-all",
    }
  ]
}

resource "aws_instance" "b_isucon_app1" {
  # private-isu cf.: https://github.com/catatsuy/private-isu
  ami           = "ami-06c39e451ff9930db"
  instance_type = "c6i.large"
  key_name      = var.instance_key_name

  subnet_id              = aws_subnet.public.id
  vpc_security_group_ids = [module.aws_isucon_sg.security_group_id]
  private_ip             = "192.168.1.11"

  tags = {
    Name = "B-ISUCON_${var.team_name}_app1"
  }

  root_block_device {
    volume_type = "gp2"
    volume_size = 30
  }
}

resource "aws_instance" "b_isucon_app2" {
  # private-isu cf.: https://github.com/catatsuy/private-isu
  ami           = "ami-06c39e451ff9930db"
  instance_type = "c6i.large"
  key_name      = var.instance_key_name

  subnet_id              = aws_subnet.public.id
  vpc_security_group_ids = [module.aws_isucon_sg.security_group_id]
  private_ip             = "192.168.1.12"

  tags = {
    Name = "B-ISUCON_${var.team_name}_app2"
  }

  root_block_device {
    volume_type = "gp2"
    volume_size = 30
  }
}

resource "aws_instance" "b_isucon_benchmarker" {
  # private-isu benchmarker cf.: https://github.com/catatsuy/private-isu
  ami           = "ami-024cfcacc753fa53e"
  instance_type = "c6i.xlarge"
  key_name      = var.instance_key_name

  subnet_id              = aws_subnet.public.id
  vpc_security_group_ids = [module.aws_isucon_sg.security_group_id]
  private_ip             = "192.168.1.21"

  tags = {
    Name = "B-ISUCON_${var.team_name}_benchmarker"
    TeamName = var.team_name
  }

  root_block_device {
    volume_type = "gp2"
    volume_size = 30
  }

  metadata_options {
    instance_metadata_tags = "enabled"
  }
}

output "app1_public_ip" {
  description = "App1 Instance public IP"
  value       = aws_instance.b_isucon_app1.public_ip
}

output "app2_public_ip" {
  description = "App2 Instance public IP"
  value       = aws_instance.b_isucon_app2.public_ip
}

output "benchmarker_public_ip" {
  description = "Benchmarker Instance public IP"
  value       = aws_instance.b_isucon_benchmarker.public_ip
}
上記Moduleをチーム分呼び出すもの
main.tf
module "b_isucon_team_resources" {
  for_each = toset(local.participants)

  # ↑のModuleを定義したpathを指定してください
  source = "./b_isucon_team_resources"

  team_name         = each.value
  # 適宜 `aws_key_pair` resourceを定義の上渡してください
  instance_key_name = aws_key_pair.isucon-instance-key.key_name
}
locals.tf
locals {
  participants = [
    # チーム名を列挙
    "Team1",
    "Team2",
    ...
  ]
}

PHP実装での参加を予定しているチームが多かったので、初期の参考実装はPHPに変更させて貰いました。
(1チームだけGoで実施するチームがあり、経験がない中健闘していたのは素晴らしかったです🤓)

PHP実装への切り替え
sudo systemctl stop isu-ruby
sudo systemctl disable isu-ruby
sudo rm /etc/nginx/sites-enabled/isucon.conf
sudo ln -s /etc/nginx/sites-available/isucon-php.conf /etc/nginx/sites-enabled/
sudo usermod -aG isucon www-data
sudo systemctl reload nginx
sudo systemctl start php8.1-fpm
sudo systemctl enable php8.1-fpm

他にも /etc/hosts の設定など行いましたが、ここでは割愛させて頂きます。🙏

ポータルサイト構築

前述の通り、ポータルサイトは iwashi/private-isu-portal を元に構築しました。
スコアの格納先は同様にFirebaseのRealtime Databaseを使っており、そこへのデータPOSTは下記の様なscriptを用意しました。
(恥ずかしながら当日利用していた最新のscriptを紛失してしまい...参考にされる方は動作確認し適宜改良した上でご利用ください)

ベンチを回してポータルサイト用DBへデータを投げ込むscript例
bench.sh
#!/usr/bin/env bash

# チーム名をインスタンスのタグから取得
TEAM_NAME=$(curl http://169.254.169.254/latest/meta-data/tags/instance/TeamName)

# ベンチを実行(`b-isucon.01.dev` 等のホスト名は `/etc/hosts` に登録されている想定です)
BENCH_RESULT=$(./private_isu.git/benchmarker/bin/benchmarker -u /home/isucon/private_isu.git/benchmarker/userdata -t http://b-isucon.01.dev)

# timestampを付与する
POST_DATA=$(echo $BENCH_RESULT | jq '. + {"timestamp": {".sv": "timestamp"}}')

# データをRealtime DatabaseにPOST
curl -X POST "https://b-isucon-202210-default-rtdb.firebaseio.com/teams/${TEAM_NAME}" -d "${POST_DATA}"

競技中

当日の競技中は、非常に白熱したものになりました。
皆さん黙々と改善を進める瞬間もあれば、チームでコミュニケーションを取る時間、また大きく点数を稼ぎ会場が沸くタイミングもあり...。
この辺りの詳細については社内の様子を発信しているThe BEENOSで近日中に公開しますので、しばしお待ちください。🙏
当日の詳細をこちらで記事として公開しました。 🙆‍♂️
写真多めでお送りしておりますので、興味のある方は是非ご参照ください! ✨

https://beenos.com/blog/contents/5757/

競技終了、計測

競技時間が終了した後、運営側で各チームのインスタンスに対してベンチマーカーを走行させて貰いました。
参加者の皆様はほぼ初めてのISUCON参加であったので、下記の手順でかなり念入りにベンチマークを回しました。

  1. 競技終了後の状態でベンチマーク
  2. 各チームのベンチマーカーインスタンスを落としてベンチマーク
  3. 各チームのインスタンスに再起動をかけ、SSH接続経由やHTTPリクエスト投げるなどでアプリ状態確認
  4. ベンチ x 3回
  5. ベンチ後に再度アプリ状態確認

これほど入念にベンチマークを回したのは、再起動後にベンチが完走しないチームもいるのでは...?という危惧をしてのものだったのですが、杞憂でした。🤓

総評、まとめ

今回多くの参加者が初めてこの様なチューニングコンテストに取り組むので、運営側としては若干心配していましたが、全てのチームにおいてハンズオンで手を動かして学んだことは実践できていたようでした。
(slow queryの分析からのindex構築や、アプリとDBのサーバーを分離するなど)
全くスコアが上がらない、というチームはなかったようで、それなりに楽しんで頂けたのではないかなと思います。

終了後アンケートを取ったのですが、皆さん普段の仕事では関わらない領域に触れる良い機会となった、というのが共通した感想でした。
いくつか原文ママで引用させて頂きます。 🙏

  • これまでインフラ周り(サーバー設定等)を触ったことがほとんどなかったため、良い経験になりました。調べながらやれば意外とできるものですね。
  • ISUCONを通じて学んだことは、業務にも活かせそうなことが多かったので、今後もISUCONをテーマにするのは良さそうな気がしました。
  • アプリはインフラのことを、インフラはアプリのことを取り組む機会として、ISUCONはよい機会になるなと思ったので、今後も継続して取り組んでいけたら素敵だと思います。

参加頂いた方々のWeb技術知識の広さ&深さを広げられる良い機会であるので、BEENOSにおいては今後もB-ISUCON等の催しを継続的に行い、社内エンジニアの技術研鑽に努めていければと思う次第です。
皆さん引き続き頑張っていきましょう、わいわい!

Wanted!!

BEENOSグループでは一緒に働いて頂けるエンジニアを強く求めております!
少しでも気になった方は、社内の様子や大事にしていることなどをThe BEENOSにて発信しておりますので、是非ご覧ください。

https://beenos.com/blog/

とても気になった方はこちらで求人も公開しておりますので、お気軽にご応募ください!
「自分に該当する職種がないな...?」と思った方はオープンポジションとしてご応募頂けると大変嬉しく思います 🙌

世界で戦えるサービスを創っていきたい方、是非是非ご連絡ください!よろしくお願い致します!!

世界で戦えるサービスを創っていく

BEENOS Tech Blog

Discussion