Redis での Rate limit 実装
〇〇 req/sec という rate limit を実装する例。 どちらも Fixed window counters を前提に説明する。
結論
- Rate limit 機能はカウンター(INCR)の要不要によって、最適な実装が変わる
- カウンターが必要な場合は timestamp 入り key を window に、不要な場合は ttl を window にするのが良さそう
カウンターが必要な場合
5req/sec、10req/sec などに制限するような場合
Redis の公式ドキュメントに記載されていた rate limit 実装を 丸パクり 参考にするのが良さそう
See. https://redis.io/commands/INCR#pattern-rate-limiter-1
# 公式ドキュメントの擬似コード
FUNCTION LIMIT_API_CALL(ip)
ts = CURRENT_UNIX_TIME()
keyname = ip+":"+ts
MULTI
INCR(keyname)
EXPIRE(keyname,10)
EXEC
current = RESPONSE_OF_INCR_WITHIN_MULTI
IF current > 10 THEN
ERROR "too many requests per second"
ELSE
PERFORM_API_CALL()
END
処理の流れ
- key に何らかの識別子(例では IP アドレス) & 秒ごと(unixtime)の timestamp 付与
- INCR と EXPIRE を atomic に実行
- INCR の戻り値の値で rate limit を超えているか判定し、API コールするかどうか決める
感想 & メモ
- key に timestamp が入ってるので実行秒ごとの rate limit counter が生成されて、不要になったら expire で設定した秒数後に消えるイメージ
- INCR の「key が存在しない場合は指定された key に 0 をセットしてから increment する」という挙動がミソ
- ちなみに 〇〇req/min の場合の実装例は https://redis.com/redis-best-practices/basic-rate-limiting/ に載ってる
- 後述の通り、カウンターが必要なケースで ttl を window に使ってしまうとレースコンディションが発生しうる、かつそれを回避しようとすると実装が複雑になるので、現時点ではこの実装が最適解と考えている
単一のカウンターで ttl を window に使うのはダメなの?
何らかの理由で INCR 後の EXPIRE のセットが失敗すると rate limit が機能しなくなる。 Redis ドキュメントにもレースコンディションが発生しうる、かつこれを回避するには複雑な実装が必要との記載あり。
See. https://redis.io/commands/INCR#pattern-rate-limiter-2
カウンターが不要な場合
1req/sec に制限するような場合
処理の流れ
感想 & メモ
- SETEX は atomic な処理でこちらは確実に ttl のセットが保証される
- カウントが不要な場合はこのやり方がシンプルで良い、やはり INCR を混ぜると一気に難しくなるのだと思う
20230717更新
いや、この場合もマルチスレッド環境にて同じタイミングで GET が走ると複数回 API リクエスト実行されるから競合状態は起きうる。(典型的な check-then-act 処理)
参考
diesel_cli のインストールで失敗する場合の対処法
$ cargo install diesel_cli --no-default-features --features postgres した時に以下のようなエラーが出る場合の対処方法を記す。
$ cargo install diesel_cli --no-default-features --features postgres
...
Compiling diesel_cli v1.4.1
error: linking with `cc` failed: exit status: 1
|
= note: "cc" "-m64" "-arch" "x86_64" "/var/folders/y_/4r9vf_jn4qx6wd44z61hd7bm0000gp/T/cargo-installc0nGsu/release/deps/diesel-c3215a2deafd6029.diesel.47e90c54-cgu.0.rcgu.o" "/var/folders/y_/4r9vf_jn4qx6wd44z61hd7bm0000gp/T/cargo-installc0nGsu/release/deps/diesel-c…nodefaultlibs"...
= note: ld: library not found for -lpq
clang: error: linker command failed with exit code 1 (use -v to see invocation)
error: failed to compile `diesel_cli v1.4.1`, intermediate artifacts can be found at `/var/folders/y_/4r9vf_jn4qx6wd44z61hd7bm0000gp/T/cargo-installc0nGsu`
Caused by:
could not compile `diesel_cli` due to previous error
結論
以下のどちらかで解決する。
- postgresql をインストールする
- libpq をインストールして PATH 通す
$ brew install postgresql $ cargo install diesel_cli --no-default-features --features postgres # or $ brew install libpq $ PATH="/usr/local/opt/libpq/bin:$PATH" cargo install diesel_cli --no-default-features --features postgres
公式ドキュメント(のドラフト)的には postgresql をインストールするよう書いてあるので、普通にそっちで良さそう。
See. https://github.com/diesel-rs/diesel/blob/v1.4.8/guide_drafts/backend_installation.md#mac-osx
説明
--no-default-features --features postgresは diesel のバックエンド(DB)を postgres に限定するオプション、デフォルトでは diesel は mysql, sqlite, postgres 全てのクライアントライブラリのインストールを要求する- libpq は keg-only(デフォルトで PATH が通らない)なパッケージなので、明示的にパス指定してやらないと動作しない
- 以下、
$ brew install libpqした時に表示されるメッセージlibpq is keg-only, which means it was not symlinked into /usr/local, because conflicts with postgres formula.
- 以下、
参考
型システム入門 プログラミング言語と型の理論(TAPL)読書メモ
See. 型システム入門 プログラミング言語と型の理論 | Amazon
メモ
- まずは5, 9, 11章読んでマスターするのがオススメ、と以下動画で訳者の方が言ってる
- 3章は簡単なことを大袈裟に言ってるだけな気がする
- 11章でバリアント、オプション、列挙型、全部出てきて最高すぎる
- 15章の部分型付けも知りたかったやつ
- 18章の型システムからみるオブジェクト指向も面白い
- 19.3 で Nominal typing(名前的部分型)、Structual typing(構造的部分型)の話出てくる
- 22章で型推論(シンプルなやつだけど)
- 数式はほぼ飛ばして文章の部分ばかり読んでる
知りたかったこと
型システムのメリットを分かりやすく
TAPL 1.2 に書いてあったので自分なりに要約する。
- エラーの検出
- 静的型検査の最も分かりやすい恩恵、プログラミングのエラーを実行前に検出できる
- 型検査時の方がエラー箇所を正確に特定できることが多い
- 抽象化
- モジュールのインターフェース(これ自身「モジュールの型」とみなせる)と実装を切り離して設計・議論することで、インターフェースをより抽象的に捉えることができる
- ドキュメント化
- 型宣言によりコードを理解しやすくなる、またこのドキュメントは静的型解析によって常に検査されるので古くなることもない
- 言語の安全性
- 静的型の安全性とは違う話らしい、、あまり理解できなかった
- 効率性
- 型情報を用いてコンパイラの最適化に貢献
👆 に加えて、今なら IDE による補完などもメリットに入ると思う
Also see. 私と型システムとポエム - The curse of λ
代数的データ型とは
TAPL 読む前の知識
- Haskell 代数的データ型 超入門 - Qiita
- 以下の3つを合わせて代数的データ型(ADT)という?
- 列挙型: いわゆる enum
- 直積型: いわゆる struct
- 直和型: いわゆる union
- 以下の3つを合わせて代数的データ型(ADT)という?
- 代数的データ型 - Wikipedia
- Wikipedia 的には代数的データ型とは直和型のことで、直積型と列挙型は特殊な例として扱われてる
- すごい Haskell 7 章にも記載あり
TAPL 読んだあと
- ...
部分型付けについて
- 構造的部分型とは
A: { a: number } :> B: { a: number, b: string }の A, B のように、親の型の構造が子のそれを含有する時、部分型であるとみなす型システム - 名前的部分型とは
B extends Aのような宣言があった場合のみ部分型であるとみなす型システム - A が親(上位型)で B が子(部分型)、子の方が親より多くのフィールドを持つ(!)
- TAPL 15.2 にフィールドの数が多い方がより小さい型(部分型 = 子の型)であるのは意外かもしれない。って書いてて自分がずっと引っかかってたところだったので最高!となった。 曰く、「フィールドが多い型の方がより要求の厳しい(より情報量のある)仕様を構成し、値のより小さな集合を形成することになるため」
- 共変と反変についても15.2に書いてある
iOS アプリ開発のメモ
Xcode
- 2021/08 現在 App Store からインストールできる安定版の最新は v12.5.1、Apple のサイトからならベータ版の Xcode 13 もインストール可能
- Xcode 13 から vim mode が使える
- フォーマッタがない、一応インデントの揃えだけ ctrl+i でできる
- プロジェクト作るとプロダクト名(= プロジェクトルートのディレクトリ名)がそのままエントリーポイントのクラス名になるので、パスカルケースで書かないといけない
- playground でピュアな Swift コードの実行もできる
Swift
- var が変数、let が定数!js と紛らわしい!
- class と struct という類似概念があるが基本的には struct を使用することが推奨されている
- class と struct の違いは値が渡される時に struct は常にコピーされる(値渡し)が、それに対して class は参照が渡されること
- class は明示的に参照を渡して保持するデータの一意性を担保する必要がある場合や Objective-C との互換性が必要な場合などにのみ使用すべき
- See. https://developer.apple.com/documentation/swift/choosing_between_structures_and_classes#overview
- protocol は他言語でいうインターフェース
- ドットアクセスから始まるやつは Implicit Member Expression って名前で、型メンバーへのアクセス時に型推論で暗黙的にレシーバの型が決定できるなら型名を省略できるというやつ
- where は generics に制約(条件)をつけるやつ
- rust や kotlin にもある
参考
- Xcode Help
- Xcode | Apple Developer Documentation
- Xcode - Videos - Apple Developer
- A Swift Tour — The Swift Programming Language (Swift 5.5)
- Swift の言語仕様がまとまってる、Xcode の playground で実行しながらやると良さそう
- Swift Tour の日本語版(v5.4 だけど)
- Swift.org - Swift Extension for Visual Studio Code
- server side swift 書く時に vscode で書けるようにするやつ
- SwiftUI Tutorials | Apple Developer Documentation
- Introduction to SwiftUI - WWDC20 - Videos - Apple Developer
- サンドイッチのアプリ作る動画、めっちゃいい
- ミクシィのiOS新卒研修資料を公開しました - mixi developers
- GitHub で Star の多い Swift プロジェクト
- Swift Playgrounds - Apple(日本)
- 子どもにやらせたい
無料にこだわる個人開発アプリ・システム構成
[2024/02/26 Update]
Vercel もDB 提供するようになったし、Planetscale や Supabase も出てきてます。この記事の内容は古いので鵜呑みにしないでください。
[2022/08/26 Update]
Heroku 有料になりましたね、、無念。
チラシの裏的な記事です。やっぱりなるべく無料にこだわりたい。
Next.js + Vercel + DynamoDB
- Vercel と DynamoDB の間に API Gateway と Lambda 挟むパターンだと API Gateway で課金される
- 実装例 https://github.com/leerob/nextjs-aws-dynamodb
- Vercel は個人的かつ非営利目的なら無料プラン利用可能
- Vercel の Serverless Functions はめちゃ便利
Node.js or Rails アプリ + Heroku + Heroku Postgres + Heroku Redis + その他アドオン
- Heroku やっぱ強い
- 他言語のアプリも載せられるけど(自分の使える言語の中だと)Node と Ruby のサポートがいい
- 無料の場合スリープモードへの対策が必要
- Next.js は単なる Node アプリなので Heroku にデプロイすることもできる(パフォーマンス的な意味での Next.js の良さは削がれる)
Create React App or Next.js (Static HTML Export) + Firebase Hosting + Firebase Authentication + Firestore + Cloud Storage
- Firebase に全部のっかるパターン、クライアントアプリは Firebase Hosting でホスティングしなくてもいいけど
- Firebase Authentication や Firestore などは単体でも便利なのでそこだけ切り出して Vercel(やその他サーバ)と繋げるのもあり
- サーバーに Firebase Admin SDK を追加する
- サーバで使う場合も SDK は Node.js が最優先のサポート対象っぽいので TypeScript 使っとくのが良さそう
- Cloud Functions 使えば外部 API も叩けるけど Blaze プラン必要
Dockerfile + Cloud Run
- Cloud Run は任意のコンテナをデプロイできるやつ、AWS でいうと ECS みたいなやつ?(よくわかってない)
GAE (Google App Engine)
- Google だし Go と相性いいのかしら(よくわかってない)
番外編「静的 Web サイトをホスティングするなら」
[2024/02/26 Update]
今なら Cloudflare Pages かなぁ
- 現状だと Netlify が一番好き
- GitHub Pages 好きだったけど Netlify と比べると反映遅いし、Firebase Hosting や Vercel を静的サイトのホスティングに使うのは何かオーバースペック感ある
感想
- やはり個人開発には Vercel, Heroku, Firebase 辺りが強い味方
- フロントエンドは Create React App と Next.js で迷ったら Next.js 使っとけばいいかな(静的サイトの場合でも)、と思ったけどまだ迷い中
- See. 普段使いから始めるNext.js
- Next.js は Jest と統合されてないのが少し不満(Zero Config を謳うなら Jest も入れといて欲しかった...)
- 一応 next.js/examples/with-typescript-eslint-jest at master · vercel/next.js はあったので、create-next-app の
--exampleオプションで入れられる
- 一応 next.js/examples/with-typescript-eslint-jest at master · vercel/next.js はあったので、create-next-app の
- 不要な依存も入るし CRA の方がいいケースもあるかも
- とりあえず無料で個人開発するには無料で使えるサーバと DB(またはその代替となるもの)を見つけるのが鍵っぽい
- 要件的には外部 API を叩けることと任意のデータを永続化できること、かな
参考
API や DB のスキーマからコードを自動生成する系のツールを触ってみた
API のスキーマからコード生成する系
OpenAPI Generator
チュートリアルなかったので拾ってきた OpenAPI の定義ファイルから typescript-fetch なるジェネレータでコード吐くところまでやった。
メモ
- OpenAPI のスキーマ定義から API クライアントのコードを自動生成できる(サーバ側コードも生成できるっぽい)
- 有名なツールだけど 2000 件超えの issue があってググるとそれに不満の声をあげてる人がけっこういる
- いろんな言語のジェネレータがある
参考
- TypeScriptとOpenAPIスキーマで型安全に READYFORが語る“スキーマファースト”で効率的な開発方法 - ログミーTech
- OpenAPI や Protocol Buffers のおかげで開発がかなり捗っている話 - Medley Developer Blog
- GoにおけるAPIドキュメントベースのWeb API開発について登壇しました | フューチャー技術ブログ
GraphQL Code Generator
チュートリアルなかったので適当な graphql スキーマからクライアントの生成(typescript-operations, typescript-graphql-request)とリゾルバの型定義の生成(typescript-resolvers)するところまでやった。
メモ
- 主に TypeScript 用、GraphQL のスキーマ(とクエリ)から API クライアント(React 用とかもある)を生成したり、サーバ側のリゾルバの型定義を生成したりできる
- 基本の
@graphql-codegen/typescriptだけだと最低限の型定義のみで、プラグインを使うことでクライアントやリゾルバの型定義を生成できる- プラグインについては All Plugins | GraphQL Code Generator が詳しい
- ts 以外にも java のリゾルバとかも吐けるっぽいけど、基本は ts 向け
参考
- graphql-codegen で型定義を生成する (React, Apollo, TypeScript) - Qiita
- GraphQL Code Generator の使い方。〜GraphQLのSchemaからTSの型定義を自動生成する〜 - Qiita
- GraphQL Code Generator で TypeScript の型を自動生成する - クックパッド開発者ブログ
gqlgen
Building GraphQL servers in golang — gqlgen やった。
メモ
- GraphQL のスキーマからサーバ側のコードを生成する Go のライブラリ
- チュートリアルではスキーマからコード生成 -> resolver 実装 -> モデルのコード変更(自動生成された
graph/model/models_gen.goとは別にgraph/model/xx.go的なファイル作る、struct の名前被ってても generate コマンド打てば勝手に上書きしてくれる) -> generate コマンドで未実装の resolver 生成 -> スキーマを満たす resolver 実装って感じ- 👆 ではコード変更がモデルから始まってるけど、実運用で変更を加える際には最初にスキーマ変更やってからモデルの変更になるのかな
- メルペイの人が gplgen で todo アプリの API サーバ作る動画があって、便利さが分かりやすい
参考
- 春の入門祭り🌸 #7 作って学ぶGraphQL。gqlgenを用いて鉄道データ検索API開発入門 | フューチャー技術ブログ
- Go + gqlgenを使ったGraphQLアプリケーションサーバーの実装 | Money Forward Kessai TECH BLOG
- Go + TypeScriptによるGraphQLスキーマ駆動開発 - 一休.com Developers Blog
gRPC
メモ
- gRPC っていうかスキーマファーストやコードの自動生成の文脈で大事なのは Protocol Buffers の方か(もうちょい調べる)
- あと、gRPC はブラウザで動かないので Web のフロントエンド ⇄ バックエンド間の通信には使えない(サーバ間通信に使う想定)
参考
- Protocol Buffers で快適な API 開発環境を構築した話 | Raksul ENGINEERING
- Web API に秩序を与える Protocol Buffers / Protocol Buffers for Web API #builderscon - Speaker Deck
- スキーマ定義言語 Protocol Buffers と protoc-gen-swagger を使って Web API のスキマを埋めよう - VOYAGE GROUP techlog
- 今さらProtocol Buffersと、手に馴染む道具の話 - Qiita
- OK Google, Protocol Buffers から生成したコードを使って Node.js で gRPC 通信して | メルカリエンジニアリング
- TypeScriptにおけるgRPC関連ライブラリの比較とプロダクト開発で採用した方法の紹介 - CADDi Tech Blog
- gRPCを使ったWebアプリケーション開発
- フロントエンドエンジニアも知っておきたいgRPC - Speaker Deck
DB or モデルのスキーマからコード生成する系
Prisma
Quickstart: Getting started with TypeScript & SQLite | Prisma Docs やった。
メモ
- モデルの定義を Prisma schema で書くと型安全な DB クライアントを自動生成してくれる ORM(今は TS/JS 用だけど、Go もサポートされるかも)
- Prisma schema が読み書きしやすくて良い、自動生成してくれるコードも過不足なく良い感じ
- mizchi 氏の「お節介ではないクエリビルダ」っていう表現がしっくりきた
- Prisma1 の頃は GraphQL 優先のツールだったっぽいけど、今の Prisma2 では GraphQL あんま関係なくなってる
参考
- Why Prisma? Comparison with SQL query builders & ORMs | Prisma Docs
- 5分で終わるPrisma 2 Tutorial - valid,invalid
- blitz-js prisma rails 倒し方
- Re: Rails を主戦場としている自分が今後学ぶべき技術について
Hasura
コース紹介 | Hasura GraphQLチュートリアル を途中までやった。
メモ
- DB のスキーマ定義書くと GraphQL API サーバを自動で構築してくれるやつ(現状は PostgreSQL のみ、MySQL はまだベータ版的位置づけっぽい)
- リッチな GUI で Hasura が構築する API サーバの設定(DB のスキーマ含む)や GraphiQL を使った動作確認ができる
- 標準だと DB スキーマに則った CRUD しかできないが、Remote Schemas なるものを使えばカスタムのリゾルバも定義できる
- 触ってないけど認証認可の仕組みもある模様
- REST API も最近サポートし始めたっぽいが、REST 形式でリクエスト受け取って、内部で GraphQL に変換してるだけっぽい
参考
- Hasura vs Prisma
- Hasura とは何者か メリット・デメリット - Speaker Deck
- 組織戦略と GraphQL、Hasura - Speaker Deck
- GraphQLサービスを提供するHasuraが約26.5億円を調達、MySQLを新たにサポート | TechCrunch Japan
今回やらなかったこと
調べていく中で見つけたやつ。
ISUCON 過去問やってみた話
社内勉強会のコピペ。
結論
ISUCON の過去問やって自分の無力さを思い知った話です
:@daido1976: @daido1976
- 2018/04〜 :feed: Feedforce Inc.
- パフォーマンスチューニングできるようになりたい
ISUCON とは
Iikanjini Speed Up Contest
See. https://www.google.com/search?q=isucon+%E3%81%A8%E3%81%AF
アプリケーションの仕様は変えずに Web アプリケーションのパフォーマンスをチューニングするコンテスト
パフォーマンスチューニング?
サーバ側なら、
- n+1 クエリの解決
- DB にインデックス貼る
- キャッシュとか???
知識としてはふんわり知ってる。実際にやれと言われて一人でできるのか?またそのチューニングの効果を実際に体験したことはあるか?
今回のルール
- 題材は ISUCON7(Docker で動かせる とのことだったので)
- 答え(参加者のブログ、ソースコード)見て OK
- 本番と同じく制限時間は(だいたい)8 時間
リポジトリ
https://github.com/daido1976/isucon7-qualify
環境構築
初回ベンチ
スコア 4000 前後
チューニング開始まで
- 自分の書いたコードに差し替えて開発できるようになるまでに半日ハマる(https://github.com/isucon/isucon7-qualify をクローンするような Dockerfile になってる)
- ほとんど Docker 力のなさによるもの
Ruby アップグレード
スコア変わらず
Ruby 2.4.2 -> 2.6.6(2.7.2 まで上げると bundler のバージョンでエラーが出たのでやめた、多分 Dockerfile の中で bundler をインストールしなおせば解決するやつ)
nginx に Cache-Control public; を追加
:github: Add Cache-Control public to nginx.conf
ここからがスコアを上げるための施策(チューニング開始)
スコア変わらず
これってローカル Docker で動かすベンチマーク(Go の HTTP クライアント)でも効くのか?CDN なら物理的な近さで早いの分かるけどローカルなら意味ない?
DB にバイナリで突っ込まれている icon 画像をファイルに書き出して nginx で配信するようにした
スコア4946 まで上がった、でも安定しない、これもローカルではあんまり効かないとか? そういえば画像をバイナリで DB に突っ込むのやめましょうってよくいうけどなんで?容量を取られるの分かるけど、ストレージも RDB でもバイナリの読み書きするなら速度的には変わらないのでは???
n+1 の解決(GET /message)
:github: Resolve N+1 in GET /message
GET /fetch の sleep 消した
:github: Remove sleep in GET /fetch
スコア 18056、ここでグッと上がった。
n+1 の解決(GET /history/:channel_id)
スコア 19357、GET /message とほぼ同じ。無論共通化などしている余裕はない。
:github: Resolve N+1 in GET /history/:channel_id
message テーブルの channel_id にインデックス追加
スコア 37783、グッと上がった。何度かやるとスコアは激しく(1 万点ぐらい)上下する、原因が分からない…
:github: message テーブルにインデックス貼る
結果
ここまでやって 100 位ぐらい
See. https://isucon.net/archives/50961437.html
次回やりたいこと
- ボトルネックを見つけるのが一番重要なので次回はそこから(プロファイリングツールなど使って)
- isucon9 が良問らしいのでやりたい
- Go でやる(参加者のほとんどが Go なので情報が多いし、サンプル実装も良いらしい)
関連記事
参考になったブログ
- ISUCON7予選2日目「Railsへの執着はもはや煩悩」で予選通過した - k0kubun's blog
- ISUCON7 予選突破コードをissue & PR付きで大公開! | Wantedly Engineer Blog
- ISUCON7の予選をはてなメンバーで通過してきました | おそらくはそれさえも平凡な日々
- このブログなんとかしたい: ISUCON 7 予選に参加した話
- ISUCON7の予選を突破しました! - abcang’s blog
- ISUCON7予選の上位陣の戦略まとめ - Bit Journey's Tech Blog
- ISUCON7予選を2日目3位で突破した(2017/10/23) - beatsync.net
- ISUCON7予選で81位でした
- ISUCON7 予選に参加した | TRIAL DANCE
- 社内勉強会でISUCON大爆死の話をしたのでスライドも公開した - はのちゃ爆発