hoge な blog

メモとかtipsとか

ISUCON12予選の参加記

こんにちは、akitoです。

team: using namespace std; で参加しました。(5・7・5) (members: hidollara, sh4869)
誰と出ようかな〜と思っていたのですが、二人ともお誘いしたら快く参加を決めてくれて感謝です。

本記事では、ISUCON12予選の高速化のために行った手順についてのメモを残します。

リポジトリ: using-std/isucon12q

事前準備

使用したミドルウェア・コマンドの類は以下のとおりです。 資料やOSSなどの著作物を公開してくださった皆様に感謝します。:bow:

  • alp
  • pt-query-digest
    • MySQLのクエリを集計して解析するツール
  • kaz/pprotein
    • さくらのクラウド上に立てて使用しました
    • ログを集計し、ブラウザ上で確認できるようにする全部入りツールです
    • めちゃくちゃ便利でした。
    • (当日の問題ではSQLiteを使う箇所があったため、その部分はサーバのコンソール上で確認しました)
  • Discord
    • はい

当日

役割分担

メインの役回りは以下のとおりです。
自分はほとんど点数に貢献できなかったので、本当に感謝です。

  • akito: インフラ・設定周り(logとかDB分割とか)
  • hidollara: アプリケーション周り(キャッシュとかIndexとか)
  • sh4869: アプリケーション周り(N+1とかbulk insertとか)

サーバ構成

最終的な構成は、以下のとおりです。

  • Host1 nginx + app + SQLite(tenant)
  • Host2 MySQL(admin)
  • Host3 なし(本当はSQLiteを剥がしたかった)

全体の時系列

  • 10:00~
    • マニュアルを読み
    • git push
    • deploy用のMakefileの作成
    • pproteinの設定の有効化
    • (2717点)初回ベンチ
  • 12:00~
    • (3376点)Host2にmysqlを分ける
    • (4004点)sqliteにndexを追加する
    • (????点)SQLiteJSONログをはく
      • JSONログを集計する JSコードも同時に作成
    • (????点)InterpolateParams=true の設定を追加
  • 13:30~
    • (5606点)dispenseID() でuuidを使う
    • (????点)playersの投入をbulk insertする
    • SQLiteで管理されているデータを /initialize 時に更新しようとする
      • SQLite*.db ファイルごとにテーブルを全てreadして、その結果を逐一Bulk insertした
      • 33秒くらいかかっていたので、ちょうどタイムアウトしてしまう
      • revert
    • この辺から"write tcp 127.0.0.1:3000->127.0.0.1:38182: write: broken pipe" みたいなログが出てくる
  • 16:00~(この辺からメモが怪しい)
    • JWTKeyをメモリ上にキャッシュ
    • RankingのN+1の解消
    • middleware類のチューニング
      • max connection とか file limit とか
  • 17:00~
    • 再起動試験
    • app×2構成にするか検討するが、実装が間に合うか&可能なのか がわからず、断念
    • "write tcp 127.0.0.1:3000->127.0.0.1:38182: write: broken pipe" みたいなログが出ており、減点されていたが、原因が解明できず終了
    • logの停止
    • お祈りフェーズ

やりたかったこと

  • JSONログの停止
    • 忘れてた
  • 3台目をDBサーバとして使う
    • MySQLにdumpしたかった
    • (NFSでマウントして使うとかもあったらしい、気づかなかった)
  • error: broken pipe の解消

おわりに

SQLiteに面食らってしまいましたが、とても面白い問題でした。 エラーの解消ができなかったのが心残りです。

再起動試験は通っているんでしょうか...
インフラ面で強くなりたい(願望)。

ICTSC2021 夏の陣に参加しました

はじめに

icttoracon.net

2021年08月の28(土), 29(日)で開催された ICTトラブルシューティングコンテスト2021 夏の陣(ICTSC2021 Summer) に参加しました。

チーム名は「add忘れ VLAN全消し 疎通なし」です。 本番は自分含め3人で参加しました。

(2021/08/30追記) 全体4位でした。入賞圏内だったと思うので、悔しいですね... ただ、本番終了のタイミングでは6位だったのですが、最後にチームメイトが提出した解答が部分点を貰うことができたようで4位に上がることができたようです。 チームの皆さんありがとうございました🙇

f:id:AkkyOrz:20210830215902p:plain
4位を取ることができました

備忘録的なものとしてどんな問題をどう解いたのかを書き残したいと思います。 (後から問題が公開されると思うんですが、環境自体は自分の手元で再現出来ない可能性が高そうなので、覚えている限り思考の手順が残せれば嬉しいです)

