Git Product home page Git Product logo

private-isu's Introduction

private-isu

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

本リポジトリが書籍の題材になりました。詳しくは以下のURLをご覧ください。

ハッシュタグ: #ISUCON本

タイムライン

2016年に作成した社内ISUCONリポジトリを2021年に手直ししました。2022年に書籍の題材になりました。

[2016年開催時のブログ]

過去ISUCON公式で練習問題として推奨されたことがある。

[2021年開催時のブログ]

ディレクトリ構成

├── ansible_old  # ベンチマーカー・portal用ansible(非推奨)
├── benchmarker  # ベンチマーカーのソースコード
├── portal       # portal(非推奨)
├── provisioning # 競技者用・ベンチマーカーインスタンスセットアップ用ansible
└── webapp       # 各言語の参考実装
  • manual.mdは当日マニュアル。一部社内イベントを意識した記述があるので注意すること。
  • public_manual.md は事前公開レギュレーション

OS

Ubuntu 22.04

起動方法

  • Ruby/Go/PHPの3言語が用意されており、デフォルトはRubyが起動する
    • Node.jsは現状メンテナンスされていない
    • AMI・Vagrantで他の言語の実装を動かす場合はmanual.mdを参考にする
  • AMI・Docker Compose・Vagrantが用意されている
    • 手元で適当に動かすことも難しくない
    • Ansibleを動かせば、他の環境でも動くはず
    • cloud-initも利用可能

AMI

セキュリティのアップデートなどは行わないので自己責任で利用してください。Node.jsのセットアップはskipしているので、Ruby/PHP/Goのみ利用可能。

  • 競技者用インスタンスはSecurity groupで80番ポートを公開する必要がある
    • Network settingsで「Allow HTTP traffic from the internet」にチェックを入れてもよい
  • ベンチマーカー用インスタンスはコンピューティング最適化インスタンスでそれなりのスペックでの利用を推奨

ベンチマーカー用インスタンスのベンチマーカー実行方法

$ sudo su - isucon
$ /home/isucon/private_isu.git/benchmarker/bin/benchmarker -u /home/isucon/private_isu.git/benchmarker/userdata -t http://<target IP>

競技者用インスタンス上でのベンチマーカー実行方法(アプリケーションと同居する形になるため非推奨)

$ sudo su - isucon
$ /home/isucon/private_isu/benchmarker/bin/benchmarker -u /home/isucon/private_isu/benchmarker/userdata -t http://localhost

最初はRuby実装が起動しているので、他の言語を使用する場合はmanual.mdを見て作業すること。

以下のAMI IDで起動する。リージョンは『Asia Pacific (Tokyo)』。

競技者用 (Ubuntu 24.04):

用途 AMI ID AMI name 推奨インスタンスタイプ
x86_64 ami-047fdc2b851e73cad catatsuy_private_isu_amd64_20240602 c7a.large
arm64 ami-0bed62bba4100a4b7 catatsuy_private_isu_arm64_20240602 c7g.large

ベンチマーカー (Ubuntu 24.04):

用途 AMI ID AMI name 推奨インスタンスタイプ
x86_64 ami-037be39355baf1f2e catatsuy_private_isu_bench_amd64_20240602 c7a.xlarge
arm64 ami-034a457f6af55d65d catatsuy_private_isu_bench_arm64_20240602 c7g.xlarge

手元で動かす

いずれの手順もディスク容量が十分にあるマシン上で行うこと

  • アプリケーションは各言語の開発環境とMySQL・memcachedがインストールされていれば動くはず
  • ベンチマーカーはGoの開発環境とuserdataがあれば動く
  • Dockerとvagrantはメモリが潤沢なマシンで実行すること

初期データを用意する

必要になるので、以下の手順を行う前に必ず実行すること。

make init

MacやLinux上で適当に動かす

MySQLとmemcachedを起動した上で以下の手順を実行。

  • Ruby実装以外は各言語実装の動かし方を各自調べること
  • MySQLのrootユーザーのパスワードが設定されていない前提になっているので、設定されている場合は適宜読み替えること
