ソフトウェアエンジニアの福井です。
業務では飲食店Webページの機能開発をしつつ、Webページを新システムに段階的移行しています。Rettyに掲載されている飲食店は様々な契約種別があり、その種別ごとに新システムに移行しています。
RettyはCDNにFastlyを使っており、新システムへのルーティングはFastlyで以下のように実装していました。FastlyはCDN(VCLベース)サービスを利用しています。
新システムにルーティングしたい飲食店IDをディクショナリに保存します。リクエストURLの飲食店IDがディクショナリ内に存在していればバックエンドを新システムにします。
ディクショナリの制約と対応策
ディクショナリは最大1,000件までしか保存できない制約があります。サポートに問い合わせることで、この制約を引き上げられる可能性があります。
(refs. https://docs.fastly.com/products/network-services-resource-limits#vcl-and-configuration-limits) 以降この記事では最大10,000件に引き上げられたとして話を進めます。
今まではルーティングしたい飲食店件数はこの最大件数に収まっていました。ですが最大100,000件ほどの飲食店を新システムにルーティングする必要が出てきました。ディクショナリを使った現行方式はそのまま使えません。
この要求を満たすため、最終的に以下2つの対応策が考えられました。
Compute + Config StoreもしくはKV Storeを使う
- 新システムにルーティングする飲食店IDのストア: Config Store/KV Store
- Config Storeは最大100,000件まで最適化されている
(refs. https://www.fastly.com/documentation/guides/concepts/edge-state/dynamic-config/#config-stores) - KV Storeにソフトリミットはない模様
- Config Storeは最大100,000件まで最適化されている
- 方式:
VCLからComputeにリクエストを流し、ComputeからConfig Store/KV Storeを読み込む - 懸念
データ保存形式を変えてディクショナリを使う
- 新システムにルーティングする飲食店IDのストア: ディクショナリ
- 方式:
飲食店IDをディクショナリに保存できる最大件数(100,000)で割った余りをkeyに、その飲食店IDをカンマ区切りでvalueにし、ディクショナリに持たせる
e.g.) 10000001の場合はkey: 1, value: 10000001,10010001... と保存される
飲食店IDの偏りはないため、特に飲食店IDをハッシュ化するなどの必要はない - 懸念
- ディクショナリはVCLに展開されるが、そのサイズは最大3MB
(refs. https://docs.fastly.com/products/network-services-resource-limits#vcl-and-configuration-limits) - ディクショナリのvalueは最大8,000KB
(refs. https://docs.fastly.com/products/network-services-resource-limits#vcl-and-configuration-limits)
- ディクショナリはVCLに展開されるが、そのサイズは最大3MB
ディクショナリの制約に余裕があり、また実装コスト面で2つ目の対応策に決まりました。
実装
VCLはTerraformで管理しています。 最大100,000件の飲食店IDをディクショナリに保存するまでの流れを記載します。ルーティングする飲食店IDは日々変化します。
1 ルーティングする飲食店IDを以下のような形式のjsonファイルで定期連携する
{ "1": [10000001,10010001], "2": [10010002], ...
2 jsonファイルをTerraformで読み込み、parseし、ディクショナリに保存する
resource "fastly_service_dictionary_items" "routing" { # ... # valueを[10000001,10010001] -> "10000001,10010001"に変換する items = { for k, v in jsondecode(file("<jsonファイルパス>")) : k => join(",", v) } }
3 VCLでディクショナリを読み込み、新システムにルーティングする
set var.restaurant_id = <リクエストURLに含まれる飲食店ID>; declare local var.restaurant_id_remainder INTEGER; declare local var.restaurant_id_remainder_key STRING; declare local var.route_restaurant_ids STRING; set var.restaurant_id_remainder = std.atoi(var.restaurant_id); set var.restaurant_id_remainder %= 10000; set var.restaurant_id_remainder_key = std.itoa(var.restaurant_id_remainder); # ディクショナリに存在しているか # 結果は"10000001,10010001"という文字列が入る set var.route_restaurant_ids = table.lookup(route_restaurant_ids_dictionary, var.restaurant_id_remainder_key); # "10000001,10010001"に"10000001"は存在しているか # なお飲食店IDの桁数は全て同一桁数になる if (var.route_restaurant_ids && std.strstr(var.route_restaurant_ids, var.restaurant_id)) { # バックエンドを新システムにする }
飲食店IDカンマ区切り文字列から飲食店IDを探す際は正規表現を使う方法もあります。ですが正規表現の動的コンパイルはできないため、関数std.strstrを使っています。
まとめ
ディクショナリの制約に引っかからないようにデータの持ち方を工夫しました。今後100,000件以上の特定の飲食店を新システムに移行する見込みはありません。対象の飲食店が全て新システムに移行できればこのルーティングの仕組み自体なくなります。引き続きComputeへの移行も検討中です