急にリクエストが1.5倍に増えてクソデカJSONと戦った
こんにちは
scrapboxのプロダクトオーナーです
機能の取捨選択や設計・運用に責任を持つ役割
今日の話
1. Scrapboxとは
2. 最近作った機能をざっと紹介
https://gyazo.com/20a9067673477fc3130a336462bf52ae
サーバー負荷が右肩上がり
動画
https://youtu.be/1-5-Ry9njjI?t=2277
(ここでデモ。ページを作ってみる)
技術スタック
他のNotaプロダクトとの関係
Gyazoで画像や動画をキャプチャし、Scrapboxに埋め込める リモート開発
フルリモートワーク、年2,3回泥酔する為に出社していた 京都rakusai.icondaiiz.icontakeru.iconTiro.iconben.icon 原作者、無茶な使い方開拓者
ScrapboxをScrapboxで開発している
用語・概念に、別ページで定義を好きなだけ書ける
格言・スローガンの様なページを作る
影響しあう機能の設計時に、リンクで説明できる
理解・考察が足りていない事をScrapboxで書く
書くとだんだんわかってくる
クソデカ感情を持っている人が直してくれるyosider.icon
通話なし、ドキュメントで相談する
Scrapboxをゴリゴリ書いていくスタイル
プルリクをぶつけあう
開発者同士で言葉を尽くして説明しあう
動くプロトタイプを触る
こうだね(確認)、を繰り返す
書くとわかってくる
例:増井俊之.iconが最初こんな感じで雑に書く
内部仕様に詳しくなくても、とにかく書く
不満に思った時点で書けばいい。わかる相手には伝わる
https://gyazo.com/8298a3470b2f4d941142a8125ea98507
詳しい人が手直しすれば良い
https://gyazo.com/59d3d480c8d687ca94c5e8246af65154
直してる側も、説明(対話)を通してようやく言語化できる事がよくある
意味が変わらなければ、他人の発言を添削・修正してもぜんぜんok
文中の専門用語を、別ページへのリンクにする
タイトルも変える
発言者を明確にしたい場合はアイコン記法を使うshokai.iconshokai.icon*5yuiseki.icon*5 https://gyazo.com/ccc910c7407405dc3839793aca4fe8c2 https://twitter.com/mineiyuki/status/1065911937685286912
人に聞くのはコスト高いけど自分で見られるから良いyosider.icontakker.icon
何度も説明する手間が省ける
https://twitter.com/Taku17_h/status/1367744547942330371 https://gyazo.com/34c3ab5b43fb76c636b6838c5762a4e6
最近の機能をざっと紹介
カラーテーマ追加 by Takeru.iconTiro.icon
https://gyazo.com/0301264793025906a49fe0921b6f32cf
エディタ背景が黒いテーマもあります
DarkとMinimal、愛してますshio.icon
テーブル記法が見ずらいのが欠点かもですtakker.icon Dark使わせてもらってますyamanoku.iconmonotony.iconyosider.icontakker.icon
ファイルアップロード
1ファイル100MB
1アカウント合計1GBまでアップロードできる
https://scrapbox.io/files/5f2e56050f9d7c002465d037.mp4
nintendo switchの30秒録画をアップロードするのに便利shokai.icon
削除したページの復活
便利kuuote.icon*10takker.icon*6wakuwakup.icon*4monotony.iconshio.icon*5
これでproject削除以外の操作を戻せるようになった。最高takker.icon*3
https://gyazo.com/20fac1dae8f647e42fbaf4d252252b36.mp4
これはなぜでしょうか?
実装できてないからですshokai.icon
business plan(有料)専用の機能
https://gyazo.com/6eee266ef1ca656f29720a2ad26dc811
監査ログ
business plan(有料)専用の機能
直近3ヶ月分のイベントが見れる
誰がどのページにアクセスした、ファイルアップロードした
招待URLが使われた
project memberの追加・削除
admin任命・解任など
IPアドレスも見れる
https://gyazo.com/9182356bd7fc5c4eeec9ecffa4b119cd
検索結果を30→100件に増やした
ありがたいです!!shio.icontakker.iconmonotony.icon
リンク貼っていないやつを探すのに便利ですtakker.icon
https://gyazo.com/abf75d03590f28018fd00ef04c697140.mp4
スクロールすれば勝手に横断検索してくれるのとっても良いUIだtakker.icon
被リンク数が多いページからの被リンク数が多いページほど、上にソートされる
/icons/知らんかった.iconyosider.icon
クソデカJSONとの戦い
急に1.5倍ぐらいリクエストが跳ね上がった
2020年4月頭
新年度、新学期
急激にリクエストが増えると
これまで問題として現れていなかった不具合が表面に出てくる
人が多い時間帯で、急にDBのCPUが90%に張り付いたりする
サービスが30分ほど不調になった事が3日間、合計5回ほどあった
胃が痛くなった
かわいそう
重いところを見つける方法
特定のURLパスが遅いのがわかる
appの計算待ちか?DB待ちか?もわかる
https://gyazo.com/3921865b9a3105ed12a2c81f9167b4bd
5秒以上レスポンスがかかったリクエストを全てslackに通知
特定のscrapbox projectが遅いのがわかる
めちゃうるさくなる。静かになるまでがんばる
数ヶ月〜年単位のグラフでも見る
1週間単位で見てると今日も横ばいだ、異常ナシ!と油断してたらこれ
年単位で見ると超右肩上がりだったりする
https://gyazo.com/e52d9e4ede3ef26e9131d5e93ec6855e
サーバーのmetricsは貯めておく
重くなった時、何をどこまで戻せば元気になるのかわかる
危機が迫っているのもわかる
2020春 急に重くなった主な原因
index効いてないDBアクセス1つ
巨大なJSONの生成コスト4つ
関連ページリストAPI
titles API
WebWorkerとUIスレッド間のpostMessage
Auto project backup
index効いてないDBアクセス
問題
MongoDBでpage.icons = { shokai: 3, nota: 1 }みたいなkey valueにすると .iconsに"shokai"というkeyがあるpageを探す時、indexが効かない
こういうデータ構造はやめようshokai.icon
解決
素直にpage.icons = ['shokai', 'nota']というArrayにした
サービスを止めずにschemaを変更
まずappを、readはkey valueとArrayどちらでも可能、writeはArrayでやるようにする
これでappを動かしているだけで徐々にデータがmigrateされていく
同時にkey valueをArrayに変えるbatchも走らせる
最後にappをArrayしかread/writeしないように修正
背景
結局、個数は特に使いみちが無く、非効率なデータ構造のまま運用していた
#member 人物、Gyazo、Scrapboxなどの大きなhashtagを表示しないようにした 問題
解決
超巨大な2 hop linkは省略する
https://gyazo.com/55ad4ff25aad5e9838da7fa6e1eb6148
Gyazoの先の2 hop linkが多すぎるので省略された
1 hop linkのGyazoから辿れるし必要十分だろう
/icons/知らんかった.iconyosider.iconkuuote.iconyamanoku.icondnin.icon
titles API
project内のページタイトルとリンク(空も含む)が全部返ってくるパワフルなAPI
ユーザーのキー入力する前にデータを持っておきたい
https://gyazo.com/d9e2d477b2530c43cd466e9cc6ca8da1
https://gyazo.com/d487581f31f748e594a067d758880ef6
UserScriptでよく使ってるAPIだtakker.icon
このAPIから自前で逆リンクや2 hop linkを計算できたりする
titles APIのJSONが巨大だった
/api/paegs:projectName/search/titlesが明らかに遅かった
https://gyazo.com/3921865b9a3105ed12a2c81f9167b4bd
原因
何万ページのprojectがけっこうある
数十MBのJSONがリアルタイムに作られていた
大量のページをscanしてDB待ちが長い
解決
pagingした
1000件ずつ取得、next IDで辿っていく
https://gyazo.com/05df1d188fff980829d7108efc4cb86f
API fetch完了まで時間がかかるようになった
fetch中はcacheから検索する
このAPIだけ時間かかるの理由がようやくわかったtakker.icon
window.postMessageに巨大objectを送るとブラウザが死ぬ
背景
送受信中、UIスレッドがフリーズする
V8がたまにout of memoryでクラッシュ
DataCloneError: Failed to execute 'postMessage' on 'Worker': Data cannot be cloned, out of memory.
要素数20万ぐらいの配列で発生するようだ
解決
1000件毎のchunkに分割して送受信させた
{data: { pages: Array }, start: true}で開始
{data: { pages: Array } }をどんどん貯めて
{data: { pages: Array }, end: true}で完了
dataの中身の型がArrayなら末尾push、objectならObject.assignでmergeする
https://gyazo.com/91681150b7dec621ff55a7c25aef9575
これを双方向にやる
実行速度は100〜300msecしか遅くならなかった
1ページずつJSON化してwritable streamに出力した
背景
projectまるごとバックアップする機能
Webサーバーではなくbatch処理で実行されている
巨大なscrapbox projectがわりとある
本文だけで100MB超え
数万ページ
フォーマットが悪い
code:json
{
"name": "nota"
"pages": [
{
"title": "page1title",
},
{
"title": "page2title",
}
]
}
これを普通に作ると
DBからページを読み出し、全部メモリに載せてから
const str = JSON.stringify(object)
1つの巨大な文字列ができる
解決
JSONをパーツ毎に動的に作り、1つ1つstreamに流した
フォーマット変えずに済んだ
まず頭を作ってwrite
code:json
{
"name": "nota"
"pages": [
ページを1つずつwriteしていく
MongoDBのquery cursorというイテレータで1件ずつ取得、JSON化してwrite
code:json
{
"title": "page1title",
},
最後に閉じる
code:json
]
}
JSONという効率の悪いフォーマットを選んだ事を少し後悔もした
末尾まで受信しないとparseできないフォーマットよ
今後export機能を作る人へ
1行1レコードなフォーマット使った方がいいぞ
ScrapboxのHTML、JS、CSS、メニューのアイコン画像、フォント、WebWorkerの事
Scrapboxはページを開いた時にHTMLやJSをダウンロードしていない
初回アクセス時を除く
HTMLやJSはそのCache Storageから読み出す
assets cacheはバックグラウンドで更新される
つまりネイティブアプリみたいになっている
HTTP API
先にCache Storageから取得してReactに渡して画面描画しつつ、裏でHTTPリクエストして差し替え
一度見たページはオフラインでも読める
https://gyazo.com/7622d63ce35541453a74a7a53c2196d3.mp4
デプロイと同時にassets cacheの再取得が殺到していた
assets cache更新フロー
デプロイ
古いサーバーが「更新します」というメッセージをSocket.IOで送信、切断
クライアントは、Socket.IO再接続した時にassets cacheを再取得する
問題
みんな同時に再接続し、一斉に再取得しにくる
解決
ウィンドウがactiveではない場合はランダムに待つようにした
他にもいくつかのAPIが、socket.ioの切断・再接続をトリガーとして同時にリロードしていた
同様にウィンドウがactiveでない場合ランダムディレイを入れた
まとめ
サーバーのmetricsとって、たまに年単位のrangeで眺めよう
右肩上がりに気づける
クソデカJSONは分割しよう
paging、chunking
ちょっとずつ送る
もしくはデカいままstreamで扱う
JSONの配列になってる部分をちょっとずつ作って送信
デプロイ直後にアプリケーションがどうなるかも気をつける
おわり
チャンネル登録と高評価お願いします
登録しました mactkg.icondnin.iconyutaro.iconwakuwakup.iconkuuote.iconrmaruon.icontetsuya-k.iconyanma.iconsta.iconshio.iconyamanoku.iconk.iconyosider.icon
リアルタイムでいいね増えてていいぞ
Scrapbox上で見てるけどいいのかなyosider.icon