bunzip2 -c webapp/sql/dump.sql.bz2 | mysql -uroot

cd webapp/ruby
bundle install --path=vendor/bundle
bundle exec foreman start
cd ../..

cd benchmarker
make
./bin/benchmarker -t "http://localhost:8080" -u ./userdata
# ./bin/benchmarker -t "http://<競技者用インスタンスのグローバルIPアドレス>/" -u ./userdata

# Output
# {"pass":true,"score":1710,"success":1434,"fail":0,"messages":[]}

Docker Compose

アプリケーションは以下の手順で実行できる。dump.sql.bz2を配置しないとMySQLに初期データがimportされないので注意。

cd webapp
docker compose up

(もしうまく動かなければdocker-compose upを使うとよいかもしれません)

デフォルトはRubyのため、他言語にする場合はdocker-compose.ymlファイル内のappのbuildを変更する必要がある。PHPはそれに加えて以下の作業が必要。

cd webapp/etc
mv nginx/conf.d/default.conf nginx/conf.d/default.conf.org
mv nginx/conf.d/php.conf.org nginx/conf.d/php.conf

ベンチマーカーは以下の手順で実行できる。

cd benchmarker
docker build -t private-isu-benchmarker .
docker run --network host -i private-isu-benchmarker /opt/go/bin/benchmarker -t http://host.docker.internal -u /opt/go/userdata
# Linuxの場合
docker run --network host --add-host host.docker.internal:host-gateway -i private-isu-benchmarker /opt/go/bin/benchmarker -t http://host.docker.internal -u /opt/go/userdata

動かない場合はip aしてdocker0のインタフェースでホストのIPアドレスを調べてhost.docker.internalの代わりに指定する。以下の場合は172.17.0.1を指定する。

3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:ca:63:0c:59 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:caff:fe63:c59/64 scope link
       valid_lft forever preferred_lft forever

Vagrant

手元にansibleをインストールしてvagrant upすればprovisioningが実行される。

benchからappのIPアドレスを指定して負荷をかける。

# appのIPアドレスを調べる
$ vagrant ssh app
$ ip a

3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 08:00:27:37:2b:2c brd ff:ff:ff:ff:ff:ff
    inet 172.28.128.6/24 brd 172.28.128.255 scope global dynamic enp0s8
       valid_lft 444sec preferred_lft 444sec
    inet6 fe80::a00:27ff:fe37:2b2c/64 scope link
       valid_lft forever preferred_lft forever

# benchで負荷をかける
$ vagrant ssh bench
$ sudo su - isucon
$ /home/isucon/private_isu.git/benchmarker/bin/benchmarker -u /home/isucon/private_isu.git/benchmarker/userdata -t http://172.28.128.6

最初はRuby実装が起動しているので、他の言語を使用する場合はmanual.mdを見て作業すること。

cloud-init を利用して環境を構築する

matsuuさんのcloud-initに対応した環境でISUCONの過去問を構築するためのcloud-config集を利用して競技者用・ベンチマーカーインスタンスの構築ができます。

cloud-initに対応した環境、例えばAWS、Azure、Google Cloud、Oracle Cloud、さくらのクラウド、Multipass、VMwareなど、クラウドからローカルまで幅広く環境構築が可能です。

Apple Silicon搭載のマシン上で動作させる場合はMultipassを利用することを推奨します。

https://github.com/matsuu/cloud-init-isucon/tree/main/private-isu

ISUCON過去問題の環境を「さくらのクラウド」で構築する | さくらのナレッジ https://knowledge.sakura.ad.jp/31520/

Cloud Formationを利用して構築する

https://gist.github.com/tohutohu/024551682a9004da286b0abd6366fa55 を参照

競技者用・ベンチマーカーインスタンスのセットアップ方法

自分で立ち上げたい人向け。provisioning/ディレクトリ以下参照。

事例集