私が解いた問題は以下のとおりです。

  • 2 頑固なindex.html(150)
  • 6 iscsi targetにログインできない!(200)
  • 8 アドレスが配布できない (200)
  • 9 止まらない (50)
  • 11 ャッシュサーバ立てたけど(250)
  • 12 Webサイトにアクセスできない(200)
  • 13 サーバ天気予報 (150)
  • 14 Webサーバが立ち上がらない(50)
  • 15 ンジンエックス(150)

2 頑固なindex.html (150点)

/var/www/html/index.htmlを変更した後、マシンを再起動すると元のファイルに戻ってしまう、という現象が生じていました。 最初はcronとかで上書きしているのかと思ったのですがcronには何も定義されていませんでした。 ならばサービスが何かやっているのかなと思い、/etc以下を探してみると、/etc/tmpfiles.d/ictsc.confに怪しげな記述が見つかりました。

/etc/tmpfiles.d/ictsc.conf

F /var/www/html/index.html 0777 root root - Welcome to ICTSC

どうやらsystemd-tmpfilesが悪さをしていたようです。 上記の部分をコメントアウトすることで、解決することが出来ました。

6 iscsi targetにログインできない! (200点)

iscsi何も分からない...と言いながら解いていました。

iscsi-targetに対してclientからログインしようとするシナリオとなっており、 以下のコマンドを打ってログインを試みたところ、ログインに失敗してしまう、というを解消する問題でした。

client$ sudo iscsiadm -m node --login
Logging in to [iface: default, target: iqn.2021-07.192.168.18.2:target, portal: 192.168.18.2,3260] (multiple)
iscsiadm: Could not login to [iface: default, target: iqn.2021-07.192.168.18.2:target, portal: 192.168.18.2,3260].
iscsiadm: initiator reported error (24 - iSCSI login failed due to authorization failure)
iscsiadm: Could not log into all portals

iSCSI login failed due to authorization failure と怒られていることから、認証系の何かが間違っていそうだと見当をつけました。

iscsi-target$ $ sudo targetcli
/iscsi/iqn.20....11:initiator> ls
o- iqn.2021-07.192.168.18.11:initiator ............................................................................ [Mapped LUNs: 1]
  o- mapped_lun0 ......................................................................................... [lun0 fileio/disk01 (rw)]
/iscsi/iqn.20....11:initiator> ls /
o- / ......................................................................................................................... [...]
  o- backstores .............................................................................................................. [...]
  | o- block .................................................................................................. [Storage Objects: 0]
  | o- fileio ................................................................................................. [Storage Objects: 1]
  | | o- disk01 .................................................... [/var/lib/iscsi_disks/disk01.img (5.0GiB) write-back activated]
  | |   o- alua ................................................................................................... [ALUA Groups: 1]
  | |     o- default_tg_pt_gp ....................................................................... [ALUA state: Active/optimized]
  | o- pscsi .................................................................................................. [Storage Objects: 0]
  | o- ramdisk ................................................................................................ [Storage Objects: 0]
  o- iscsi ............................................................................................................ [Targets: 1]
  | o- iqn.2021-07.192.168.18.2:target ................................................................................... [TPGs: 1]
  |   o- tpg1 ............................................................................................... [no-gen-acls, no-auth]
  |     o- acls .......................................................................................................... [ACLs: 1]
  |     | o- iqn.2021-07.192.168.18.11:initiator .................................................................. [Mapped LUNs: 1]
  |     |   o- mapped_lun0 ............................................................................... [lun0 fileio/disk01 (rw)]
  |     o- luns .......................................................................................................... [LUNs: 1]
  |     | o- lun0 ............................................. [fileio/disk01 (/var/lib/iscsi_disks/disk01.img) (default_tg_pt_gp)]
  |     o- portals .................................................................................................... [Portals: 1]
  |       o- 0.0.0.0:3260 ..................................................................................................... [OK]
  o- loopback ......................................................................................................... [Targets: 0]
  o- vhost ............................................................................................................ [Targets: 0]
  o- xen-pvscsi ....................................................................................................... [Targets: 0]

ACLを見るとiqn.2021-07.192.168.18.11:initiatorとなっていることが分かるため、clientの認証情報を照らし合わせます。 設定は/etc/iscsi/initiatorname.iscsiにありました。 以下のように変更します。

...
InitiatorName=iqn.2021-07.192.168.18.11:initiator

再度ログインを試みて、ログインに成功することを確認できました。

