Helpfeel専用Cosense Export APIはMongoDB query cursorを閉じ忘れている
from scrapbox.ioのみ時間経過でメモリ使用量が増加する原因を、Codex CLIとともに探る
Helpfeel専用Cosense Export APIはMongoDB query cursorを閉じ忘れているCodex CLI.icon
Helpfeel エクスポート API のコードを読み込んで、メモリが積み上がりやすい(もしくは実際にリーク扱いされる)箇所を洗い出しました。以下の点を優先的に確認・改善するのがよさそうです。
カーソルを閉じていない
users, events, pages それぞれで Model.find(...).cursor() を開いていますが、streamClosed でループを抜ける際に await cursor.close() していません (src/server/controllers/helpfeel/download-jsonlines.js:74-133)
クライアントが途中で切断すると MongoDB 側にカーソルが残り、サーバーも最後に受け取った 4MB/101 件のバッチを抱えたまま GC 待ちになります。
Helpfeel 側から頻繁に再接続されるケースだと、カーソルとバッチが溜まり続けてメモリリーク同様の症状になります。try/finally で cursor.close() を保証してください。
mongoose 6以降ではcursor()呼び出さずにqueryを直接for await...ofで回せるを使おうshokai.icon
やめた。for awaitでは明示的にcloseできないshokai.icon
不要フィールドを全件読み込んでいる
User.find({ _id: { $in: project.users } }) でも _id,name,email 以外のフィールド(例えばアイコンや設定値)が全部載った Document を受け取っています。projectToJsonLine も含め API では使わないデータがかなり多いため、.select({ name:1, email:1 }) と .lean() を付けるだけで 1 人あたり数 KB 〜 数十 KB 削減できます (download-jsonlines.js:70-90)。
Event でも _id, type, pageId, data.titleLc, created, updated 以外は不要なのに全文取得 (download-jsonlines.js:93-116)。
とくに Page.find(conditions) では lines, links, files, helpfeels 以外の巨大フィールド(infobox, icons, projectLinks など)も全部入ったドキュメントを 1 バッチ 101 件ずつメモリに載せています (download-jsonlines.js:118-146)。各ページが数百 KB〜数 MB になると、バッチ 1 回で数百 MB を消費し、GC が追いつかず「リーク」しているように見えます。.select({ ...必要な
フィールド... }) と .lean() を加えてください。
バッチサイズが大きすぎる
MongoDB のデフォルトバッチは 101 件(または 16MB)。Helpfeel エクスポートは 1 件が大きいので、cursor().batchSize(5) などに下げると瞬間的に保持するメモリを抑えられます。特に Pageで顕著でした。
ユーザー取得の繰り返し
pageToJsonLine 内で await page.getUser() を呼び、ページごとに User.findById と User.findById(lastUpdateUserId) を発行しています (src/server/controllers/helpfeel/download-jsonlines.js:33-69 → src/server/models/page/methods.js:144-189)。ここでもフル Document を 2 件ずつ保持するため、ページ数に比例してメモリが膨らみます。lastUpdateUserId が同じユー
ザーで何度も再取得されるので、Map でキャッシュしつつ .select({ name:1, email:1 }) と .lean() で軽量化すると効果が大きいです。
まずは①カーソルの明示的 close、②.lean().select() の適用、③batchSize の縮小を行うと、Helpfeel エクスポート時の常駐メモリが大幅に下がり、「リーク」の再現度も落ちるはずです。必要であればヒープスナップショット(node --inspect --expose-gc run-server.js → Chrome DevTools)で改善前後を比較すると根拠を示せます。
明らかにメモリリークしてる箇所は修正したshokai.icon
done.icon Helpfeel専用Cosense Export APIはMongoDB query cursorを閉じ忘れている #7938
ここでの最終的な知見はmongooseのfind query cursorは色々な回し方があるけどどれ使えばいいの?にまとめ、Claude Codeに食わせて他の全箇所に一括適用したshokai.icon