はじめに
今年、16年勤めた上場企業を辞め、ベトナム拠点のスタートアップ(日系)に転職しました。退職時に有給休暇がたくさん残っており、こういう滅多にない節目の長期休暇では、リフレッシュのため、自分探しの旅に出るんだろうなと想像していましたが、生憎のコロナ渦、ということで自宅に。細々とあれやこれややっていましたが、基本的には暇を持て余していました。
旅行することも出来ず、暇なので、勉強することにしました。Manager として長らく実装からは離れていましたが、スタートアップでは実装も求められるし。
という訳で、まずはこれまでほぼ担当することがなかったフロントエンドのリハビリから。YouTube で、html, css, js(vanilla) をやってみました。次に Python。そして Node.js、TypeScript と。そして AWS ばかりだったので GCP。時間だけはたくさんあるので、色々できました。教材は、YouTube と Udemy と書籍。
で、こういうの作れませんか?と相談を受け、実践の場を得て、先日リリースしたんですが、あちこち躓き、色々な学びがあったので、その反省文。
作ったもの
- 様々なサービスを大量に毎日巡回してスクレイピングし、データを貯めるバッチ
- そのデータをあれこれ加工して表示する Web サイト
最初は Python で作り始めたけど、最終的には、
- バッチ: Node.js
- サイト: Angular.js
- DB: MongoDB(AWS DocumentDB)
- サーバ: AWS EC2
の構成に。EC2 以外は、Production は初めて。
Python から Node.js へ
スクレイピングと言えば、Python。と BeautifulSoup でせっせと作っていましたが、GeoIP 問題。スクレイピングするものが、グローバルに展開しているサービスで、日本からだとアメリカのサイトが取得できないとか。まぁ、なんか解決方法あるんだろうけど、面倒くさいなーと、ネットを彷徨っていたところ、いい感じにやってくれるライブラリを発見。
だいたいいい感じにやってくれるんだけど、ちょっとだけ足らない。やりたいことが全部できない!そんな中、同様のライブラリを Node.js で発見。こちらはやりたいことが全部できる(その時点ではそう思っていた)。という訳で、Python を捨て、Node.js に変更。
学び1: スクレイピング、本当に自分でするの?
特に有名なサービスは、誰かが既にやってて、しかも公開されてるからそれを使った方が良い。Python コミュニティや Node.js コミュニティの Assets は検討の上、極力活用すべき。
MongoDB という罠
Local で開発する分には、Docker で MongoDB 公式の Image から立ち上げてしまえば、超簡単にできるし、今回利用したライブラリは、取得した値が JSON 形式で返ってくるので、(ほぼ)そのままぶっ込める MongoDB は使い勝手が良かったので、軽い気持ちで採用した。いいじゃん、NoSQL くらいの気持ちで。
Node.js から MongoDB への接続には、mongoose を使ったのだけど、mongoose のトップページに書いてあるコードをコピペしてちょっと変更したものが動かない!なんでやねん!とググったところ…
学び2: MongoDB 関連はドキュメントや記事が少ない。特に日本語。
MySQL 等の RDB に比べ、NoSQL は参考にできるものが極端に少ない。Stack Overflow 様がいなければ詰んでました。英語が読めて良かったよ。ついでに言うと、エラーメッセージ見てもサッパリ。
無事使えるようになって、さぁ Production!となった時に、問題発生。
学び3: AWS, GCP で MongoDB は MySQL ほど気軽に使えない
GCP では、MongoDB Atlas を使うか、コンテナ等で立ち上げるか、なのかな?なんだけど、ちょっと高すぎない?それにあちこちに支払いが発生するのも嫌だなと。コンテナでも良いんだけど、バックアップだの何だのやってられないので、GCP はやめました。
一方 AWS は?というと、こちらもちょっと高いんだけど、DocumentDB という MongoDB 互換の NoSQL が使えて、Node.js 側は何も変えなくても使えるので、こちらに。EC2 内とかコンテナで MongoDB を立ち上げるのは、前述の通り面倒でやってられなので、却下。DocumentDB も安くはないんだけど、個人で払うわけじゃないし、起業レベルで考えれば、まぁ良いかということで、相談の上、採用。立ち上げようとして問題発生
学び4: AWS DocumentDB はインスタンス数が1つでも、Multi AZ じゃないとダメ
(重要だけど)社内ツールなので、スケーリングもしないし、ロード・バランシングもしないし、Single AZ で良いやと VPC を構築していたので、作り直し。変なところで手を抜かなきゃ良かった。
なお、AWS と GCP は思想というか哲学というかが全然違う。AWS でよくやることは、GCP では… みたいな使い方は、すべきでない。
果たされない Promise
学び5: Node.js の非同期処理は1日にしてならず
Node.js って、他の言語に比べると、非同期処理の扱いがなかなか理解し難く、今回作ったバッチは、基本逐次処理したいものだったので、Promise ってなんやねん!Sleep ないのかよ!などと四苦八苦。async/await しまくって、かなり非効率なプログラムになってるんだろうなと思う。
また、Local では問題なかったものが、スペックの高いサーバで実行した結果、問題になることも。全然処理終わってないのに、じゃんじゃか進んじゃって、メモリが溢れるなど。初心者丸出しを痛感。
言語ごとの特徴をこれほど強烈に感じる言語も、他にはあんまりないのかなと思った。
メモリが足らない!
バッチは前述の理由で、メモリが足らなくなるので、処理を分割するのと、減らすのと。急遽仕様変更。何に時間がかかってるかは分かりやすかったので、まぁなんとか。チームひとりだったので良かったけど、Go Live 直前の仕様変更とか嫌ですね。
一方で、Angular.js の Build が通らなかった時は、もうどうしようかと。
学び6: Angular.js は AWS 無料枠の t2.micro では build できない?
Build 済みのものを持っていくとか、そういう風にするのが良いのかも知れないけど。社内ツールで、トラフィックもそんなにないし、インスタンスサイズはしょぼくても良いやと思ってたけど、ダメだった。最終的には、インスタンスサイズを上げ、
$ node --max_old_space_size=1069 (path_to_ng)/ng build --prod
などとして実行。メモリ増やしてもそのまま実行するとエラーになる。なった。Node.js でなにかを作る時は、メモリサイズをしっかり気にしないといけないんですね。
SPA で作るべきだったのか?
Angular.js を選んだ理由は、SPA(Single Page Application)やってみたかったから、ですが、最初は、うぉぉぉぉ!超速い!!!すげえええええ!!!!と感動していましたが、今回作ったサービスは、画像やビデオを大量に扱うため、Angular.js の assets ディレクトリが肥大化しがち。
で、案の定コンパイルできなくなり、まじかーと。
他にも、Creative Tim のテンプレート持ってきてそのまま使ったら、コンパイルできなくなった。そんなことあんの?と思ったが、コンパイルできなかった。
学び7: どこまで Single Page であるべきかは要検討
Webサービスにおいて、レスポンス速度は非常に重要な要素であり、SPA は1つの解ではあるものの、レスポンス速度改善の本質は別の所にあることが多い。
また、Creative Tim で配布されているようなリッチなコンテンツを実現したり、コンテンツがどんどん増えていったりなどの場合、Angular.json の budgets のサイズを変更することで、コンパイルはできるようになるが、SPA の利点が失われていくし、限度がある。
規模の大きなサービスを作る(そうなる可能性がある)場合は、よくよく考えてから使うようにしないと、後で泣くことになるかも知れない。ただ、Angular.js による SPA は感動するほど速かった。フロントエンドからバックエンドまで一気通貫で、JavaScript/TypeScript で作れるのも、非常に良い。今後続けて勉強していくかは悩ましい。
なお、大量の画像やビデオは、S3 に置いて、https アクセスするようにした。Creative Tim は使うのを止めた。全体的に Bootstrap を使ったけど、使わなくても良かったなと、出来上がったものを見て思った。コピペでサクっと出来ちゃうのは良いけど、Bootstrap と相性悪いライブラリもあるし、大したデザインじゃないし、flex box の万能感。
スクレイピングにおけるページロードの待ち時間
結局、どうしても独自でスクレイピングしないといけないものがあって、Puppeteer を使いました。待ち時間の設定はいくつかあるんだけど、どうにも思った通りに動かない。
やりたかったことは、2つあって、ページをスクロールして読み込まれるものを全部待ってからスクレイピングするのと、画像を並べて(直リンクで表示)スクリーンショットを撮るというもの。
Puppeteer の待ちオプションだと、うまく行かないケースがあって、仕方がないので、ロード状況等は無視して、一定時間必ず待つようにした。他にも、スクレイピング先に迷惑をかけないように、ちょいちょい待たせているので、時間がかかる。
遅いなーと思って、電卓叩いて全ての処理が終わる最小時間を計算したら、もう全然無理じゃんとなったので、スクリーンショットの方は止めることに。スクロールの方は、対象が増えると破綻するはずなので、なんか考えないといけない。
学び8: スクレイピングできることと、運用に耐えうるかは別問題
本番で失敗する前に電卓叩いた私は偉いと思うけど、デイリーで大量のスクレイピングを伴うサービスって難しいんだなと思った。Python でやる場合、言語自体のパフォーマンスも含め、実装は簡単でも実運用できる設計は難しいのかな。
Puppeteer は他にも、スクリーンショットでブラウザ幅がうまく変更できないとか、Chrome Headless Browser を Amazon Linux2 で動かすのに一手間いるとか、読み方が分からないとか色々つまづき所がある。ぴゅーぺてぃあー
終わりに
今回、Node.js, Angular.js + MongoDB を AWS で動かすサービスを、フロントエンドからバックエンド、インフラまで全て一人で作って Production にリリースしてみた。大量のトラフィックをさばく必要はなかったので、その点は楽だったが、初めてのものが多く、あれこれ躓いた。が、ぶっちゃけ楽しかったし、学びもたくさんあった。今後改善すべき点も、実務でバリバリやっている人にとっては、バカじゃねーの?ってレベルだとは思うが、明確なので、次のモチベーションにもなった。
大企業では分業化が進んでいて、サービスの全てを担当することなんてほぼないし、まして技術選定を全て決められることなんてない。基本的には枯れた技術を使って安定運用することの方が重要だったりするし。そういう意味で、スタートアップはエンジニアとして面白い。辛い点としては、助けてくれる人も聞ける人も近くにいないというのがあるが、インターネット上にいくらでもいるので、たいした問題ではなかった。できなきゃいつでもクビみたいな緊張感も、刺激的だ。
枯れた技術の運用ばっかりやっていても面白くもなんともないし、自身の技術力の向上感を感じることは少ないと思うが、今回新しい言語でも、久しぶりの実作業でも、割とすんなり対応できたのは、そうしたつまらん期間に基礎をみっちりやってきたからに他ならないと思う。16年もいる必要はないと思うが、つまらん期間も大事だ。
エンジニアの採用を長らくやってきて、ベンチャー思考、新しいもの(だけ)好きなエンジニアの薄っぺらさ、応用の効かなさ、基礎のなってなさみたいなものはさんざん見てきているので、まぁやっぱり大事で間違いないんじゃないかと思う。前職は16年前は現職より小さな会社で、あれよあれよと言う間に大きくなったが、会社が成長する過程で、上が詰まってなかったので、私自身も色々なことに挑戦できたというのは、ラッキーだったかも知れない。
終わりの最後に、途中で「チームひとり」と書いたが、いま本当にひとりだ。他にもエンジニアはいるが、もっとプリミティブな部分だったり、アプリだったり領域が違う。で、今後作っていくサービスのブループリントを描いて、おおまかな方針は決めたが、かなり壮大なサービスをスクラッチから作ることになる。特にトラフィックとレスポンス要件が、え?そんなにシビアなの??って感じで、当然、ひとりでは作れない。震える。ちなみに、Angular.js や Node.js は Plan B のオプションではあるものの、使わない。"ベトナム"のスタートアップで、壮大な VISION を技術面から一緒に追いかけたい、という人がいたらTwitterか何かでご連絡ください。いまが一番熱い時。