$ sudo systemctl restart iscsid
$ sudo iscsiadm -m node --login
Logging in to [iface: default, target: iqn.2021-07.192.168.18.2:target, portal: 192.168.18.2,3260] (multiple)
Login to [iface: default, target: iqn.2021-07.192.168.18.2:target, portal: 192.168.18.2,3260] successful.

$ lsblk | grep -E "(NAME|sda)"
NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda      8:0    0    5G  0 disk 

8 アドレスが配布できない

IPv6のアドレスがDHCPサーバから上手く設定されない、という問題でした。 (正直動いたからヨシ!という感じで何も理解できていないので後で調査します...)

以下の3つの手順で解決しました。

  1. isc-dhcp-server6が動いていなかったので、configを書いて立ち上げる
  2. RAも広告したいので、radvd を立ち上げる
  3. net.ipv6.conf.all.forwarding = 1 をsetする

isc-dhcp-server6.serviceの起動

まず/etc/defalut/isc-dhcp-serverを編集します。

DHCPDv6_CONF=/etc/dhcp/dhcpd6.conf
DHCPDv6_PID=/var/run/dhcpd6.pid
INTERFACESv6="eth1"

変更した設定を適用します。

$ sudo systemctl enable isc-dhcp-server6
$ sudo systemctl restart isc-dhcp-server6

radvd の起動

さらに、radvdを設定します。

/etc/radvd.conf

interface eth1 {
  AdvSendAdvert on;
  MinRtrAdvInterval 3;
  MaxRtrAdvInterval 10;
  AdvOtherConfigFlag on;

  prefix fc01:1:1:1::/64 {
    AdvOnLink on;
    AdvAutonomous on;
    AdvRouterAddr on;
  };
};

正直設定の意味はあまり分かっていないのですが有効化します。

$ sudo systemctl enable radvd.service
$ sudo systemctl restart radvd.service

net.ipv6.conf.all.forwarding = 1 をsetする

たしかradvd.serviceのstatusを見たら「forwardingしなさい!」みたいなWarningが出ていたため、対応します。

以下の内容で/etc/sysctl.d/99-dhcp-server.confを作成します。

net.ipv6.conf.all.forwarding = 1

これをsudo sysctl -p /etc/sysctl.d/99-dhcp-server.confで適用します。

radvdを再起動してserverの設定は終了です。

あとはclient側で sudo dhclient -6 を実行するとアドレスが降ってきて、pingが通るようになったことを確認できました。

9 止まらない(50点)

フレームワークGinを使って作成されたシンプルなWebサーバをgoで立ち上げるシナリオです。 docker run .... go run main.goみたいな感じで立ち上げられるのですが、Ctrl + Cで止めることが出来ない、という問題を解決する課題でした。

https://docs.docker.com/engine/reference/run/#foreground によると、

A process running as PID 1 inside a container is treated specially by Linux: it ignores any signal with the default action. As a result, the process will not terminate on SIGINT or SIGTERM unless it is coded to do so.

となっており、 Ctrl+Cで終了できない、という問題は、docker runコマンドで起動するプロセスがあらゆるシグナルが無視することが原因だと考えました。

そのため、-it オプションを付与しました。

また、そのままではWebサーバは正常終了することができないため、以下のようにシグナルを待ち受けるよう、(前提条件「host上に追加でパッケージをインストールしてはいけない。」を満たすように)変更を加えました。

diff main.go main.go.bk 
7,10d6
< 
<       "log"
<       "os"
<       "os/signal"
18,42c14
<       server := &http.Server{
<               Addr:    ":8080",
<               Handler: r,
<       }
< 
<       quit := make(chan os.Signal)
<       signal.Notify(quit, os.Interrupt)
< 
<       go func() {
<               <-quit
<               log.Println("receive interrupt signal")
<               if err := server.Close(); err != nil {
<                       log.Fatal("Server Close:", err)
<               }
<       }()
< 
<       if err := server.ListenAndServe(); err != nil {
<               if err == http.ErrServerClosed {
<                       log.Println("Server closed under request")
<               } else {
<                       log.Fatal("Server closed unexpect")
<               }
<       }
< 
<       log.Println("Server exiting")
---
>       r.Run(":8080")

以下のように変更を行うことで、Ctrl+Cで正常終了することが出来ます。

11 キャッシュサーバ立てたけど(250点)

DNSに関する問題です。(多分) キャッシュサーバを立てたのにも関わらずgethostbyname()が実行されてもキャッシュサーバに問い合わせが行われない、という問題でした。

外部DNS(/etc/resolv.confで指定されているリゾルバ)よりsystemd-resolvedが優先させるように変更を加えます。 まず/etc/nsswitch.confhost:で指定されている部分を以下のように書き換えます。

