もくじ
スタートダッシュ Source->GitLab->EC2デプロイまで
ぐぬさんによるまとめ><
0. 事前準備 ・デプロイコマンドを先に準備する ・アプリ再起動、キャッシュ削除など ・チェックリスト用意 1. まずベンチマークを実行する →ベンチマークを実行して、スコアやログはコミット時点なのかも記録 ※MYSQLの設定、nginXの設定などもコミットするように ※gitのタグを使ってスコアを記録するのもある ーーベンチマーク重要ポイントーーーーーーーーー ・細かく書いて1コミットをする。 ・1コミットをして1ベンチぐらいが最高 ・ぶれる可能性がるので2、3回ぐらい実行する ・測定すべき指標 ・CPU占有率の高いプロセスがいないか →htopをベンチ実行しながら確認 ・アクセスログにどんな記録がされているか →alpを利用、LTSVにする巣にぺっとを事前、思いエントポイントから直すAVG高いもの ・データベースに発行されていないくりは何か ・各種リソースはベンチマーク実行中どう使われているか ・CPU、メモリ、帯域、IO ーーーーーーーーーーーーーーーーーーーーーーー 2. マニュアルを1時間見る、以下の項目確認 ・採点ルール ・原点ルール ・ユーザーエージェントの確認 ※クローラ巡回拒否対策&検証方法 https://qiita.com/comefigo/items/1e0265b342c4166ee8bf) 3. サーバーの状態を確認 ・cpu確認:cat /proc/cpuinfo ・メモリ:free -h ・サービス:systemctl list-units --type=service --state=running 4. git管理 ・git init してgit add .&& git commit -m "initial state" 5. 開発 ・アプリのログを残して、ボトルネックとかを解決 6. やれることがないへの対象(何をすればいいかわからないの時) ・スキーマ、アクセスローグ、メトリクス、マニュアルを読み直す ・計測をして情報量を増やす 7. 便利なツールを入れてみる ・netdata, New relic、pprof 8. 掃除しておく ・MySql再起動しておく ・便利なツールを外す(リソースを食う) ・最終ベンチに向けて、アプリに必要なもの以外をきちんと止められるように準備 ・案外忘れられるRACK_ENVなどをちゃんと付ける 9. 他の人のスコアを見る ・解決できる部分をもっと調べる
0-1
- 最初にレギュレーションを3人で
- どんなアプリなのか。
- alp アクセスログ、スロウクエリで遅いクエリ
- EC2
- 評価スクリプト
- 何が原因で加点なのか?減点なのか?
- ルールの穴
・1秒で切れるようにキャッシュするようにするには? - ベンチマークしながら
コミットの点数をまとめていく
・点数高いやつ
・対応できそうなやつ - EC2へデプロイする為のGitの構築
・GitLabにリポジトリ … 今日つくっておいて
・UbuntuとGitLab - Git + SSH + Stashとかでシミュレーション(金広)
alp
- alp nginx
調べておく avg, countでcountが多くて
sum(count * avg) = そうレスポンスタイム
決めること
- チーム名(5分)
・ぐぬぬ!ぱんだちゃんハッカー(仮) - 戦術案(20分)
Go言語
●担当 - 当日までにやること(15分)
・ISUCON
https://github.com/isucon/isucon8-qualify
・H2O
・Goフレームワーク
・gin(岡田)
・Echo(金広)
・go redis cache echo
・毎週チェック
下記は草案
最初にやること
- アプリ全体をざっくり把握する
- スコア評価スクリプトを叩く
- スコア評価スクリプトが判定している内容を把握する
- Google Chromeのデベロッパーツールや、Firefoxのネットワークによって遅いAPIの処理を見つける
キャッシュ is King
- 値が変わらないキャッシュできるAPI
NoSQL RedisによるCacheをする - 値の変更が発生するAPI
・更新時にRedisのCacheをクリアし、再度キャッシュする
GO
- N^2になっているループ処理は修正する必要がある
GOインストール
VS Code プラグインチェック!
- 補完
- 関数ジャンプ
Go言語文法
金広はこれやってる
-
- Go言語からRedisにレスポンス内容をキャッシュ、POSTでの更新時にパージする方法
- 配列 … 要素数が固定 arr := [3]int{2, 3, 5}
- スライス … 要素数が可変 sl := []int{2, 3, 5}
・append() … 要素を追加
・make() … スライスを作成
・len() … 要素数取得
・cap() … 要素数最大取得。容量以上の要素が追加された時にメモリを2倍取得してしまう。メモリを気にするプログラミングで使う。またメモリを過剰に取得すると速度も落ちるので速度パフォーマンスを重視する時に使う。sl := []int{100, 200} fmt.Println(sl) // [100 200] sl = append(sl, 300) fmt.Println(sl) // [100 200 300] sl = append(sl, 400, 500, 600) fmt.Println(sl) // [100 200 300 400 500 600] sl2 := make([]int, 5) // 配列の作成 fmt.Println(sl2) // [0 0 0 0 0] fmt.Println(len(sl2)) // 5 ... 要素数取得 fmt.Println(cap(sl2)) // 5 ... 容量 ... メモリを気にするような時に使う sl3 := make([]int, 5, 10) // ... make([]T, 初期値で埋める要素数, 容量) sl4 := append(sl3, 1, 2, 3, 4, 5, 6, 7) fmt.Println(len(sl4)) // 12 fmt.Println(cap(sl4)) // 20・range … GO言語にforeachがないのでforとrangeでforeachを実現
sl := []string{"Python", "PHP", "GO"} for i, v := range sl { fmt.Println(i, v) } /* 0 Python 1 PHP 2 GO */ - copy() … PHPのclone()
sla := []string{"PHP", "GO"}
slb := sla
slb[0] = "Kotlin"
fmt.Println(sla) // [Kotlin GO] ... PHPが上書きされてしまった
sl := []int{1, 2, 3, 4, 5}
sl2 := make([]int, 5, 10)
n:= copy(sl2, sl) // sl2にslをコピーして代入。nにはコピーできた要素数が代入される
fmt.Println(n, sl2) // 5 [1 2 3 4 5]
-
-
-
- スプレッド演算子 […]
func Sum(s ...int) int { n := 0 for _, v := range s { n += v } return n } func main() { fmt.Println(Sum(1, 2, 10)) // 13 sl := []int{4, 5} fmt.Println(Sum(sl...)) // 9 } -
- map … 連想配列 … m := map[string]int{“apple”: 100, “banana”: 160}
m := map[string]int{"apple": 100, "banana": 200} for k, v := range m { fmt.Println(k, v) } /* banana 200 apple 100 */ // 要素追加 fmt.Println(m["apple"]) // 100 m["grape"] = 400 fmt.Println(m) // map[apple:100 banana:200 grape:400] // エラーハンドリング _, ok := m["hoge"] // 変数okにはboolが入る。hogeというキーは存在しないのでfalseが代入 if !ok { fmt.Println("error") } // error - for, range … foreach
sl := []string{"A", "B", "C"} fmt.Println(sl) // [A B C] for i := range sl { fmt.Println(i) // 0 // 1 // 2 } for i, v := range sl { fmt.Println(i, v) // 0 A // 1 B // 2 C } for i := 0; i < len(sl); i++ { fmt.Println(sl[i]) // A // B // C } - 型アサーション … 型の復元と評価
-
func main() {
i := interface{}("hello1")
s := i.(string)
fmt.Println(s) // hello1
// i2 := interface{}("hello2")
// s2 := i2.(int)
// fmt.Println(s2) // panic: interface conversion: interface {} is string, not int
i3 := interface{}("hello3")
n, ok := i3.(int) // 引数を2つ取ることでpanicを防止
fmt.Println(n, ok) // 0 false ... panicにならない
s3, ok := i3.(string)
fmt.Println(s3, ok) // hello3 true
var x interface{} = 3
if x == nil {
fmt.Println("None")
} else if _, isInt := x.(int); isInt {
fmt.Println(x, "x is Int")
} else if s4, isString := x.(string); isString {
fmt.Println(s4, "x is String")
} else {
fmt.Println("I don't know")
}
// 3 x is Int
// switchで再現
switch x.(type) {
case int:
fmt.Println(x, "x is Int")
case string:
fmt.Println(x, "x is String")
default:
fmt.Println("I don't know")
}
// 3 x is Int
// switchで再現
switch v5 := x.(type) {
case int:
fmt.Println(v5, "v5 is Int")
case string:
fmt.Println(v5, "v5 is String")
default:
fmt.Println("I don't know")
}
// 3 v5 is Int
}
-
- defer() … 処理の最後に実行するもの ex.
func TestDefer() {
defer fmt.Println("End")
fmt.Println("Start")
}
func main() {
TestDefer()
defer func() {
fmt.Println("1")
fmt.Println("2")
fmt.Println("3")
}()
// Start
// End
// 1
// 2
// 3
}
-
- 実践的な例
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.Create("test.txt")
if err != nil {
fmt.Println(err)
}
defer file.Close() // 最後に実行される
file.Write([]byte("Hello"))
}
-
-
- エラーハンドリング … panic(), defer()
func main() { defer func() { if x := recover(); x != nil { fmt.Println(x) } }() panic("runtime error") fmt.Println("Start") // runtime error } - 平行処理 go routin
package main import ( "fmt" "time" ) func sub() { for { fmt.Println("Sub Loop") time.Sleep(100 * time.Millisecond) } } func main() { go sub() go sub() for { fmt.Println("Main Loop") time.Sleep(200 * time.Millisecond) } } - 構造体 struct
- エラーハンドリング … panic(), defer()
package main import "fmt" type User struct { Name string Age int // X, Y int } // コピーに対して更新していて更新できない関数 func UpdateUser(user User) { user.Name = "A" user.Age = 1000 } // こう書く🐱 func UpdateUser2(user *User) { user.Name = "A" user.Age = 1000 } func main() { var user1 User fmt.Println(user1) // { 0} user1.Name = "user1" user1.Age = 10 fmt.Println(user1) // {user1 10} user2 := User{} fmt.Println(user2) // { 0} user2.Name = "user2" fmt.Println(user2) // {user2 0} user3 := User{Name: "user3", Age: 30} fmt.Println(user3) // {user3 30} user4 := User{Name: "user4", Age: 40} fmt.Println(user4) // {user4 40} user7 := new(User) fmt.Println(user7) // &{ 0} ... ポインタ型になる // 🐱基本はこの書き方 user8 := &User{} fmt.Println(user8) // &{ 0} ... ポインタ型になる UpdateUser(user1) fmt.Println(user1) // {user1 10} ... 変わらない UpdateUser2(user8) fmt.Println(user8) // &{A 1000} ... 更新することができている🐱 }構造体 メソッド
package main import "fmt" type User struct { Name string Age int // X, Y int } func (u User) sayName() { fmt.Println(u.Name) } // これだとコピーしたインスタンスのNameを更新していることになるので、更新できない func (u User) setName(name string) { u.Name = name } // 🐱こう書く func (u *User) setName2(name string) { u.Name = name } func main() { user1 := &User{"user1", 999} fmt.Println(user1) // &{user1 999} user1.sayName() // user1 user1.setName("A") user1.sayName() // user1 ... 変わってない user1.setName2("B") user1.sayName() // B ... 変わってる🐱 }構造体 埋め込み
package main import "fmt" type User struct { Name string Age int // X, Y int } type T struct { User User } type T2 struct { User } func main() { t := T{User: User{Name: "user1", Age: 10}} fmt.Println(t) // {{user1 10}} fmt.Println(t.User) // {user1 10} fmt.Println(t.User.Name) // user1 t2 := T2{User: User{Name: "user1", Age: 10}} fmt.Println(t2.User.Name) // user1 }構造体 コンストラクタ
package main import "fmt" type User struct { Name string Age int // X, Y int } func (u User) sayName() { fmt.Println(u.Name) } func NewUser(name string, age int) *User { return &User{Name: name, Age: age} } func main() { user1 := NewUser("user1", 999) fmt.Println(user1) // &{user1 999} }- チャネル + go routin
-
package main
import (
"fmt"
"time"
)
func reciver(c chan int) {
for {
i := <-c
fmt.Println(i)
}
}
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go reciver(ch1)
go reciver(ch2)
i := 0
for i < 100 {
ch1 <- i
ch2 <- i
time.Sleep(50 * time.Millisecond)
i++
}
}
-
- チャネル + go routin + close
- チャネル + go routin + close
package main
import "fmt"
func reciver(c chan int) {
}
// func main() {
// ch1 := make(chan int, 2)
// close(ch1)
// ch1 <- 1 // panic: send on closed channel
// }
func main() {
ch1 := make(chan int, 2)
ch1 <- 1
close(ch1)
i, ok := <-ch1
fmt.Println(i, ok)
// 1 true
i2, ok := <-ch1
fmt.Println(i2, ok)
// 0 false
}
-
-
-
-
- 応用
package main import ( "fmt" "time" ) func reciver(name string, ch chan int) { for { i, ok := <-ch if !ok { break } fmt.Println(name, i) } fmt.Println(name, "End") } func main() { ch1 := make(chan int, 2) ch1 <- 1 go reciver("1.goroutin", ch1) go reciver("2.goroutin", ch1) go reciver("3.goroutin", ch1) i := 0 for i < 100 { ch1 <- i i++ } close(ch1) time.Sleep(3 * time.Second) // 3.goroutin 1 // 2.goroutin 1 // 1.goroutin 0 // 1.goroutin 4 // 1.goroutin 5 // 1.goroutin 6 // 2.goroutin 3 // (略) // 1.goroutin 83 // 1.goroutin 84 // 1.goroutin 85 // 1.goroutin 86 // 1.goroutin 87 // 1.goroutin 88 // 2.goroutin 69 // 3.goroutin 82 // 3.goroutin 90 // 3.goroutin 92 // 3.goroutin 93 // 3.goroutin 94 // 1.goroutin 89 // 1.goroutin 96 // 2.goroutin 91 // 2.goroutin 98 // 2.goroutin 99 // End 2.goroutin // 1.goroutin 97 // End 1.goroutin // 3.goroutin 95 // End 3.goroutin }チャネル + go routin + for
package main import "fmt" func main() { ch1 := make(chan int, 3) ch1 <- 1 ch1 <- 2 ch1 <- 3 close(ch1) for i := range ch1 { fmt.Println(i) } // 1 // 2 // 3 }チャンネル + select その1
-
package main import "fmt" func main() { ch1 := make(chan int, 2) ch2 := make(chan string, 2) ch2 <- "A" select { case v1 := <-ch1: fmt.Println(v1 + 1000) case v2 := <-ch2: fmt.Println(v2 + "!?") } // A!? }その2
func main() { ch1 := make(chan int, 2) ch2 := make(chan string, 2) ch2 <- "A" ch1 <- 1 ch2 <- "B" ch1 <- 2 select { case v1 := <-ch1: fmt.Println(v1 + 1000) case v2 := <-ch2: fmt.Println(v2 + "!?") } // ランダムになる // kanehiroyuu@MacBook-Pro-2 golang-webgosql % go run channelSelect.go // A!? // kanehiroyuu@MacBook-Pro-2 golang-webgosql % go run channelSelect.go // A!? // kanehiroyuu@MacBook-Pro-2 golang-webgosql % go run channelSelect.go // 1001 // kanehiroyuu@MacBook-Pro-2 golang-webgosql % go run channelSelect.go // 1001 // kanehiroyuu@MacBook-Pro-2 golang-webgosql % go run channelSelect.go // A!? }その3
package main import "fmt" func main() { ch3 := make(chan int,) ch4 := make(chan int) ch5 := make(chan int) // reciever go func() { for { i := <-ch3 ch4 <- i * 2 } }() go func() { for { i2 := <-ch4 ch5 <- i2 -1 } }() n := 0 L: for { select { case ch3 <- n: n++ case i3 := <-ch5: fmt.Println("recieved", i3) default: if n > 100 { break L } } } } // kanehiroyuu@MacBook-Pro-2 golang-webgosql % go run channelSelect2.go // recieved -1 // recieved 1 // recieved 3 // recieved 5 // recieved 7 // recieved 9 // recieved 11 // recieved 13 // recieved 15 // ... // recieved 183 // recieved 185 // recieved 187 // recieved 189 // recieved 191 // recieved 193 // recieved 195 // recieved 197 -
-
-
- ポインタ
package main
import "fmt"
func Double(i int) {
i = i * 2
}
func Doublev2(i *int) {
*i = *i * 2
}
func Doublev3(s []int) {
for i, v := range s {
s[i] = v * 2
}
}
func main() {
var n int = 100
fmt.Println(n) // 100
fmt.Println(&n) // 0xc00012a008
Double(n)
fmt.Println(n) // 100
var p *int = &n // &変数 ... ポインタ
fmt.Println(p) // 0xc000132008
fmt.Println(*p) // 100 ... *をつけると実体
*p = 300 // *pで実体を指定して代入
fmt.Println(n) // 300
n = 200
fmt.Println(*p) // 200
Doublev2(&n)
fmt.Println(n) // 400
// スライスは参照型なので関数先で自動的に値が参照渡しされてる
var sl []int = []int{1, 2, 3}
Doublev3(sl)
fmt.Println(sl) // [2 4 6]
}
-
- CRUD操作 … 掲示板アプリなどで
- JSON操作
- フレームワーク
Echoの可能性が高い
フレームワークなしって可能性もある - Redisにレスポンスキャッシュ, 更新時にキャッシュクリア & キャッシュ
- init() … main()より先に呼ばれる関数
package main
import (
"fmt"
)
func init() {
fmt.Println("init()")
}
func main() {
fmt.Println("main()")
// init()
// main()
}
- ジョブ・キュー
ミドルウェア側のチューニング
- 画像やCSSといった静的ファイルの圧縮
- メモリの最適化
- プロセス、CPUといったパラメータチューニング
- MySQLのINDEXを貼る
Nginx
ルールが参照系だけの場合
- 可能な限りHTTPレスポンスはNginx側でキャッシュする
キャッシュ is キング。 - ログインが存在するアプリの場合
・ページキャッシュ
・セッションキーがある場合はキャッシュしないようにする
・おそらくルール上利用は難しい
・オブジェクトキャッシュ
・RedisによるNoSQL実装をする必要がある - http2の利用
・listen 443 ssl http2;
・listen 80 http2; - CPU設定 autoで自動設定
worker_processes auto;
- https://worklog.be/archives/3222#cache_pathconf
- https://www.linuxcapable.com/set-up-nginx-fastcgi-cache-on-ubuntu-20-04/
user www www;
pid /var/run/nginx.pid;
## auto を指定すれば最大限利用できる CPU コア数を勝手に設定してくれる
## 指定したいならサーバの CPU コア数以下で設定する
worker_processes auto;
worker_rlimit_nofile 4096;
events {
use epoll;
multi_accept on;
worker_connections 1024;
}
http {
include mime.types;
default_type text/plain;
charset utf-8;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
server_tokens off;
keepalive_requests 100;
keepalive_timeout 3;
server_names_hash_bucket_size 64;
types_hash_max_size 2048;
client_body_buffer_size 64k;
client_body_temp_path /home/www/tmp/client_body_temp 1 2;
## gzip圧縮を有効化
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript
application/json application/javascript application/x-javascript
application/xml application/rss+xml application/atom+xml
image/svg+xml;
## SSL周り
ssl_session_timeout 30m;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_dhparam /etc/nginx/ssl/dhparam.pem;
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256;
## fastcgi
fastcgi_buffers 8 64k;
fastcgi_buffer_size 64k;
fastcgi_connect_timeout 60;
fastcgi_send_timeout 60;
fastcgi_read_timeout 300;
## proxy (WordPressだけだったらここは不要)
proxy_connect_timeout 60;
proxy_send_timeout 60;
proxy_read_timeout 120;
proxy_http_version 1.1;
proxy_cache_bypass $http_upgrade;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
proxy_temp_path /home/www/tmp;
## cache_pathについては別ファイルで設定する
include conf.d/cache_path.conf;
## デフォルトのサーバ
server {
listen *:80 default_server;
root /home/www/htdocs;
access_log /var/log/nginx-access.log combined;
error_log /var/log/nginx-error.log warn;
index index.html;
include conf.d/common.conf;
}
## バーチャルドメインの設定
include conf.d/virtual.conf;
}
MySQL
- INDEXを貼る
・WHERE = {キー}
・ORDER BY {キー}
このキーに対してINDEXを貼る - EXPLAINによるALL解析EXPLAIN SELECT xxxx
mysql > EXPLAIN select * from sampledb.sampletable ;
typeにALLやindexがあったらINDEXを貼った方が良い。
indexの場合は、
typeがrefになるようにwhereの位置など変更してINDEXが効くようにクエリを改良する。
ただし、
一覧など全データを取ってきてという処理であれば、ALLは仕様がない場合もある。
- innodb_buffer_pool_size … GOの処理に割り当てる以外のメモリの8割はここに充てる
- query_cache_size=64M








