Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
TypeScript でバックもやるって実際どう? 実運用で困ったこと3選
Search
Yuichiro SERITA
November 27, 2024
Programming
4
890
TypeScript でバックもやるって実際どう? 実運用で困ったこと3選
ROSCA株式会社さん主催のイベント
フロントからバックエンドまで、TypeScriptでシームレスな開発エクスペリエンスを
で発表させていただいた際に使用したスライドです。
Yuichiro SERITA
November 27, 2024
Tweet
Share
More Decks by Yuichiro SERITA
See All by Yuichiro SERITA
KEPPLE DB リプレースにおける技術的負債のバランス感覚
yuichiro_serita
1
280
Other Decks in Programming
See All in Programming
Kaigi on Rails 2024 〜運営の裏側〜
krpk1900
1
300
PipeCDの歩き方
kuro_kurorrr
3
130
みんなでプロポーザルを書いてみた
yuriko1211
0
290
Jakarta EE meets AI
ivargrimstad
0
1k
A Journey of Contribution and Collaboration in Open Source
ivargrimstad
0
1.4k
Reckoner における Datadog Browser Test の活用事例 / Datadog Browser Test at Reckoner
nomadblacky
0
160
React への依存を最小にするフロントエンド設計
takonda
21
7.6k
DoctrineとPostgreSQLの便利な機能、どっちも諦めたくない!
77web
0
150
Arm移行タイムアタック
qnighy
0
370
シェーダーで魅せるMapLibreの動的ラスタータイル
satoshi7190
1
490
TypeScriptでライブラリとの依存を限定的にする方法
tutinoko
3
780
[FlutterKaigi2024] Effective Form 〜Flutterによる複雑なフォーム開発の実践〜
chocoyama
0
3.5k
Featured
See All Featured
A Tale of Four Properties
chriscoyier
156
23k
Code Reviewing Like a Champion
maltzj
520
39k
Rails Girls Zürich Keynote
gr2m
94
13k
For a Future-Friendly Web
brad_frost
175
9.4k
Learning to Love Humans: Emotional Interface Design
aarron
273
40k
Docker and Python
trallard
40
3.1k
Building a Scalable Design System with Sketch
lauravandoore
459
33k
Done Done
chrislema
181
16k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
356
29k
The World Runs on Bad Software
bkeepers
PRO
65
11k
Designing the Hi-DPI Web
ddemaree
280
34k
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
29
2.3k
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/