#hosts:      files dns
hosts:      files resolve [!UNAVAIL=return] dns

そしてsystemd-resolvedを再起動することで、終了状態を満たします。

個人的に一番勉強になった問題でした。

12 Webサイトにアクセスできない(200点)

k8sに関する問題です。 Ingressでサービスを公開したが、curl -k https://<loadbalancer IP> とすると502レスポンスが返ってくる、という問題です。

確かまずはじめに、 ingress のログを見ると upstream sent too big header while reading response header from upstream みたいなエラーが出ていることを確認したと記憶しています。 proxy-buffer-sizeが8Bしかない状態だったので、適当な大きさまでproxy-buffer-sizeを上げました。

変更を適用して、再度curlしてみたのですが、今度はhttpsリクエストを待ち受けているはずなのにHTTPリクエストが来ているぞ、というエラーが返ってきました。 ingressからpodへのリクエストがHTTPで飛んでしまっていると見当を付け、HTTPSで振り分けるように変更を加えました。

最終的なdiffは以下のとおりです

$ diff ingress.yaml{,.origin}
7,8c7
<     nginx.ingress.kubernetes.io/proxy-buffer-size: "16k"
<     nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
---
>     nginx.ingress.kubernetes.io/proxy-buffer-size: "8"

13 サーバ天気予報 (150点)

CORSに関する問題です。

reverse proxy が web.hogeapi.hoge に振り分けていて、api.hogeにPOSTすると上手く行かない、みたいな問題だったと記憶しています。 apiサーバはExpressで記述されていました。

VNCで接続したPCのブラウザからweb.hogeにアクセスしてみたところ、OPTIONSPOSTでエラーを吐いていそうです。

3点変更を加えました。

  1. リバースプロキシ側のnginxのAccess-Control-Allow-MethodsにPOSTを加える
  2. リバースプロキシのnginxからAccess-Control-Allow-Origin を削除し、api側でapp.use(cors())を有効化する
  3. api側でOPTIONSリクエストを捌くようにする

diffは以下のとおりです。(この辺からメモが雑になる)

user@proxy-server$ diff /etc/nginx/nginx.conf{,.bk}
79c79,80
<             add_header Access-Control-Allow-Methods 'GET,POST';
---
>             add_header Access-Control-Allow-Origin '*';
>             add_header Access-Control-Allow-Methods 'GET';
user@api-server$ diff /srv/api/server.js{,.bk}
6c6
< app.use(cors());
---
> //app.use(cors());
16,20d15
< 
< app.options('/', (req, res) => {
<         res.status(204);
< });
< 

以下のファイルの変更を適用し、それぞれサーバとサービスを立ち上げると上手く行くことが確認できました。 (が、これも「なんか動いたからヨシ!」って感じになっています...)

後はapiサーバを永続化させろということだったので、適当にサービスを作ります。

$ sudo cat <<EOF | sudo tee /etc/systemd/system/ictsc-api.service
[Unit]
Description=ictsc2021-api
After=

[Service]
WorkingDirectory=/srv/api

User=user
Group=user
ExecStart=/usr/bin/npm start
ExecStop=/bin/kill -s QUIT \$MAINPID

Restart   = always
Type      = simple

[Install]
WantedBy=multi-user.target
EOF
$ sudo systemctl daemon-reload
$ sudo systemctl enable ictsc-api.service
$ sudo systemctl start ictsc-api.service
$ sudo systemctl status ictsc-api.service
● ictsc-api.service - ictsc2021-api
     Loaded: loaded (/etc/systemd/system/ictsc-api.service; enabled; vendor preset: enabled)
     Active: active (running) since Sat 2021-08-28 14:53:54 JST; 17s ago
   Main PID: 1665 (npm)
      Tasks: 19 (limit: 1108)
     Memory: 41.1M
     CGroup: /system.slice/ictsc-api.service
             ├─1665 npm
             ├─1686 sh -c node server.js
             └─1687 node server.js

Aug 28 14:53:54 api-server systemd[1]: Started ictsc2021-api.
Aug 28 14:53:55 api-server npm[1665]: > api@1.0.0 start /srv/api
Aug 28 14:53:55 api-server npm[1665]: > node server.js
Aug 28 14:53:55 api-server npm[1687]: server is listening on port 3000...

多分立ち上がっているのでOKです。

14 Webサーバが立ち上がらない(50点)

flaskが使われているプログラムを立ち上げようとすると、 どうやら以前は正常にプログラムが立ち上がっていたらしいのですが、pyenvを導入したことで立ち上がらなくなったようです。

多分pathが変わったのでは?と予想を立てたのですが、まあ強引にpipで入れて動かしました

