とあるサービスのサーバー代を年間半千万円(500万円)削減する方法

# これは

- [ドワンゴのAdvent Calendar 2023](https://qiita.com/advent-calendar/2023/dwango) の25日目の記事です


### こういう記事もあります (兄弟記事)

- 6日目
    - [ChatGPT の Code Interpreter でアニメ原画の領域(特徴点)認識プログラムを書かせたよ](https://qiita.com/ystmg/items/48e5a86fd3a44bfd35c6)
- 1日目
    - [ゆるくやるRAMMapなどを使ってプロセスの使用量に表れないメモリ使用量を特定 (Windows、DriverLocked) (Bing Chat頼り)](https://t-hazawa.hatenablog.com/entry/2023/12/01/%E3%82%86%E3%82%8B%E3%81%8F%E3%82%84%E3%82%8BRAMMap%E3%81%AA%E3%81%A9%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%83%97%E3%83%AD%E3%82%BB%E3%82%B9%E3%81%AE%E4%BD%BF%E7%94%A8%E9%87%8F%E3%81%AB%E8%A1%A8)

### 私は

- <ins>ゆいもっぷ</ins> アニメクリエイター で BingChat中毒者
    - (最盛期は毎日2時間40分Bing Chat)


# 今回の話(大量アクセス)の経緯

- (自分が管理してる、とあるサービスの) <ins>アクセスログ</ins>を見ていると、 **<ins>半分以上</ins>** が<ins>外国</ins>の<ins>某大企業</ins> **<ins>1～2社</ins>** からのリクエストだった
    - (1社で1日100万リクエストはしてくる規模)
    - Google Bot (や Bing bot) と違い、クローラへのこちら側の応答費用を <ins>全くペイしないほどのかなり少ないユーザー流入</ins> しかもたらしてなかった
        - 自分サイドのサービス (クロールされる側) が日本語の文字物なのに、クロールする側のサービスはその国 (外国、人口多い) のユーザ相手だけのサービスだった。
            - (クロール元は、日本語話者でない人向けのサービスなので、日本語サイトである我々のサイトをめちゃクロールしてるのに、クロール元からの流入が極端に少ない)
            - その国自身のWebサイトへのクロールでは、被クロール側も十分なユーザ流入を得てるのだと思う
        - その企業は<ins>アメリカの企業ではないが、IPアドレス的にはシアトルから</ins>クロールしてきていた
- こんな小ネタが大トリでいいのかちょっと悩んだものの、<ins>ビジネスインパクト</ins>はかなりあるので、アドカレの枠をとった
    - 新たに売上を上げるのはかなり大変。それに比べると、既に発生している費用を削減するのはだいぶ簡単

# TL;DR

- そういう<ins>自サイトのためになってない大量クロールをブロックすると サーバー費用を大きく削減できる</ins>
    - 皆さんもチェックしてみてはいかがかな？


---------
(↓以下、こういう状況の原理を検討↓)

# どうしてこんなに<ins>応答</ins>費用がかかるのか

- 自分の個人サービスを中心に費用を検討してみると、サーバを立てて待っていると、1 requestに対して 0.0025～0.004円 (<ins>1000 request に対して 2.5～4円</ins>) (DBとかLBとか全部込みで) サーバー費用がかかるようでした。
    - 人間のユーザをもたらしてくれないbotのリクエストが半分を占めたりすると、当然この費用が倍かかってしまう訳です。
    - (とはいえ、1000リクエストで 2.5～4円 は、インターネットで調べられるアドネットワーク広告のRPMを余裕で下回ってるので、ADNW広告でも十分ペイする水準)



# どうしてこんなにクロールしてくるのか / どうしてこんなにクロールできるのか

- クロールする側もペイしてる訳でしょう
- クロールする側は、コンピューティングコストとしては、「HTTPリクエストを送って → 相手の処理を待って → 受け取るだけ…」 なので、 LBもDBもヘビーな計算も要らないので、クロールを受ける側ほどコストがかからなさそう…
    - <ins>計算</ins>してみると、一番小さいEC2インスタンスで 10req/sec でリクエストを送ると、 <ins>0.001円/req</ins> くらいしかコストがかからないっぽい。(クロールを受ける方の応答の方が2.5～4倍ほど費用がかかる)
        - (<ins>想像よりコスト差は小さかった</ins>)
- <ins>結局は (クロールする側が) どうやって収入を得ているか次第</ins> ですね 
    - 収益性が良いと、クロール量をセーブせずに大量に(収益性が悪い時の10倍くらいは)クロールできるわけですね
    - (つまり、(クロール元の収入源が) (その国自身の)企業案件やユーザ課金という訳です)
        - (クロール元が 企業案件/ユーザ課金で、クロールを受ける側が ADNWだとこういう状況になるんじゃないかな)
        

 ----
 ーーーー
 (↓以下、どうやってそういう (クロールを受ける側が、自サイトのためになってない大量の) リクエストを見つけて、どう対処するかのコーナー↓)

# 細かい調査方法

## アクセスログをワンライナーだよ

### 本当にアクセスログをワンライナーでいいのか

- なんか、(↑は) ものすごくロートルな方法のようにしか思えないのですが、特にAWSなら、Athena？とかそういう「知能なんとか」系のAWSの機能がこういう状況になっていたら教えてくれるものなのではないでしょうか？
    - (慣れていれば一瞬(15分)で調べられることではあるけど、本来は、そういう状況が発生したら、システムの方からPushして教えてくれてきた方がいいですね)
- Bingに聞いてみた (Bing依存症)
    - (「創造的に」モードだけがGPT-4なので、常に「創造的に」にするのが良いです)

#### 調べた結果

- … (Bingで調べたところ) 結局は、「自サービスのためになってない大量アクセスはいないかな？」という部分は人間 (運用者) がワンライナーなりSQL的な構文なりで調べるしかないようだった
    - あるいは、AWS WAFの BotControl の `TGT_VolumetricSession Rule` (targetedなので、100万リクエストで1430円かかる) だと、異常に多いと自動でchallenge (CAPTCHA要求) や block にできる (ただ、大事な大事なGoogleBotを弾かないか怖い)
        - …というより、1日数百万リクエストある中で、100万超リクエストが余計にくるとサーバ費用が1日あたり4000円(1年で146万円) 増えて困るという状況だと、100万リクエストごとに1430円かかるソリューションでは解決に至らないのだった (多分他にtargeted Rule が役立つシチュエーションはあるのだろうと思う)

### 本当にアクセスログをワンライナーする

- 慣れていたら一瞬 (結果を知るまで15分) 
- (今回、大量リクエストを送ってきていた主体は２つあるのですが、以下の調査部分は、そのうち、「UAで何者かを名乗って無い方 (2013年のChromeのUAにしている方) 」です)

#### 今回のアクセスログの形式

- アクセスログの形式は今回は↓みたいな感じだった
```
{"method":"GET","path":"/ao/love/yuimop/kendo-bu","format":"html","status":200,
"ip":"ao.love.yui.mop","referer":"https://ao-love-yuimop.hatenablog.com/",
"user_agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/30.0.1599.101 Safari/537.36",
"level":"info","time":"2023-12-11T08:07:51+09:00","params":{"ao_love":"yuimop"}}
```

- (IPのマスクは `19.90.3.5` とかがいいかもしれませんね) (数字なので)
    - ↑のIPアドレス (19.90.3.5) は、Google検索すると、アメリカ ミシガン ディアボーンのフォード自動車の持つIPアドレスのようですね

#### 分析

##### User Agent 編

- ↓ みたいにすると、User Agent を多い順に出せます
- `tail -10000 /var/log/ao_love_yuimop/access.log | egrep -o '"user_agent":"[^"]+"' | sort | uniq -c | sort -n`
     - 今回のアクセスログみたいに見出しがついてない時は、 `awk -F ',' '{print $15;}'` みたいなことをしてます


```
$ tail -10000 /var/log/ao_love_yuimop/access.log | egrep -o '"user_agent":"[^"]+"' |\
sort | uniq -c | sort -n
..(略)..
    337 "user_agent":"Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm) Chrome/116.0.1938.76 Safari/537.36"
    684 "user_agent":"Sleipnir/281 CFNetwork/1485 Darwin/23.1.0"
   4886 "user_agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36"
```

- ↑のように、<ins>半数を占める</ins> User Agent が見つかったりします (しかも↓のように<ins>かなり古く</ins>て怪しい)
- Windows NT 6.1 は Windows 7
    - WOW64 は 64ビットなWindows で 32ビット用プログラムを動かす仕組みとのこと
        - cf. [WOW64とは｜「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典 (i-3-i.info)](https://wa3.i-3-i.info/word12434.html)
- Chrome/30 は、 PC版もスマホ版も 2013年リリース

##### IPアドレス編

- (今回のアクセスログの形式の場合) ↑の egrep の中を ip にすると ipが抜き出せますよ
- `tail -10000 /var/log/ao_love_yuimop/access.log | egrep -o '"ip":"[^"]+"' | sort | uniq -c | sort -n`

(↓IPアドレスをそのまま出すとよくないのでマスクしています)
```
$ tail -10000 /var/log/ao_love_yuimop/access.log | egrep -o '"ip":"[^"]+"' | sort |\
uniq -c | sort -n
..(略)..
    161 "ip":"XX.XXX.XX.XXX"
    172 "ip":"ao.love.yui.mpq"
    179 "ip":"XX.XXX.XX.XXX"
    185 "ip":"ao.love.yui.mpp"
    201 "ip":"XX.XX.XX.XXX"
    301 "ip":"ao.love.yoc.doc"
    309 "ip":"ao.love.yoc.dot"
    363 "ip":"19.90.3.15"
    415 "ip":"19.90.3.5"
    710 "ip":"ao.love.yui.moz"
    738 "ip":"ao.love.yui.mot"
    739 "ip":"ao.love.yui.moc"
    759 "ip":"ao.love.yui.mor"
    770 "ip":"ao.love.yui.moq"
    783 "ip":"ao.love.yui.mop"
```
- ↑のように、第３オクテットまで同じ、第4オクテットもかなり近いものばかりで総リクエストの半分を占めていたりするわけです。
- ↓のようにすると、特定のIPセグメント内だけを絞れていいですね。
    - `tail -10000 /var/log/ao_love_yuimop/access.log | egrep -o '"ip":"[^"]+"' | sort | uniq -c | sort -n| grep "ao.love."`

```
$ tail -10000 /var/log/ao_love_yuimop/access.log | egrep -o '"ip":"[^"]+"' | sort |\
uniq -c | sort -n | grep "ao.love."
     11     "ao.love.ao.X9"
     11     "ao.love.ao.X7"
     15     "ao.love.XX.X7"
     15     "ao.love.XX.X4"
     15     "ao.love.yui.X4"
     19     "ao.love.XX.X9"
     19     "ao.love.XX.X4"
     20     "ao.love.X.X0"
     20     "ao.love.XX.XX1"
     26     "ao.love.X.XX8"
    133     "ao.love.yui.X6"
    182     "ao.love.yui.X2"
    340     "ao.love.yoc.X1"
    369     "ao.love.yoc.X5"
    631     "ao.love.yui.X4"
    651     "ao.love.yui.X9"
    659     "ao.love.yui.X2"
    664     "ao.love.yui.XX0"
    668     "ao.love.yui.X8"
    674     "ao.love.yui.XX8"
(合計5142)
```

- 何にせよ、総リクエストの半数が10個のIPアドレスからのリクエストで、しかも古い10年前のUAな訳です。 (なにかのCFとかでもない)
    - (某社のクローラーと違ってUAで名乗ってもいない)

##### 次点

- なお、今回特に多かった2つの接続元の他には、有名SEOツールのクローラー(Webサイト運営してアクセスログを見たことがあれば皆知ってそうなサービス) が (上述の大量リクエストを除いた後の) 1割を占めるのが次点として一番多かったです (Googlebotより多い)
    - (そのSEOツール、使ってみたい)

##### UA偽装？

- なお、今回大量アクセスしていた某社のクローラー(User Agentでちゃんと名乗ってくれている) については、クローラーの名前でGoogle検索してみると、「blockしたらUAをGoogleBotに偽装してアクセスしてきた」という情報もあったので、block後の様子もチェックするといいのかもしれません(…？)


# 細かい対策方法

- 個人サービスの方では、(本当にアクセス頻度を見て多かったら) 429返していて、私は親切 (だと勝手に思っています) 
- 色々あると思いますが、↓のこととかで対策しました

## AWS WAF編

- AWS WAF には Web ACLs という機能があり、そこでIPアドレス範囲やUser Agent 指定で Block できます
    - この  Rule 追加の費用はかなり小さいっぽい
        - cf. [WAFの料金](https://aws.amazon.com/jp/waf/pricing/)
- Web ACLs の Management Consoleの画面は (上のヘッダーではなく) 画面内のリージョン選択しないと設定済みのACLが出てこないのが難しいポイントです。

 ### AWS WAF Bot Control 編

 - Targeted の `TGT_VolumetricSession` Rule を使えば、今回のような「特定の1～2社だけが多い」から守れそうですね
     - cf. https://dev.classmethod.jp/articles/aws-waf-targeted-bot/
     - (Targeted の料金は 100万request につき 10ドル(1430円))
     - Googlebot を弾かないか不安 
         - 少し調べてみたけど、やはり、「このBotはなにがなんでもallow」みたいな設定も無いようだし、大事な大事な Googlebot を block してしまうことがありそう。。
 

## iptables編

- 自分の個人サービスだと、超安VPS (とても安いのでおすすめ) なので Linuxしかない
- DoS攻撃 を block するのに便利
    - 何故か北の大国の方からの DoS攻撃 が前は結構きてた
        - (多かったら自動で429にする仕組みを自分で書いて入れてから気にならなくなっただけかも)
- 伝家の宝刀
    - `iptables -A OUTPUT -d ao.love.0.0/16 -j DROP`
        - ( `ao` とか `love` とか `yui` とか `mop` のところには実際には数字が入ります) (←実際のIPアドレスを書くとよくないのでマスク)
            - 大量アクセスしてきてるIPアドレスをグーグル検索して、外国からのリクエストだと確認できておくと (& なにかのプロキシ的サーバでないことも確認できておくと)、安心です
        - 多分、意味合い的には `iptables -A INPUT -s ao.love.0.0/16 -j DROP`の方が正しいと思う (↑↑だと、こっちのサーバがハンドシェイクしようとして無駄な処理が発生してると思う)

## その他編 

- AWSなどのプラットフォームでない時は、LBについてるACL (Access Control List) や Webサーバミドルウェアソフト(nginxやApacheなど) の 設定ファイルに deny するIPアドレス範囲を書いたりするのだと思います


----
ーーーー
(↓余談的な話題↓)

# こういう費用削減もしました

## 画像の変換するサイズを数種類に絞るとかなり費用削減できる場合も

- CDNについてるような 画像サイズ変換処理は結構お高めな場合も (世界で一般的に提供されてるCDNのこと)
    - 変換後に変換結果をキャッシュしてくれる場合はサイズの種類を絞るとお得

### しかし Lambdaは安い

- しかし、AWS Lambda で変換をかませる時は激安なので、変換先サイズの数を絞っても意味なさそう
    - [AWS Lambda 料金](https://aws.amazon.com/jp/lambda/pricing/)
    - (10000回変換して 3円～12円くらいっぽい)

## ElasticSearch 的な検索エンジンの検索対象を絞ろう

- [AWS OpenSearch 料金](https://aws.amazon.com/jp/opensearch-service/pricing/) の下の方にある例だと 1500GBのデータで 月間 3600ドルかかっていますね。
    - サービスレベルと相談して削減できる箇所があるかも。


# 倹約個人サービスでの経験編

- 自分は個人サービス (3cpu, 2GB RAM) では、 GoogleでもBingでもない超大手企業のBot にもアクセスが多い時は 429 を返してる
    - 3cpu, 2GB で、計算がたくさん要るWebサービスだと、bot 全部裁くのは無理 
        - (↑↑のBotはユーザ流入に寄与してる感じがしないし……)
    - (というより、Googlebot もクロール頻度を 1秒1回などに抑制しないと無理 ←そのクロール頻度抑制ツールなくなるけど本当に大丈夫かちょっと心配)

# ポイント

- Googleは使ってるIPアドレスを(GCPのユーザが使えるIPアドレスとそれ以外に分けて)公開しているので、チェックすると誤ブロックを防げます (DNS逆引きもあります)
    - http://developers.google.com/search/docs/crawling-indexing/verifying-googlebot?hl=en


# まとめ

- たくさんリクエストしてきてる主体が何かはっきり分かった感じでよかった
- お金も節約できそうで良かった 💰🎄🎁