Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
TypeScript でバックもやるって実際どう? 実運用で困ったこと3選
Search
Yuichiro SERITA
November 27, 2024
Programming
18
8.7k
TypeScript でバックもやるって実際どう? 実運用で困ったこと3選
ROSCA株式会社さん主催のイベント
フロントからバックエンドまで、TypeScriptでシームレスな開発エクスペリエンスを
で発表させていただいた際に使用したスライドです。
Yuichiro SERITA
November 27, 2024
Tweet
Share
More Decks by Yuichiro SERITA
See All by Yuichiro SERITA
技術的負債と向き合うカイゼン活動を1年続けて分かった "持続可能" なプロダクト開発
yuichiro_serita
0
1.2k
KEPPLE DB リプレースにおける技術的負債のバランス感覚
yuichiro_serita
1
470
Other Decks in Programming
See All in Programming
マンガアプリViewerの大画面対応を考える
kk__777
0
400
テーブル定義書の構造化抽出して、生成AIでDWH分析を試してみた / devio2025tokyo
kasacchiful
0
320
GC25 Recap: The Code You Reviewed is Not the Code You Built / #newt_gophercon_tour
mazrean
0
130
Building, Deploying, and Monitoring Ruby Web Applications with Falcon (Kaigi on Rails 2025)
ioquatix
4
2.6k
contribution to astral-sh/uv
shunsock
0
550
スマホから Youtube Shortsを見られないようにする
lemolatoon
27
34k
What's new in Spring Modulith?
olivergierke
1
180
Cursorハンズオン実践!
eltociear
2
1.2k
kiroとCodexで最高のSpec駆動開発を!!数時間で web3ネイティブなミニゲームを作ってみたよ!
mashharuki
0
960
オープンソースソフトウェアへの解像度🔬
utam0k
17
3.2k
開発組織の戦略的な役割と 設計スキル向上の効果
masuda220
PRO
10
1.8k
iOSでSVG画像を扱う
kishikawakatsumi
0
170
Featured
See All Featured
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
658
61k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
35
3.2k
The Illustrated Children's Guide to Kubernetes
chrisshort
49
51k
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
140
34k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
190
55k
Into the Great Unknown - MozCon
thekraken
40
2.1k
A Modern Web Designer's Workflow
chriscoyier
697
190k
GraphQLとの向き合い方2022年版
quramy
49
14k
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
230
22k
Become a Pro
speakerdeck
PRO
29
5.6k
Principles of Awesome APIs and How to Build Them.
keavy
127
17k
4 Signs Your Business is Dying
shpigford
186
22k
Transcript
TypeScript でバックもやるって実際どう? 実運用で困ったこと3選 芹田 悠一郎 @株式会社ケップル KEPPLE CREATORS LAB
• 株式会社ケップル KEPPLE CREATORS LAB 所属 • フルスタック的に Web まわりのエンジニア
をやっています • めちゃくちゃ夜型です。正午の登壇すら 正直言ってちょっとつらい • とにかくコーヒーが好き 芹田 悠一郎 Yuichiro SERITA
前置き
TypeScript でバックもやる
賛否両論
飛び交うポジショントーク
何も信じられない……
立ち位置 • 小さいエンジニア組織 • そのわりに多いプロダクト • つまり全員フルスタックじゃないといけない • フロントが TypeScript
なので全員 TypeScript を使える (共通項) • バックエンドも TypeScript でいいじゃん
立ち位置 • TypeScript のバックエンドに NestJS を採用 • 小規模なもので Hono を使っているものもある
• そんなに特別なことをバックエンドでやっていない DB などから取得したデータを集計して JSON にして返す程度のレベル感
弊社の代表的な構成 フロント BFF データ ソース Next.js NestJS NestJS Hono TS
以外もある
話すこと • TypeScript でバックエンドを運用してて遭遇した困りごと • その困りごとの対処方法
持って帰ってほしいこと • すでに TypeScript でバックエンドを構築している方 → TypeScript でバックエンド構築するとやっぱこうなるんだ、みんな同じだね • これから TypeScript
でバックエンドを構築したい方 → TypeScript でバックエンド構築しても、トラブルってその程度で済むんだー、 へー、なるほどねー
本題
#1 例外が遅い。date-fns が意外と遅い
当時の状況 • とある API のレスポンスが、件数が多いときにやたら遅い • 外部 API から取ってきたデータに対し、フィルタしてソートして先頭1000件を返 すだけの処理
• 数万件くらいのデータがたまにあって、30秒以上かかってタイムアウトする
調査してみる • performance.now() 使って printf デバッグ • 日付の変換処理が遅かった • 外部
API から yyyy-MM-dd の形式で string の日付が返ってくる • Date にして返す
原因のコード import { isValid, parse } from 'date-fns'; import {
zonedTimeToUtc } from 'date-fns-tz'; export function tryParseDateAsJST( dateString: string | null | undefined, ): Date | undefined { if (dateString /= null) { return undefined; } const formats = ['yyyy/MM/dd', 'yyyy/M/d', 'yyyy-MM-dd', 'MM/dd', 'M/d']; // referenceDate は年をとりたいだけなのでタイムゾーン考慮しない const referenceDate = new Date(); for (const format of formats) { try { const zonedDate = parse(dateString, format, referenceDate); if (!isValid(zonedDate)) continue; return zonedTimeToUtc(zonedDate, JST); } catch { // noop } } return undefined; }
原因 • Node.js の例外は遅い。throw して catch するだけで 1 ミリ秒くらい •
date-fns の parse は実は遅い。 0.1ミリ秒の桁 • date-fns-tz (v2) の zonedTimeToUtc も実は遅い。 0.1ミリ秒の桁
対応 • parse するところは手書き const [yyyy, MM, dd] = dateString.split('-');
if (yyyy /= null /& MM /= null /& dd /= null) { return new Date( parseInt(yyyy, 10), parseInt(MM, 10) - 1, parseInt(dd, 10), ); } • zonedTimeToUtc は 1000 件に絞ってからかける → 50倍程度高速化できた
#2 CPU bound な処理
当時の状況 • 原因不明のタイムアウト • バックエンドがまったく応答しなくなる • 鬼のように飛んでくるアラート • heartbeat すら応答しない
当時の状況 • とりあえずコンテナの実行環境のスペックを上げてみたりしたが解決しない • ひたすらログとにらめっこ • エラー発生時に特定の処理が走っていることに気づく
原因 • どうしてもなくせない CPU bound な処理があった • データが増えてきて顕在化した • Node.js
は基本的にシングルプロセス・シングルスレッド • CPU bound な処理をすると他の処理がブロックされてしまう
解決策 • Worker threads を使って別スレッドで処理する ◦ 頻繁に叩かれる API ではなかった •
他にもいろいろ手はある ◦ Lambda に逃す ◦ 別言語の別バイナリに投げる
Worker threads 呼ぶ側 (大枠) import { Worker } from "worker_threads";
new Observable((subscriber) /> { const worker = new Worker(workerFilePath); worker.postMessage(args); worker.on("message", (message) /> { subscriber.next(message); }); worker.on("error", (error) /> subscriber.error(error.message)); worker.on("exit", (code) /> { // exit code を見ていろいろやる処理がありますが省略しています worker.terminate(); subscriber.complete(); }); }).pipe( catchError((err) /> { // worker側のエラーをメインスレッド側でハンドリングしないとメインスレッドが落ちるので注意 return throwError(() /> err); }) );
Worker threads 呼ばれる側 (大枠) import { parentPort } from "worker_threads";
parentPort.once("message", async (arg) /> { // いろいろ処理する // Promise で扱うなら postMessage は 1 回だけ呼び出すほうが面倒がない parentPort.postMessage(result); }
#3 NestJS の気持ちがたまに分からない
#3 NestJS の気持ちがたまに分からない #3.1 前提の話 (なぜ NestJS か)
opinionated なフレームワークがほしい • 強い制約がある代わりに生産性が高いフレームワークを opinionated という • プロダクトを早く届けるにはレールに乗るほうがよい • 必要なものが最初から揃っている
• 規約をゼロから作らなくても 「このフレームワークの作法ではこうだから」 で 決められる
opinionated なフレームワークのデファクトスタンダードがない • Node.js の世界では express のような薄いフレームワークが人気 (たぶん) • Ruby
だったら Ruby on Rails, Java だったら Spring, PHP だったら Laravel, のように、他言語だったら opinionated でデファクトスタンダードな フレームワークがある • Node.js にはデファクトスタンダードがない • 悩んだ結果、NestJS を選択 (2020年くらいから使ってます)
NestJS を実際に使った感想 • REST API と GraphQL をまとめて扱いやすい ◦ 書き味がだいたい同じ、Logger
や Interceptor を共通化できる、などなど • NestJS の作法にしっかり従う必要がある • Spring Boot や ASP.NET Core あたりに触れたことがあると馴染みやすい • Angular に触れたことがあると馴染みやすい
#3 NestJS の気持ちがたまに分からない #3.2 困ったこと その1 RxJS
RxJS とは (ざっくり) • 非同期処理の枠組みで、 Observable と呼ばれる非同期でデータを出力するス トリームが土台になっている • Observable
に対して宣言的に処理を書いていく 例:1,2,3 が流れるストリームを10倍する of(1, 2, 3).pipe(map(x /> 10 * x)) https://rxjs.dev/api/operators/map より抜粋
RxJS が (我々にとって) オーバースペック • RxJS で簡単にできて Promise では面倒なことはたくさんあるが、 ほぼ使っていない
(クライアントがコネクションを切ったら全処理を中断するとか) • 学習コストが高い • 記述量がかなり増えてしまう ◦ 複数の非同期処理が絡むとき、Promise なら await を書けばいいだけだが forkJoin や concatMap が必要 ◦ テストでは毎回 subscribe して done を呼ぶ
Promise を使っていいことにしました • 新たに実装する Resolver, Controller, Service は Promise を使う
• @nestjs/axios の HttpService は引き続き使用する。 firstValueFrom で Observable から Promise に変換する • リクエストのキャンセルなどが必要になったら別途考える
NestJS のリクエストのライフサイクル ここだけ Promise にする Middleware Guard Interceptor Pipe Controller
/ Resolver Service Interceptor Exception filter
RxJS と @nestjs/axios に関する余談 • @nestjs/axios の HttpService は、 Observable
の Teardownlogic でリクエストをキャンセルするように作られている • しかし、その処理は 2020年5月から2024年10月までずっと壊れていた ◦ https://github.com/nestjs/nest/pull/4803 ここで壊れて ◦ https://github.com/nestjs/axios/issues/1217 この issue がきっかけで修正された • Observable の恩恵受けてるユーザーほぼいないのかも……
#3 NestJS の気持ちがたまに分からない #3.3 困ったこと その2 モジュールシステム
NestJS のモジュールシステム • Angular とほぼ同じモジュールシステム • import していないモジュールの Service を
DI して使おうとするとエラー https://docs.nestjs.com/modules より抜粋
モジュールシステムが (我々にとって) オーバースペック • モジュールシステムが必要になるほどの規模のバックエンドを作ることがない • テストを書くときに独特の作法が必要で学習コストが高い ◦ 公式ドキュメントに書いてない (読み取れない)
ことが多い
ドキュメントとコミュニケーションで解決 • 分からないところは NestJS ソースコードを読む • ドキュメントを残す ◦ PR のコメントに書く、コード中にコメントを書く、程度でもよい
• 知見を共有する ◦ レビューやオンボーディングでしっかり共有する
#3 NestJS の気持ちがたまに分からない #3.4 困ったこと その3 Logger が独特
Logger が独特 • デフォルトの ConsoleLogger の日時の出力フォーマットが MM/dd/yyyy, h:mm:ss AA で固定
(例: 11/21/2024, 5:48:32 PM) • 引数が独特すぎて変更できないため、カスタムロガーを実装するにしても苦しい ◦ 最後の引数が string だったら context とみなす、stacktrace は引数の数を2 つにして2つ目に string で入れる、stacktrace かどうかの判定は正規表現で やっている、引数の数が2つではない場合は別の判定、Error オブジェクトを受け取 れるようになっていない、などなど……全部実装する必要がある
日付のフォーマットの対応 • Custom Logger を実装して対応 (まだやってない) ◦ Custom Logger 自体はあるが、ConsoleLogger
に移譲している • 今すごく困っているかと言われると微妙なので、後回しにしている
引数が独特すぎる問題の対応 • 引数を1つだけ渡すようにし、意図しない挙動を踏み抜くことを防ぐ ◦ Node.js の util.inspect を通して string で渡す
• 標準の BaseExceptionFilter など、内部で呼び出しているものに関しては 特になにもしない
おわりに
困ったことが全然出てこなくて資料作成に困った • 3選と銘打ったはいいものの、実運用で困ったことが出てこなくて困った • CPU bound な処理がキツいのなんて最初から分かってることなので、 実際のところ実運用で困ったとは言い難い • NestJS
に対していろいろ言いましたが、何の文句もないフレームワークなんて 存在しないと思う
NestJS は実運用に耐えます • NestJS 嫌いな人がネット上に多い • でも余裕で実運用できます (我々のようなスタートアップにおいては特に) ◦ 20チームで分担して1つの大きなバックエンドを作っていて……
みたいな組織には向かないと思います • バグ踏んで困った回数で言えば Next.js のほうが全然多い • 怖がる必要はない
TypeScript でバックもやるって実際どう?
普通に運用できます!
おわり
エンジニア絶賛募集中です! • TypeScript が好き • スタートアップが好き • 「プロダクトエンジニア」に興味がある ぜひお話ししましょう! https://lab.kepple.co.jp/