$ pip3 install flask
$ python3 app/index.py &
 * Serving Flask app 'index' (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on all addresses.
   WARNING: This is a development server. Do not use it in a production deployment.
 * Running on http://192.168.7.1:5000/ (Press CTRL+C to quit)
 
$ curl 192.168.7.1:5000 
192.168.7.1 - - [28/Aug/2021 10:13:13] "GET / HTTP/1.1" 200 -
Hello Flask

OKっぽいです。

15 ンジンエックス(150点)

curl --http2 localhost でリクエストを送ったら200レスポンスが返ってくるようにせよ、という問題だった気がします。

以下のようにしてnginxを起動しようとするととエラーが出ました。 どうやらhttp2に関するモジュールが存在していないようです。

$ sudo /usr/local/nginx/sbin/nginx 
nginx: [emerg] the "http2" parameter requires ngx_http_v2_module in /usr/local/nginx/conf/nginx.conf:36

$ sudo /usr/local/nginx/sbin/nginx -V
nginx version: nginx/1.21.1
built by gcc 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04) 
configure arguments: --with-debug
# configure arguments: --prefix=/usr/share/nginx (中略) --with-http_v2_module (後略)となっていないため、サポートされていない

普段ならaptとかで入れ直す、みたいなことが出来ると思うんですが、今回はセキュリティ的な制約でファイルをダウンロードしてはいけない、といった制約があったと記憶しています(もしかしたらもっとマイルドな感じの制約だったかもしれません) nginxのモジュールを導入するにはコンパイルが多分必要だと思うのですが、ファイルをダウンロードしないことにはコンパイルできなさそうだなと考えてしまいました。 (多分想定解はnginxをコンパイルし直すことだったのだろうと思います。そのソースをVM上から探し当てることができませんでした)

そのため、以下のように/usr/local/nginx/conf/nginx.confに変更を加えてお茶を濁しました。(何も解決していない感がすごいですが...)

diff conf/nginx.conf{,.bk}
36c36
<         listen       80;
---
>         listen       443 http2;
$ sudo /usr/local/nginx/sbin/nginx
$ curl --http2 localhost
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

感想

昨年度はほとんど問題で解答にたどり着くことができなかったのですが、今回は沢山問題を解けた気がするので成長を感じました。

非常に教育的に感じる問題も多く、わからない問題を手探りでググりながら学んでいくことができ、色々なことを学ぶ良い機会になったと思います。 非常に楽しいイベントでした。 運営に携わった方々には本当に感謝しています。

いくつかの問題で、想定解と異なる解答をしたり、あるいはよくわからないけど動いている、みたいな状態で提出したりしてしまった部分があったので、改めて勉強し直す必要があるなと感じました。

ただ、久しぶりに長期間集中して作業することになったので、非常に目が疲れました... まずはゆっくり休もうと思います...

BoostnoteでMarkdown上で改行した際にpreview画面でも改行させる

本記事で扱うソフトウェア

  • Boost Note 0.17.1
  • Ubuntu 20.04

目的

多くのMarkdownエディタでは <br> を明示的に挿入してあげないと改行が行われない。 しかし、どうしてもenterキーを押すだけで改行を行いたい。

before
Markdown

abcd
efgh

あいうえお<br>
かきくけこ

preview

abcd efgh

あいうえお
かきくけこ

after
Markdown

abcd
efgh

あいうえお<br>
かきくけこ

preview

abcd
efgh

あいうえお
かきくけこ

方法

カスタムCSSを適用して上げれば目標を達成できることが分かったのでその方法についてメモしておく。

github.com

といっても上記のIssueを見るのが早いのだが。

"File" -> "Preferences" -> "Markdown" にて、p 要素に対して下記のCSSを追加する。

p {
    white-space: pre-wrap;
}

保存すると期待した動作になる。

以上

CyberAgent 2days サーバーサイド向け 開発型インターンシップ ONLINE 参加記

6/6~6/7の2日間でCyberAgentの「2days サーバーサイド向け 開発型インターンシップ ONLINE」に参加させていただきました.

2日間でISUCONのようなことをやっていきました(ISUCONエアプ並感)

具体的には

みたいなことをインターン参加者全員で競うというものです.

実装言語についてはNode.js, PHP, Java, Goから選ぶことが出来ました. 私はどれもそこまで詳しく知りませんでしたが(どれも1ヶ月も触っていない),なんとなくGoを選択しました.
Goの選択者が8割くらいでした.