他の言語実装

private-isu's People

Contributors

23akei avatar akkyorz avatar catatsuy avatar dependabot[bot] avatar edvakf avatar fujiwara avatar kazeburo avatar renovate-bot avatar renovate[bot] avatar rosylilly avatar shinyasakurai avatar t-cyrill avatar walf443 avatar whywaita avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

private-isu's Issues

初期データで1つのpostについたコメントは全部同じユーザーがつけたことになっていた

1.upto(100_000).each do |i|
  post_id = (71237 * i) % 10000 + 1 # 71237 は適当に大きな素数
  user_id = (22229 * i) % 1000 + 1 # 22229 は適当に大きな素数
  comment = kaomoji[(9323 * i) % kaomoji.length] # 9323 は適当に大きな素数
  created_at = DateTime.parse('2016-01-03 00:00:00') + (1.to_r / 24 / 60 / 60 * i) # 毎秒1コメントされたことにする

  query.execute(i, post_id, user_id, comment, created_at.to_time)
end

適当に大きな素数を使うのは良くないな。

mypageの仕様を変えたい

自分のつけたコメントと自分の写真がまぜこぜに並ぶページというのが普通のウェブアプリケーションでありえない気がする。

そういう仕様なので全件習得してsortするしかなくて微妙。

普通に自分の写真が一覧で並んでればいいような。

↑それだとISUCON的におもしろいポイントが無さそうなのが問題といえば問題かもしれないけど。

各postの個別ページを作る

  • 今のトップページでコメントするとトップページの一番上に戻ってしまうことを解消したい
    • コメント投稿欄はトップページから個別ページに移動
  • ベンチマーカーがコメントしたときに、ちゃんと反映されているかチェックできる
  • ここで出てるコメントの数と、トップページでCOUNTクエリで出すコメントの数があってることをベンチマーカーでチェックしたい

ベンチマークかけてたらたまにapp.rbが Protocol wrong type for socket (Errno::EPROTOTYPE)

たまに出る。良いんだろうか。

