月間1.6億秒の Lambda x Node.js 利用から得られた知見

Tags
月間1.6億秒の Lambda x Node.js 利用から得られた知見
Page content

はじめに

Stockmark のプロダクトでは、各メディアから記事を収集するために AWS Lambda (実行環境はNode.js) を大量に利用しています。「大量」とは実際にはどの程度なのかを紹介すると、月間で 1.6億 秒ほど(1日で約60日分) 使用しています。もしかしたら「えっ、なんでそんなに使っているの?」と思われているかもしれません。

本記事ではその疑問に回答しつつ、実運用から得られた知見を一部共有していきます。段階的に理解いただけるように、技術選定理由から説明していきます。

  • なぜ Node.js なのか?
  • なぜ AWS Lambdaなのか?
  • Lambda x Node.js でスクレイピングする際の落とし穴

ということで、早速1つ目からいってみましょう!

なぜ Node.js なのか?

ストックマークのプロダクトでは、Web記事などを中心としてスクレイピングして収集した情報をベースにコンテンツが提供されています。Webページから必要な情報を抽出するためには、クライアント側(ブラウザ側)でレンダリングされた後の情報が必要になります。現代のWebページは、Client Side Renderingされることが多いため、単に curl して得られた情報のみでは、必要な情報が不足してしまいます。

そのため、JavaScriptを実行して、HTMLが構築されたあとのDOMから必要な情報を抽出する必要があります。具体的には、Puppeteerを用いてHeadless Chromeでページのナビゲーションを制御したり、JSDOMでDOMの操作を行なったりしています。以前はPythonで処理を記述していましたが、HTMLやJavaScriptの制御のしやすさ、ブラウザのAPIとの親和性などから、Node.jsを選定してそちらへ移行しています。

なぜ AWS Lambdaなのか?

Node.jsやPuppeteerを活用している背景は、ここまででお分かりいただけたかと思いますので、次に利用しているインフラストラクチャについて説明します。

前述した Puppeteer はAWS Lambda上で動かしています。最初期のスクレイピングにはLambdaではなくEC2を利用していましたが、次のシンプルな要件を満たすのが困難になってきました。

👉 要件:1時間で1万記事をスクレイプできること

Stockmarkは毎朝、ユーザーごとに最適化された記事を配信しています。その記事のソースとなり得る記事を短時間に、かつ攻撃にならないように収集する必要があります。そこで、水平方向にスケールして、要件を満たせる実装として、2018年からAWS Lambdaを利用するようになりました。

しかしLambdaのおかげで万事解決したかというと、そういうわけでもありません。プロダクションサービスを提供するにあたり、実装上の落とし穴がたくさんあるのです。

Lambda x Node.js でスクレイピングする際の落とし穴

落とし穴 その1: Puppeteer がクラッシュする

Lambdaは関数単位で動作しており、関数間でステートが完全分離されていると考えている方も多いでしょう。しかし、実運用ではそのように動作してくれません。

実際にはLambdaを大量に動かしていると、VMにアタッチされたストレージにキャッシュが溜まり、容量が足りなくなってPuppeteerの処理が失敗するようになります。これは外部動作の振る舞いを観察して分かったことです。

そこで、内部の実装としてキャッシュによりストレージが枯渇しないように、キャッシュをpurgeする処理を入れています。現在はこれで正常に動作しています。

落とし穴 その2: Lambdaが最大並列実行数に到達しない

スクレイピングシステムは、Lambda → SQS → Lambda → SQS → ….というようなマイクロサービス構成になっています。大量のサイトをクローリングするためにはLambdaが並列に実行される必要があり、Lambdaにはあらかじめ最大並列実行数を予約しておく機能があります。しかし、実際にはSQSをトリガーにすると、Lambdaが最大並列実行数に到達しないことがあります。

最大並列実行数に実並列実行数が到達しないと観測してわかった場合には、同じLambda関数をIaCで複数個定義、作成することで、必要な並列実行数を担保するなどの工夫をしています。

落とし穴 その3: 巨大なXMLの存在

一部のサイトでは、XMLを利用して情報を配信しています。小さくシンプルなXMLであれば特に問題ないのですが、実際にはかなり大きなXMLが存在しています。具体的には、50MBを超えるXMLを複数圧縮して配信しているサイトがあるため、ダウンロード自体は問題なくても、parseにかなり時間がかかり、ものによってはタイムアウトで処理が失敗してしまいます。

もともとはJavaScriptですべて処理していたのですが、巨大なXMLを提供しているサイトは数十あることから、部分的なパフォーマンス・チューニングとしてRustで記述してコンパイルしたWASMを利用しています。本件の詳細は昨年のアドベントカレンダーの記事で説明してありますので、詳細はそちらをご確認ください。

落とし穴 その4: HTML Semantics に従っていないサイトの存在

Semanticsとは、HTML Elementに含まれる目的や役割のことです。HTMLには様々なtagが用意されており、たとえば h1 tagであれば、ページの最上位の見出しを表します。理想的には、tagを見れば「どれが見出しで、どれがナビゲーションで、どれが本文の段落か」がある程度わかるはずです。

しかし、現実では最低限のtagを除き、div や span tagで構成されているサイトも少なくありません。この場合、目的のコンテンツを抽出するためにHTML Semanticsを参考にできないため、HTMLの解釈とコンテンツの抽出方法に工夫をしています。(工夫について知りたい場合は、ぜひカジュアル面談などへどうぞ!)

まとめ

本記事では、Stockmarkのスクレイピング周りの技術選定理由と、実装上の落とし穴について説明してきました。本記事では紹介しきれない課題、またR&Dチームの研究開発力を活かした取り組みも進めておりますので、詳しく知りたい場合はぜひお話しましょう!