インターンの中でどのようなことをしたのか,どう考えるべきだったかなどを振り返って行こうと思います.
メモを取りながらやっていたのですが,どうやら最後の最後でメモを他のファイルで上書きしてしまったようで,正しく書けるか心配です...
commitログを見ながら頑張って思い出します.

1日目

作業環境整理

とりあえず作業環境を整えることを目指しました.

  • ssh鍵登録
  • マシンスペック確認
  • zsh,vimの環境構築
  • 使用されているミドルウェアの設定ファイルやアプリケーションなどをhomeディレクトリにcopy
  • go実装に切り替える
  • nginx.confにaccess logを設定
  • ここでベンチを回す
  • MySQLでslow query logを整理
  • deployスクリプトを整備
  • MySQLの中身をdump
  • VSCode-remoteの設定
  • kataribe, pt-query-digest, pprofなどのインストール

を,大体上から順に行っていきました.

今思い返せば,

  • ベンチを回す
  • homeディレクトリにとりあえず全部移してはじめからgit管理する
  • deployごとにgit commitが走るようにする

などを行えていたらよかったと反省しています(ぶっ壊したときの復旧にめちゃくちゃ時間を掛けてしまったため)

これらを全部終えられたのが大体2時間くらいだと思います.

deployサイクルとしては,

  • homeディレクトリに存在するconfigファイルやアプリケーションをいじる
  • deploy.shを走らせる
  • プロファイルする

という感じなものを目指していたため,deploy.shの中で,

  • slow queryログ,access logのrotate
  • app.goのmakeと,サービスが動作していたディレクトリへのコピーとバックアップ

の2つをやりました.これもプロファイル情報を加工してすぐに簡単に見えるようにする工夫をすればよかったのかなと思います.

あとは,N+1あるぞ,と思って改善したり,COUNT(*)が色んなとこで呼ばれてて遅そうだな?とか思ってゴニョゴニョしていましたが,全くスコアは上昇しませんでした.
後で原因は判明したのですが,deploy.shを走らせたときに正しくデプロイされていなかったのが原因です.
「スコアが上がったときにcommitすればいいか〜」などと楽観的に考えていたため,あとで絶望することになりました.こういう人為的なミスを引き起こしそうなところはどんどんスクリプトに落としていくべきだったと後悔しています.

中間説明

中間説明では,

  1. プロファイラの設定
  2. 最初のボトルネック特定
  3. ボトルネック解消

のサイクルを一通り簡単に説明していただきました.
それを元にボトルネックを解消することにしました.
正直中間説明前までは何も改善作業をできておらず,スコアも最下位でした.プロファイラを見て何処のエンドポイントが悪さをしているのかがわかっても,何が悪さをしているのかが分からず途方に暮れていたので,中間説明に非常に感謝しています:bow:

indexを張る

2つくらい確かindexを張るところがあったので張りました.

GET / を改善しようと頑張る

めっちゃ重いクエリがgetPopularArticle()の中にあることが分かり,Countの結果でsortしているためこのクエリのまま改善することは難しそうだと分かりました.
とりあえず,articlesテーブルの中にカウンタを用意しておき,その値を読むようにしようと変更を試みましたが,実装が重くてこれでめちゃくちゃ時間を食ってしまいました.

1日目終了

以上で1日目に作業した作業は終わりです. 今振り返ってみると,この時点ではDBの変更しかスコアに反映されていないため,どの言語でやっても同じくらいのスコアだった感がありますね. (一応Goのコードも触っていたのですが,デプロイされていないためスコアには反映されていません)

2日目

N+1の改善

getUser

GET / は諦めて,他のプロファイルを見てみます. articlesが遅いというのがでてきたので,そこを見てみます. すると,getUserでauthorを取得した後にauthorが書いたarticleのupdated_atを取得するクエリをN回叩いていそうです. そこを改善しました. テーブルを結合してそのまま突っ込んであげれば良さそうでした.

( {{hogeFunc}} みたいにevalっぽい動きをするやつは,<!-- -->で囲ってコメントアウトしても呼ばれてしまうことに気づかずにベンチを回してしまい,スコアが伸びなくて焦りました)

getArticleTagNames

これもN+1でした. LEFT JOIN してあげればスコアが少しだけ上がりました.

再び GET / を改善しようと頑張る

さて,やはり GET / が遅いということで,slow queryを確認して見ると, getPopularArticle()が重いことが分かります. カウンタの実装は流石にきつそうだったので,どうしようかと思っていたところ, チームメンバーに「メモリ全然使われていないから,それを使うといいよ」というアドバイスを頂いたので,オンメモリで動かしてみようと思いました.

1回目

