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.5k
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
970
KEPPLE DB リプレースにおける技術的負債のバランス感覚
yuichiro_serita
1
410
Other Decks in Programming
See All in Programming
從零到一:搭建你的第一個 Observability 平台
blueswen
0
210
クラシルリワードにおける iOSアプリ開発の取り組み
funzin
1
800
Agent Rules as Domain Parser
yodakeisuke
1
330
ts-morph実践:型を利用するcodemodのテクニック
ypresto
1
540
少数精鋭エンジニアがフルスタック力を磨く理由 -そしてAI時代へ-
rebase_engineering
0
130
抽象データ型について学んだ
ryounasso
0
210
💎 My RubyKaigi Effect in 2025: Top Ruby Companies 🌐
yasulab
PRO
1
130
JSAI2025 RecSysChallenge2024 優勝報告
unonao
1
370
Use Perl as Better Shell Script
karupanerura
0
650
JVM の仕組みを理解して PHP で実装してみよう
m3m0r7
PRO
1
250
OpenTelemetryで始めるベンダーフリーなobservability / Vendor-free observability starting with OpenTelemetry
seike460
PRO
0
160
AIにコードを生成するコードを作らせて、再現性を担保しよう! / Let AI generate code to ensure reproducibility
yamachu
7
6k
Featured
See All Featured
Fashionably flexible responsive web design (full day workshop)
malarkey
407
66k
Producing Creativity
orderedlist
PRO
346
40k
Imperfection Machines: The Place of Print at Facebook
scottboms
267
13k
What’s in a name? Adding method to the madness
productmarketing
PRO
22
3.5k
Statistics for Hackers
jakevdp
799
220k
The Straight Up "How To Draw Better" Workshop
denniskardys
233
140k
XXLCSS - How to scale CSS and keep your sanity
sugarenia
248
1.3M
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
3.9k
How to train your dragon (web standard)
notwaldorf
92
6k
Build The Right Thing And Hit Your Dates
maggiecrowley
35
2.7k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
31
1.2k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
667
120k
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/