03:48:08 unicorn.1 | E, [2016-03-15T03:48:08.227746 #61224] ERROR -- : app error: Protocol wrong type for socket (Errno::EPROTOTYPE)
03:48:08 unicorn.1 | E, [2016-03-15T03:48:08.227964 #61224] ERROR -- : /Users/atsushi/go/src/github.com/catatsuy/private-isu/webapp/ruby/vendor/bundle/ruby/2.1.0/gems/unicorn-5.0.1/lib/unicorn/http_response.rb:54:in `write'
03:48:08 unicorn.1 | E, [2016-03-15T03:48:08.228092 #61224] ERROR -- : /Users/atsushi/go/src/github.com/catatsuy/private-isu/webapp/ruby/vendor/bundle/ruby/2.1.0/gems/unicorn-5.0.1/lib/unicorn/http_response.rb:54:in `block in http_response_write'
03:48:08 unicorn.1 | E, [2016-03-15T03:48:08.228148 #61224] ERROR -- : /Users/atsushi/go/src/github.com/catatsuy/private-isu/webapp/ruby/vendor/bundle/ruby/2.1.0/gems/rack-1.6.4/lib/rack/lint.rb:714:in `block in each'
03:48:08 unicorn.1 | E, [2016-03-15T03:48:08.228199 #61224] ERROR -- : /Users/atsushi/go/src/github.com/catatsuy/private-isu/webapp/ruby/vendor/bundle/ruby/2.1.0/gems/rack-1.6.4/lib/rack/file.rb:110:in `block in each'
03:48:08 unicorn.1 | E, [2016-03-15T03:48:08.228307 #61224] ERROR -- : /Users/atsushi/go/src/github.com/catatsuy/private-isu/webapp/ruby/vendor/bundle/ruby/2.1.0/gems/rack-1.6.4/lib/rack/file.rb:102:in `open'
03:48:08 unicorn.1 | E, [2016-03-15T03:48:08.228487 #61224] ERROR -- : /Users/atsushi/go/src/github.com/catatsuy/private-isu/webapp/ruby/vendor/bundle/ruby/2.1.0/gems/rack-1.6.4/lib/rack/file.rb:102:in `each'
03:48:08 unicorn.1 | E, [2016-03-15T03:48:08.228590 #61224] ERROR -- : /Users/atsushi/go/src/github.com/catatsuy/private-isu/webapp/ruby/vendor/bundle/ruby/2.1.0/gems/rack-1.6.4/lib/rack/body_proxy.rb:31:in `each'
03:48:08 unicorn.1 | E, [2016-03-15T03:48:08.228747 #61224] ERROR -- : /Users/atsushi/go/src/github.com/catatsuy/private-isu/webapp/ruby/vendor/bundle/ruby/2.1.0/gems/rack-1.6.4/lib/rack/lint.rb:708:in `each'
03:48:08 unicorn.1 | E, [2016-03-15T03:48:08.228922 #61224] ERROR -- : /Users/atsushi/go/src/github.com/catatsuy/private-isu/webapp/ruby/vendor/bundle/ruby/2.1.0/gems/rack-1.6.4/lib/rack/body_proxy.rb:31:in `each'
03:48:08 unicorn.1 | E, [2016-03-15T03:48:08.228995 #61224] ERROR -- : /Users/atsushi/go/src/github.com/catatsuy/private-isu/webapp/ruby/vendor/bundle/ruby/2.1.0/gems/unicorn-5.0.1/lib/unicorn/http_response.rb:54:in `http_response_write'
03:48:08 unicorn.1 | E, [2016-03-15T03:48:08.229047 #61224] ERROR -- : /Users/atsushi/go/src/github.com/catatsuy/private-isu/webapp/ruby/vendor/bundle/ruby/2.1.0/gems/unicorn-5.0.1/lib/unicorn/http_server.rb:573:in `process_client'
03:48:08 unicorn.1 | E, [2016-03-15T03:48:08.229096 #61224] ERROR -- : /Users/atsushi/go/src/github.com/catatsuy/private-isu/webapp/ruby/vendor/bundle/ruby/2.1.0/gems/unicorn-5.0.1/lib/unicorn/http_server.rb:658:in `worker_loop'
03:48:08 unicorn.1 | E, [2016-03-15T03:48:08.229144 #61224] ERROR -- : /Users/atsushi/go/src/github.com/catatsuy/private-isu/webapp/ruby/vendor/bundle/ruby/2.1.0/gems/unicorn-5.0.1/lib/unicorn/http_server.rb:508:in `spawn_missing_workers'
03:48:08 unicorn.1 | E, [2016-03-15T03:48:08.229192 #61224] ERROR -- : /Users/atsushi/go/src/github.com/catatsuy/private-isu/webapp/ruby/vendor/bundle/ruby/2.1.0/gems/unicorn-5.0.1/lib/unicorn/http_server.rb:132:in `start'
03:48:08 unicorn.1 | E, [2016-03-15T03:48:08.229248 #61224] ERROR -- : /Users/atsushi/go/src/github.com/catatsuy/private-isu/webapp/ruby/vendor/bundle/ruby/2.1.0/gems/unicorn-5.0.1/bin/unicorn:126:in `<top (required)>'
03:48:08 unicorn.1 | E, [2016-03-15T03:48:08.229295 #61224] ERROR -- : /Users/atsushi/go/src/github.com/catatsuy/private-isu/webapp/ruby/vendor/bundle/ruby/2.1.0/bin/unicorn:23:in `load'
03:48:08 unicorn.1 | E, [2016-03-15T03:48:08.229341 #61224] ERROR -- : /Users/atsushi/go/src/github.com/catatsuy/private-isu/webapp/ruby/vendor/bundle/ruby/2.1.0/bin/unicorn:23:in `<main>'

Memcachedを用意しておく

2つの理由から。

  • TPC接続しまくる罠
  • SESSIONをset, getするような便利関数を用意しておいて、キャッシュにはこれを使ってね、というヒント

初期データを用意する

  • メモリに乗りきらないぐらいがいい
    • とりあえず1GBぶんぐらいはほしい
    • 画像1枚100kBぐらいなので、1万postぐらい用意
    • ユーザー数は1000ぐらい
    • コメント数は10万ぐらいあるとよい
  • ラーメン画像はhttp://twilog.org/catatsuy をスクレイピングして、ImageMagickでフィルターをかけてハッシュ値を変える

チェッカーまわりリファクタリングしたい

#83 (comment)

CheckFuncの中で次のリクエストをするのをやめたい

CheckFuncはそのリクエストのチェックに徹したい。

goは内部的には非同期IOだけどうまく隠蔽されててコールバックスタイルにならずにすむところが良いところのはず。一直線に書きたい。

before

    a := checker.NewAction("GET", "/")
    a.ExpectedStatusCode = http.StatusOK
    a.ExpectedLocation = "/"
    a.Description = "/にある画像にひたすらアクセス"
    a.CheckFunc = func(s *checker.Session, body io.Reader) error {
        doc, _ := goquery.NewDocumentFromReader(body)

        imgCnt := doc.Find("img").Each(func(_ int, selection *goquery.Selection) {
            url, _ := selection.Attr("src")
            imgReq := checker.NewAction("GET", url)
            imgReq.Play(s)
        }).Length()

        if imgCnt < PostsPerPage {
            return errors.New("1ページに表示される画像の数が足りません")
        }
        return nil

    }

after

    a := checker.NewAction("GET", "/")
    a.ExpectedStatusCode = http.StatusOK
    a.ExpectedLocation = "/"
    a.Description = "/にある画像にひたすらアクセス"

        imageUrls := []string
    a.CheckFunc = func(s *checker.Session, body io.Reader) error {
        doc, _ := goquery.NewDocumentFromReader(body)

        imgCnt := doc.Find("img").Each(func(_ int, selection *goquery.Selection) {
            url, _ := selection.Attr("src")
            imageUrls = append(imageUrls, url)
        }).Length()

        if imgCnt < PostsPerPage {
            return errors.New("1ページに表示される画像の数が足りません")
        }
        return nil
    }
    a.Play(s)

    for _, url := range imageUrls {
        imgReq := checker.NewAction("GET", url)
        imgReq.Play(s)
    }

この例だと複雑になってるけど、2つ目のリクエストがさらにCheckFuncを持つときにはこのほうが良い。

PlayWithURL はなくせそう

CheckFuncがリダイレクト先のURLを引数に受け取るだけで良さそう

CheckFuncがSessionを受け取る必要もなさそう

外のスコープの値を使えば良さそう。(どちらでもよい)

PlayとCheckFuncを分けずにの第二引数にCheckFuncを渡すスタイルでいい気がする

そのほうがコードが一直線になって良さそう

genActionがアチコチ飛ぶのがなんか気持ち悪い

genActionはすべて消して、シナリオごとに流れを一直線に書けばいい気がしてる。

その上で共通部分をくくりだす感じで。

indexで「もっとみる」

LIMIT, OFFSET方式にしたいけど、アプリケーション的にmax_id方式のほうが自然。

max_idで継ぎ足すんだけど、中でやってることは同じw という感じにするとよさそう。

絶妙な遅さにしたい

現状↓これが8秒ぐらいかかる

SELECT * FROM posts ORDER BY created_at DESC

カラムを絞れば0.04秒になる

SELECT id,user_id,body,created_at FROM posts ORDER BY created_at DESC

↓ここも15秒ぐらいかかってる。

      cs.each do |c|
        unless comments[c[:post_id]]
          comments[c[:post_id]] = []
        end
        comments[c[:post_id]].push(c)
      end

コメントは10万件あるからなー。

さすがに20秒以上かかるのはつらいので、コメントは必要な分だけN+1で取るようにしようかな。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.