まずはじめに,DBへのアクセスを減らせばいいよな,と思い,iine_drity_flagのようなものを用意しました. これは,iineが作成,削除などを行われたことを管理するもので,flagがtrueであるときにgetPopularArticle()が呼ばれた時にだけDBにアクセスするようにしました. これにより,スコアが劇的に改善されました.(2000点台にまでスコアが伸ばせました)

しかしプロファイラはまだDBアクセスが律速だと言っています.

2回目

dirty_flagを消し去り,全てのiineCntをメモリで管理することを試みました.
具体的には,初期状態のcntをapp起動時にmap[article_id] = iineCntの形で初期化しておき,iineに変更が加えられたときに値を上下させ,sortさせることを考えました.
ただ,うまく初期化する方法が思いつかず,残り時間があと僅かだったため,この作業を停止しました.

最後のベンチマークを走らせる

以前のコミットに戻ってプロファイラを全て切ってVSCode-remoteも切ってスコアを計算しようとしました.ここで大ポカをやらかします. N+1を改善したときのcommit以降コミットを忘れていたせいで,スコアが大幅に落ちます. おそらく,getUser, getArticleTagNames, iine_dirty_flagの全ての作業を消し飛ばしてしまったのだと思います. iine_dirty_flagの作業は覚えていたためそれは復旧させましたが,getUser, getArticleTagNamesの変更を消し飛ばしてしまったことに気づいたのはベンチマークサーバ停止後でした.

最終スコア

  • index修正✕2
  • getPopularArticle()の改善
  • (何か他に忘れている作業)

のスコアで1885でした.

f:id:AkkyOrz:20200608154500p:plain
最終スコア

ただ,奇跡的に過去のスコアが残っていたため,スコアは2121でした.

f:id:AkkyOrz:20200608161438p:plain
最高スコア
(スコアサーバはインターン終了後に少しの間動かしておくということだったので,最終スコアは別の値となっています.)

その後

結果発表後,解説を聞いたり,社員さんのパネルディスカッションを聞いたり,社員さんと直接お話をしたりしました. それぞれで一番面白かったのは以下のとおりです.

  • スコアを上げるにはいくつか壁があり,高い点数を目指すならHandler側からHTMLファイルを生成して埋め込むなどの手法があるという話
  • ISUCON優勝者の方がプライベートクラウドの開発をしてるよ〜というお話
  • サービスインの前は非常に気を使って負荷テストを行っているという話(ゲーム開発の方)

最後に

2000点台に乗せることができてとても嬉しかったです. 全体では5位ということでしたが,同チームの人のアドバイスやメンターの方のアドバイスのおかげだと思います. 基本的に初期ではDBに律速が生じるため,まずはINDEXを確認したり,クエリを確認したりしないと,大きな律速を解除することができずスコアが伸びないことが分かりました. 計測→改善というパフォーマンスチューニングの姿勢に少しは身につけられたではないかなと思います.非常に有意義なインターンでした.

(P.S. MacWindowsが推奨されていましたが,Linuxマシンでも全然大丈夫そうでした.)

Chromeで特定サイトへのアクセスのみをproxy server経由で行う方法

はじめに

これは筆者が学内のIPアドレスのままリモートで論文にアクセスする方法を模索した結果をメモしたものです.
大学公認のSSL-VPN Gatewayサービスを使えと言う意見はごもっともなのですが,なんとなく不便を感じたため,他の方法を模索しました.
色々と模索した結果,ユーザ側で自由度の高そうなアクセス処理ができそうな方法を見つけたので共有しようと思った次第です.
筆者はpaperpileを論文管理に用いているため,Chromeをブラウザとして利用していますが,Firefoxなどでも可能だと思います.

設定の方法のみを述べるため,SOCKS proxyやDynamic Forwardingの説明,sshの細かい設定の仕方などには触れません. 既にいくつかのサイトにて説明がされているため,他のサイトをご参照ください.

何か他に良い方法をご存知の方は,コメントしていただけると幸いです.

対象者 and 目的

  • VPNサーバを(何らかの理由で)立てることはできないが,sshでアクセスは可能という場合
  • HTTP(or HTTPS)で特定のサイトに踏み台サーバのIPアドレスでアクセスしたい場合
  • 全てのトラフィックVPNに流してトラフィックを増大・集中させたくない場合
  • 特定サイト以外はダイレクトにアクセスしたい場合
  • SwitchyOmegaの使い方を知りたい場合

使用するツール

構成図

Create a SOCKS proxy on a Linux server with SSH to bypass content filters を参考にしました.

f:id:AkkyOrz:20200509233446j:plain
ネットワーク的な構成図

設定方法

大きく分けると,以下の2つを行う必要があります.

  1. sshでトンネルを張る
  2. SwitchyOmegaを設定する

1. sshでトンネルを張る

~/.ssh/configを設定します.

第617回 SOCKSを利用してSSHのみで簡易VPNを構築する:Ubuntu Weekly Recipe|gihyo.jp … 技術評論社 を参考に設定します.

Host socks
    HostName <proxy server name>
    User <username>
    IdentityFile /path/to/secret_key
    DynamicForward 1080
    Compression yes
    ExitOnForwardFailure yes
    Port <ssh port (default 22)>

その後ターミナルを起動し,

$ ssh socks -f

でトンネルを張ります.-fオプションはバックグラウンド実行のオプションです.

2. SwitchyOmega を設定する

Chrome Extensionを追加

Proxy SwitchyOmega - Chrome Web Store から,SwitcyOmegaをChromeに追加します.

次に,SwitchyOmega の設定を行います.

qiita.com を参考にしました.

Chrome Extension を追加した後,アイコンをクリックし,Options を選択します.

これはSwitchyOmegaの画像です.
SwitchyOmegaのアイコン

Proxy Profile の作成

まずは Proxy の設定をします. SwitchyOmega のオプション画面が開いたら,左の欄の PROFILES から New Profile を選択します.

f:id:AkkyOrz:20200513222741p:plain
SwitchyOmegaのNewProfile選択画面

Profile name を入力し, Proxy Profile を選択して Create ボタンを押します.

f:id:AkkyOrz:20200513223416p:plain
New Profile作成画面

Proxy の設定画面が開いたら,以下の図のように Proxy を設定します.
この設定画面では,利用するProxyのプロトコル,サーバのIPアドレス,Port番号を指定します.
今回はローカルの1080番ポートにポートフォワーディング用のポートを開いているため,localhost:1080にSOCKS5で接続します.
設定したら,左の欄の ACTIONS から Apply changes を押して変更を反映させます.

f:id:AkkyOrz:20200513223547p:plain
Proxy設定画面

Switch Profile の作成

次に,Switch の設定をします.
先ほどと同様に,左の欄の PROFILES から New Profile を選択し今度は Switch Profile を作成します.
Profile name を入力し,Switch Profile を選択して Create ボタンを押します.

Condition Type, Condition Details, Profile を組みあわせて設定を行います.
上から下に向けてマッチングを行っていくため,上の方設定のほうが優先されます.
詳しい設定の方法については,下図のマウスカーソルの部分の?をクリックしてでてくるHelpを参照してください.
私はProxy経由でアクセスしたいサイトに 先程作成したProfileを設定し,それに当てはまらないサイトは直接接続するように設定しました.

f:id:AkkyOrz:20200513234849p:plain
Switch設定画面

Apply changes を選択して設定を反映させます.

Switchの有効化

最後にSwitchの設定を有効にします.
SwitchyOmegaのアイコンをクリックし,使用したいSwitchのProfileをクリックして有効化します.
これで設定は完了です.

まとめ

VPNではなく,SOCKS proxy を用いて特定のサイトにアクセスする方法についてまとめました.
参考になれば幸いです.

備考

Firefoxでの利用方法

github.com でも言及されていますが,SwitchyOmegaには,Experimentalなアドオンが存在するようです.
これを利用するとChromeと同様なことができると思います.

結局コマンド実行してるし,結局ログインする手間と変わらなくない?WOWWOW?

ごもっともです.

それって大学のネットワークでやっていいの?

東京大学附属図書館 ASKサービス - 詳細画面 を見て,研究室内のIPアドレスを用いて論文をダウンロードすることはOKだと判断しました. ただ,大量にダウンロードを行うとシステマティックダウンロードと疑われてしまうため,注意が必要とのことでした.

Warningについて

導入してすぐに,いくつかのWarningっぽいのがでました.

Resources that failed to load A few resources failed to load due to issues with your network, proxy server or the webpage. SwitchyOmega is just the reporter of these issues, not the cause of them.

1 .fontawesome.com 1 .bootstrapcdn.com

You can add switch conditions for them only when using a Switch Profile.

firefox/SwitchyOmega.sorl at master · nonoroazoro/firefox みたいなのを導入するといいのかもしれません.

Ubuntu Weekly Recipe

今回のSOCKS Proxyの詳しい内容は,技術評論社が連載を行っている,Ubuntu Weekly Recipe を参照しています.

gihyo.jp

今回のSOCKS Proxyの話以外にも,参考になる記事が沢山有るので,Ubuntuを使用している方は特に見てみると良いかもしれません.おすすめです.