なぜ"REST"なのか？性能で考えるウェブアーキテクチャ 

RESTはウェブサービスのアーキテクチャスタイルとしてよく採用されている。それゆえにか、RESTに関したモヤッとした観念的な議論が多く行われる事がある。この記事ではそのアンチテーゼとして、RESTを中心に性能に立脚してウェブアーキテクチャを語る。

# 観念的に語られる"REST"

RESTはRepresentational state transferの略であり、Web系のプロダクト開発に携わっている人には広く知られた単語だろう。このQiitaで"REST"と検索すれば多くの記事が出てくる。RESTはウェブサービスのソフトウェアアーキテクチャスタイルの1つである、というのがこの"REST"の端的な説明としては適切だろう。

このRESTに関し、「RESTとは何か」「どのようにRESTでリソースマッピングするのか」「REST APIでアプリケーションのエラーはHTTPステータスコードで表すべき/表すべきではない」などといった話題が耐えない。だが、そこで語られる多くのことの多くは、「こうあったほうがわかりやすい」や「こうあるべきだ」といった、主観的なというか、観念的なものである。もちろん、わかりやすさも開発効率という意味で大事だし、ソフトウェアコンポーネント同士を分離して責任範囲を明確にすることも業務において大事であろう。

ただ、こういう観念的な視点はどうしても答えが分かれがちである。例えば、REST原則としてHTTPメソッドのセマンティクスをきちんと正しく扱うべきだとする物がある。「URIによって指し示されるリソースを更新する冪等性のある操作はPOSTではなくPUTを使うべきだ」といったようなものがそれだ。ちなみに、私個人としてはPUTを使うべきというのに同意で、自分が設計しろと言われたらそうするだろう。でも、一緒に仕事をする人に「POSTで動くんだからPOSTにしよう」と言われても「そうだね」と返す可能性が高い。よほどの特殊事情がない限り、POSTの代わりにPUTを使っても性能に寄与しないからである。元から観念的な意見である以上、ゴリ押しする必要もないというのが私のスタンスである。

人によっては「RESTはアーキテクチャスタイルなんだから観念的なのは仕方ないだろう」と思うかもしれないが、**決してRESTは単に観念的な概念ではない**。この記事ではRESTを中心にウェブアーキテクチャの観念的ではない側面、「**性能**」に焦点を当てよう。観念的なことを語るのはその後でも遅くないだろう。

# RESTの原点

RESTの提唱者はRoy Fielding氏であり、氏の博士論文がその原点とされている[^1]。オンラインで公開されてて誰でも読むことができるので、時間があるときに読んでみると面白いかもしれない。なお、本記事はこの論文を解説するのではなく、あくまでその考え方に触れつつ、ウェブのアーキテクチャにごちゃっと述べるものである。REST自体を知りたい方はFielding氏本人の論文にあたってほしい。

https://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm

[^1]: もちろん「初出」ということではない。博士論文というのは原則としてそれまでの研究をまとめ上げた文章であり、その過程で新しい言葉を定義したりはしていても、全く新しい新規の提案がそこでなされているということはめったに無い。Fielding氏の博士論文もそうで、博士論文を出す少し前に行われた学会に本人が出した Principled design of the modern Web architecture https://ieeexplore.ieee.org/abstract/document/870431 ですでにRESTの提案がなされており、博士論文からも参照されている。

なお、一般に概念というものは時とともに変化していくし、ウェブに絡む状況の変化もあるために、オリジナルのRESTがいまFielding氏が考えているRESTと同一でないこともありえるだろう。ただ、私が読んだ限りこの論文自体は未だに陳腐化していないと見ている。

Fielding氏は博士論文でRESTのアーキテクチャスタイル、すなわち制約を語る前に、アーキテクチャの評価の難しさを指摘している(2.2)。アーキテクチャの良し悪しはそれを実行した時の特徴から得ることができるだろうが、現実にアーキテクチャが必要になるのは実装を完了させる前であるため、単純に評価を取って比較というわけにはいかない。そこでFielding氏はアーキテクチャの評価指標を明確に設けつつ、アーキテクチャスタイルを制約のツリーと捉えて、それを構築していくという考え方を提案している。この論文は2000年のものであるが、この考えが輝きを失っているということはない。その証左に、Fielding氏の論文の第3章においてMobile Code Stylesとして整理されているスタイルは、SPAやPWAといった今のウェブ技術そのものである。流れが激しいと言われるウェブの世界でも、その根底に流れるアーキテクチャは20年以上経っても陳腐化していないという事実はある意味で救いである。 ~~なお文中にある通り当時からHotJavaがあったのですが。~~

さて、Fielding氏がアーキテクチャ評価指標に選んだものは大きく7つに分けられているが、その筆頭としたものはなにか？そう「**性能(Performance)**」である。その次に"Scalability"、"Simplicity"などと続いていくが、論文全体を通しても最も力を入れているのが性能である。

# ウェブサービスの性能を左右するもの

コンピュータシステムに対する要求として必ずと行っていいほど挙げられるのが情報セキュリティと性能である。まずここに異論は無いであろう。なにせどっちも意味が広い！そのため、それらを語る前に、その意味を具体化する。

ウェブサービスで最も重要な性能指標となるのは、ユーザーが所定の操作をしてから、それが画面に反映されるまでの時間であろう。よくレスポンス時間と呼ばれるものであり、短いほうが良い。このレスポンス時間は終点をどこに置くか(例えばすべての表示を終えるまでか、部分的に低品質で良いので表示されるまでか、など)で定義が変わってくるが、ざっくり以下の要素がその増減に関わってくる[^2]。

[^2]: くどいかもしれないが、この記事はFielding氏の論文を単に解説するものではないため、同じ分類をとっていない。氏のパフォーマンス分析はもっと一般的な応用ができる形になっているが、ここでは典型的なウェブサービスに限定することでわかりやすくしている。

- ブラウザがリクエストを解釈/生成する時間
- サーバーとの接続セットアップ
  - 名前解決(DNSなど)
  - TCPコネクション確率
- リクエスト送信
- サーバー処理時間
- レスポンス受信
- ブラウザでのレスポンス解釈と描画

見ての通り、ウェブサービスというのは、明らかにネットワークを用いるアプリケーションの一つである。そして、ネットワークを用いるアプリケーションにおける一般的な傾向として、ネットワークを使わないことが最もパフォーマンス的に優れている。もしキャッシュにより、ネットワークから要求をとってくる必要がなければ、上の一覧はこの様になる。

- **ブラウザがキャッシュに問い合わせる時間(NEW)** ~~ブラウザがリクエストを解釈/生成する時間~~
- ~~サーバーとの接続セットアップ~~
  - ~~名前解決(DNSなど)~~
  - ~~TCPコネクション確率~~
- ~~リクエスト送信~~
- ~~サーバー処理時間~~
- ~~レスポンス受信~~
- ブラウザでのレスポンス解釈と描画

ある時点で普及している一般的な機器を組み合わせたシチュエーションなら、キャッシングで通信をしないほうが圧倒的に速い。SSDのレイテンシは1桁ミリ秒かそれ以下であり、最近のものであれば瞬時に1Gbpsを超える帯域幅も出る。ウェブで表示するような1MBを切るような画像であれば、もはやディスクIOは画面の更新速度に相当する環境というのもザラではない[^3]。また直近にキャッシングしたなら、DRAMにページキャッシュとして乗っかってて、SSDアクセスすら必要がないこともありうるだろう。そうなると更に桁違いに時間は縮まる。

[^3]: 現実にはIOキューイングとかでうまいこと行かないことの方が多いのだが、この記事はストレージコントローラーの記事ではないので無視する。

一方、ネットワーク越しにリクエストを飛ばすとなると、1パケットのRTT(いわゆるping)で1桁ミリ秒ならかなり速い回線で、Wi-Fiなどの無線が挟まったらそれだけで +10ms 前後は覚悟する必要がある。そして、帯域幅も理論値で1Gbpsだったとしても、多くの人で共有しているため、実測でそんな速度が出るケースは稀である。さらに、輻輳制御のためにコネクション確立直後はウィンドウサイズが絞られており、最大の帯域幅を出すことはできない。このあたりはKeep-aliveやHTTP/2、次のHTTP/3で緩和する方法があるが、どちらにせよローカルのディスクアクセスなどとは比べ物にならないぐらい遅いのは変わらない。

キャッシュにより通信をしないこと、それがウェブサービスの性能を上げる最も効果的な方法である[^4]。そして、これは単なる性能だけでなく、サービスのスケーラビリティにも大きく貢献する。キャッシュによって一人あたりの通信帯域を抑えるという性能の定数倍改善という効果はもちろん、CDNなどの中間におけるキャッシュを有効活用することで、サーバーへの負荷をユーザー数に比例しない形に抑えることも可能になる。これはシステムの大規模化において非常に大きな手助けとなる。

一方で、適切なキャッシュを用意する仕組みの複雑さ、そのオーバーヘッドなどのデメリットが存在することも忘れてはならない。次節でRESTの話を交えながら、軽くHTTPキャッシュの解説をする。

[^4]: なお、HDDにブラウザのキャッシュを入れてるとちょいちょい逆転する。この場合でもキャッシュはサービス全体のスケーラビリティには貢献しているのだが、ネットワークよりもディスクが遅いとキャッシュしたほうが遅いという地獄を味わうことになる。ええ、私のパソコンです。

# HTTPキャッシュとRESTの制約

今のHTTPキャッシュの仕組みが形になったのは1997年のHTTP 1.1(RFC 2068)である。 **(後述するがこれは古い。参考程度に。)**

https://tools.ietf.org/html/rfc2068

まず注目してほしいのは筆者である。筆頭著者として "R. Fielding" という名があるはずだ。そう、RESTの提案者のFielding氏である。そして時期にも注目してほしい。RESTの提案の直前に相当する。Fielding氏は単にウェブのアーキテクチャスタイルとしてのRESTを提案しただけでなく、そのベースとなるHTTPをセットで構築していたのだ。Fielding氏の博士論文6.3でちらっと触れられているが、RESTの論文に書かれていたような効率的なシステムのために、HTTPの仕様を改良していったのだ。キャッシュ関係が1.1で大きく変えられたのもその一環である。書きぶりからしておそらく彼の脳内には将来的に"REST"と名付けられるアーキテクチャスタイルが先にあって、それを実現するためにHTTPを構築していたのだろう。この辺の詳細は本人に聞いてみたいところではある。

ちなみに、余談だがFielding氏はHTTP 1.0の筆者としても、Tim Berners-Lee氏の次に名を連ねている。Fielding氏はRESTの提案者として有名だが、明らかにアーキテクチャスタイルの提案だけでなく、Webのもっと根幹であるHTTPにおいても中心人物と言えるだろう。一言で言えば「偉大」である。

さて、ウェブサービスにおいてHTTPキャッシュが働きうる場所は多くある。ざっくりと図示してみた。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306456/56e841c7-9b5f-d67d-fb78-e1c86cdbb809.png)

サービスの規模やユーザーの環境によって変わってくるが、サーバーとクライアントが1対1だったとしても、複数のキャッシュがその間に存在してる。そして、当たり前のようにユーザーの数が多くなれば、それだけ全体におけるキャッシュの数は多くなる。**ウェブサービスというのは必然的に分散キャッシュシステムになるのである**。高性能でスケーラビリティの高いウェブサービスを実現するには、この分散キャッシュを適切に制御するということが肝になる。

HTTP/1.1におけるキャッシュの仕様はRFC 7234 https://tools.ietf.org/html/rfc7234 という形で文書化されている。先程紹介した RFC 2068 は今では非推奨となって、現在HTTP/1.1は複数のRFCに分冊化されている。キャッシュ解説記事じゃないので細かい話は省くが、サービス側はHTTP応答におけるヘッダにキャッシュの可能性や有効期限、ETagなどのキャッシュ制御の情報を載せることでキャッシュ制御を行う。そして、キャッシュコンポーネント(ブラウザキャッシュなどのこと。普通は単に「キャッシュ」というが、ちょっとややこしいので「キャッシュコンポーネント」と書く)はそれを受けて、レスポンスをストレージなどに格納したり、取り出したりする。

ここでキャッシュコンポーネント1つに注目した構造を図示してみた。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306456/61aa2835-552c-37c1-aa5a-cb17d1f2e7cc.png)

キャッシュコンポーネントを要求を処理する「キャッシュシステム」とキャッシュエントリを管理する「キャッシュストレージ」に分けた。キャッシュシステムはクライアント側コンポーネントからHTTPリクエストを受け取る。(クライアント「側」としたのは相対的な関係を示すため。先の図でいえばサーバーのクライアント側はリバースプロキシであり、リバースプロキシのクライアント側はCDNである。)そして、キャッシュストレージにリクエストを渡し、もしキャッシュエントリがあれば、それに従ってレスポンスをそのまま返したり、サーバー側コンポーネントにHTTPリクエストを投げたりする。

ここで重要なのが**HTTPレスポンスはHTTPリクエストによって決定する前提がある**という点である。「何を当たり前な」と思ったかもしれないが、これは**決して当たり前ではない**。例えばこのQiitaのトップページ https://qiita.com/ を考えよう。そこには「トレンド」という項目が並んでいるはずだ。そこの説明書き通り1日2回12時間おきに更新される。すなわち、**全く同じHTTPリクエストに対して同じHTTPレスポンスが返ってくる時間は最長で12時間**となっている。HTTPリクエストによってHTTPレスポンスが決まっているのではなく、そこに時間という別のパラメータが介在している。もっと極端な例をだせばアクセスカウンターだったり、直前にアクセスされた内容をもとにおすすめを出すページとかを考えよう。Twitterのタイムラインでも良い。これらは**同じHTTPリクエストを投げたとしても、それ以外の情報によってHTTPレスポンスが変化するために、一切キャッシュすることができない**。

RESTの制約の中で「ステートレスであること」というのが存在する理由がここにある。RESTのステートレスの説明には、HTTPリクエストにHTTPレスポンスを生成するのに必要なすべての情報を含むこととある。HTTPキャッシュが適切に機能するために必要なのは、HTTPレスポンスがHTTPリクエストによって決定することであり、もしそうでないならばキャッシュを活用できない。逆に言うと、REST APIと言いながら、キャッシュ制御をしてないなら片手落ちと言える。Modifiabilityなどの他のメリットもあるため全く無駄というわけではないが、RESTスタイルでAPIを構築していて、もし応答がキャッシュできそうなのであれば、ぜひ積極的に検討するべきだろう。特にCDNに適切なキャッシュをさせることはスケーラビリティに大きく貢献する。通信しないことに勝る高速化はない。なお、CDNキャッシュに乗った内容が想定外の要求から参照されて情報漏えいする事例も存在する。漏洩してはならない情報の場合、死ぬほどHTTPキャッシュの仕様に詳しくない限りは private キャッシュに限定することが無難だろう。

さて、先のキャッシュコンポーネントの図で、キャッシュシステムからキャッシュストレージへの要求部分をHTTPリクエストではなく単に「リクエスト」と書いた。これは「内部実装だからHTTPリクエストがそのまま流れるわけではない」とかいう話ではなく、ここでキャッシュストレージにどういう問い合わせをするのかが重要な話になるので、抽象的な書き方にしたのである。

キャッシュストレージはざっくり言えば「リクエスト」をキーにしたKey-Value Store(連想配列、ハッシュなどとも)となっている。具体的な実装は物によって多少の際はあるが、HTTPの仕様としてはキャッシュのキーはHTTPメソッドとURIの組み合わせが用いられる[^5][^7]。これはすなわち、**リクエストボディやリクエストヘッダーはキーに含まれていないことを意味している**。

[^5]: Varyヘッダーで拡張できる。 https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Vary を参照

[^7]: (2021/11/15追記) https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-safe-method-w-body-02/ リクエストボディをキャッシュのキーにするQUERYメソッドが提案されている。このあたりの事情は今後変化していくかもしれない。要ウォッチだ。 https://asnokaze.hatenablog.com/entry/2021/11/09/231858 などもご参照あれ

これは場合によっては面倒な事態を引き起こす可能性がある。例えば Accept-Language ヘッダーで表示言語を変えるウェブサービスを考えよう。そして、CDNやリバースプロキシみたいな複数クライアントで共有するキャッシュの存在を想定しよう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306456/cf163286-b06a-e404-d26b-7624da6de758.png)

上図のように、同じキャッシュを利用する前後のクライアントが同じURIに大して別の言語を Accept-Language をしていすると、前のクライアントのリクエストによってキャッシュされた別の言語のコンテンツが返されてしまう。もちろん、これ自体には回避策があるのだが、ここで重要なことは正常なキャッシュのためには何がキャッシュのキーになっているかを考えないといけない点である。

Fielding氏の論文におけるRESTにおけるURIと性能の関係は間接的な指摘にとどまっている。ただ、URIがその名の由来になったとおり、プロシージャではなくリソースを指し示している事自体がHTTPキャッシュの仕組みの前提となっていることは確かである。RESTというのはRPC(Remote Procedure Call)やRMI(Remote Method Invocation)に対するアンチテーゼとされている。プロシージャ呼び出しは名前の通り遠隔地のサブルーチンを呼び出す仕組みであり、HTTPリクエストとそのレスポンスの関係性に制約をもたせるものではない。URIという識別子をキーにしてキャッシュ可能なリソースを渡すという、HTTPそのものに持たせた機能をなるべく活用する使い方が分散キャッシュたるウェブサービスをよりスケーラブルなものにするだろう。

# キャッシュ無効化(Cache invalidation)

ここまで、キャッシュを取り出す話をしてきた。一方で、一般に**キャッシュの肝はキャッシュの無効化にある**(私見含む)。これは、高い一貫性を要求されるマルチコアプロセッサにおけるキャッシュにおいて特に顕著である。詳細は省くがMOESIのようなキャッシュコヒーレンスプロトコルは、いかにしてキャッシュを必要最小限なだけ無効化するのかで、キャッシュの、そしてシステム全体のパフォーマンスを左右する。これはウェブにおいても共通の原理が成り立つ。キャッシュを可能な限り長い期間温存させつつ、鮮度が落ちる、すなわちキャッシュが無意味化したときに適切にキャッシュストレージからキャッシュエントリを削除することが性能上げる上で重要になる。

ところが、ウェブという分散キャッシュにはちょっと困った特徴がある。序盤の図で青く塗りつぶしたブラウザキャッシュやフォーワードプロキシに対して能動的な関与ができないのである。特にユーザーが多いウェブサービスにおいて、圧倒的に数が多く、動いたときに最も性能が出るのがブラウザキャッシュであるが、サービス側はユーザーからアクセスがあったときにその応答のヘッダにキャッシュ制御に関する情報を混ぜることができるだけで、能動的にキャッシュエントリを削除しに行くことはできない。

そのため、ウェブサービス側が制御可能なHTTPキャッシュの無効化タイミングは以下のようになる。

- 時間経過
- 条件付きリクエスト
- 安全ではないメソッドを利用したクライアントからのHTTPリクエスト

前者2つは仕様を見てもらうとして、アーキテクチャで関係してくるのは最後の「安全ではないメソッド」である。安全なメソッドはRFC 7231でGET、HEAD、OPTIONS、TRACEの4つとされており、それ以外は「安全ではないメソッド」である。要はPOST、PUT、DELETEである。(CONNECTはここでは忘れて)RFC 7234において、これらの安全ではないメソッドのリクエストが成功した場合、そのURIのキャッシュを無効化することがMUSTとなっている。普通にウェブサービスを設計してて、そんな訳のわからん設計にすることはまれかとは思うが、下記の様な流れで操作が行われると、せっかく本来はキャッシュできるはずのコンテンツがうまくキャッシュできない。

1. GET /fuga (長い時間キャッシュ可能)
2. POST /fuga ("GET /fuga"の結果にに変更は加えないが、クリックしたことを通知する、などの要求)
3. GET /fuga (**キャッシュが使われない**)

1.の段階でURIをキーに /fuga がキャッシュされ、2.でその内容をいじっていないにもかかわらず、HTTPの使用により2.の時点でキャッシュが無効化されてしまい、3.のときにはまた要求をサーバーに飛ばすこととなる。これは大変にもったいないことである。ここから**POSTがGETの内容を変更しないのであれば、同じURIを使うべきではない**ということが導かれる。逆に、もしPOSTがGETの内容を変更し得るのであれば、積極的に無効化してもいいだろう。設計難易度は上がるが、適切なキャッシュ無効化が可能であるならば、積極的なキャッシュができる可能性も広がるからだ。

このキャッシュ無効化はRFCにおける仕様上、同じホスト内のリソースに対しては[^6]、余計にやっても良いことになっている。それはある意味当然で、キャッシュにはそのサイズ上限という機構上の制限があるために、無効化するな、と言われてもそんなことはできん！という状況がありえる。一方でこれはRFCに書かれていない手段でキャッシュを無効化しても全く構わないことも意味している。例えば、サービス側で管理しているキャッシュに関しては適切なタイミングで無効化をかけられるのだから、無効化をかけるまで無期限にキャッシュするということも可能である。

[^6]: DoS回避を狙って、無効化してはならない、という場合もある。細かくは https://tools.ietf.org/html/rfc7234#section-4.4 を参照

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306456/021b420f-bda0-eb9c-5deb-f8373ef14bf5.png)

上図はそのアイデアの図示である。まずは通常のCDNのようにGETの応答を複数のエッジキャッシュに横展開して、多くのクライアントからの要求をさばく。その後、2のようにどこかのクライアントがPOSTでリソースに変更を加え、それが成功した場合、1で横展開したキャッシュを今度は逆に横断的に無効化していく。2での無効化の仕組みを前提とするならば、変更が加わりうるコンテンツも1で長期間キャッシュさせてスケーラビリティを確保しつつ、キャッシュの矛盾を最小限に抑えることができる。

このようにウェブサービス側で管理可能なエッジキャッシュ同士が連携して、ある程度の時間内にキャッシュ無効化ができるのであれば、それを前提として長期間動的コンテンツをキャッシュさせるというアグレッシブな分散キャッシュを実現することも可能である。なお、いわゆるCDNの類でこの安全ではないメソッドによるキャッシュ無効化をしている事例がさっと調べた感じだと見当たらなかったので、現実にサービスとしてこの仕組みを動かすのはメリットが薄いのかもしれない。(自分で言っておいてあれだが、これを活用できるウェブサービス設計してくれ、と言われたら逃げたくはなる。)

# RESTが切り開く、かもしれない分散キャッシュの新しい形

序盤で私は「よほどの特殊事情がない限り、POSTの代わりにPUTを使っても性能に寄与しない」と言った。実際、真っ当なHTTP実装を前提とするなら、その2つのメソッドを交換したところで、意味論以上の違いはほぼ無い。PUTだろうがPOSTだろうが、HTTPの仕様としては、サーバーにどんな影響を及ぼすかの厳密な既定は無いからである。ところが、2014年に出されたHTTP/1.1のRFCの改版で、PUTメソッドの説明に怪しげな文章が増えている事がわかる。

https://tools.ietf.org/html/rfc7231#section-4.3.4

文量だけ見てもこれまでの2倍になっており、明らかに前のRFC 2616 https://tools.ietf.org/html/rfc2616#section-9.6 と様子が異なっている。 "HTTP does not define exactly" 以下の段落のように、HTTPとしてPUTメソッドがサーバーにおける挙動を規定することはない、というのは踏襲しながらもPUTの使用法に細かい制限を追加しているのである。例えば以下の文、PUTはキャッシュできないことを前提とするなら、妙な制約である。

> An origin server MUST NOT send a validator header field (Section 7.2), such as an ETag or Last-Modified field, in a successful response to PUT unless the request's representation data was saved without any transformation applied to the body (i.e., the resource's new representation data is identical to the representation data received in the PUT request) and the validator field value reflects the new representation. 

要はPUT送信内容が全くそのままにGET要求に流用できる場合を除き、ETagやLast-Modifiedを送ってはならない、ということなのだが、そもそもPUTは安全ではないメソッドとされておりレスポンスはキャッシュされないため、これらのヘッダをつけたところで、普通の実装では無意味である。また、この続きに条件付きリクエストの話が書かれているのだが、PUTリクエストをキャッシュ可能という話は私の知る限りHTTP関連のRFCには記載がないため、304 Not Modifiedをもらっても仕様の範囲ではどうにもならない。もちろん、JavaScriptからPUTレスポンスヘッダを見てXHRで条件付きリクエストを送り、単にメモリ更新をスキップすることも可能ではあるが、HTTP実装に何かさせるわけでもないすごい中途半端な仕様である。そんなものは、HTTPではなくアプリケーションとしてやればいい話とも言える。

PUTが本当に目指していた意味はコンテンツの置き換えである。要は単純なファイルアップロードである。でも、これまでHTTPはそれを強制してこなかった。あくまで意味の違いという形にとどめておいたのだ。それゆえに、Fielding氏の博士論文6.3.3.2 Write-through Cachingには以下のような文章が書かれている。

> HTTP does not support write-back caching. An HTTP cache cannot assume that what gets written through it is the same as what would be retrievable from a subsequent request for that resource, and thus it cannot cache a PUT request body and reuse it for a later GET response. There are two reasons for this rule: 1) metadata might be generated behind-the-scenes, and 2) access control on later GET requests cannot be determined from the PUT request. However, since write actions using the Web are extremely rare, the lack of write-back caching does not have a significant impact on performance.

Fielding氏はHTTPキャッシュの仕組みでは、PUTの内容をGETのキャッシュとして流用することはできないとしている。その理由として、サーバー側でメタデータを生成し得る、PUTした内容のアクセス制御ができないことを上げている。そして、もし流用できたとしても、ウェブでの書き込み操作は非常に少ないため性能への影響は小さいとしている。仕様としては現在もPUTをGETに流用はできないので変わっていないし、その理由ももっともである。だが、「ウェブでの書き込み操作は非常に少ない」という前提はおそらくこの20年間で覆ったといっていいだろう。例えば、このQiitaの記事を書いている間、ほぼ毎秒と言っていいほどプレビュー用のPOSTが飛んでいるし、SNSでも書き込みワークロードは多くある。画像のアップロードも頻繁に行われているし、ユーザーが明示的に投稿などをしなくても、ポチポチ操作するたびに多くの情報がサービスに送信され、そして書き込まれる。この様なウェブの使い方はもしかしたら2000年時点では想像がつかなかったのかもしれない。

先の文章でFielding氏があげた2点の問題点は、それこそ制約を設ければ容易に克服できるはずの問題である。メタデータ生成に関しては、現行HTTP/1.1のように完全に一致してヘッダの変更もない場合のみキャッシュ可能なヘッダを付けられるように縛ってしまえばよく、アクセス制御も公開されているものだけキャッシュするようにサーバーが応答すればいいだけのことである。私はなぜ2000年当時のFielding氏がこれを克服して、PUTリクエストの流用をHTTPに組み込まなかったか、正確なところは内心の問題なので承知しきれないところはあるが、2014年改版を見ると状況の変化に応じて気が変わったのではないかと推察する。互換性を保つ必要があるので、いきなりキャッシュ可能とするわけには行かないであるが、互換性を破壊しない制約は可能な限り増やしておくことで理想に近づけようということなのかもしれない。

だが、このPUTの中身をキャッシュしてGETで利用というのを、ウェブサービス側が管理する分散キャッシュ同士でやる分には、ユーザー側から見てなんの問題もない。つまり、CDN的な位置にいるキャッシュがPUTを受け取って、サーバーがこれを受理かつキャッシュ可能としたら、そのままその内容を他のエッジキャッシュにコピーして展開していく。図示すると下のようになる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306456/5cf97de7-cef4-4622-8fde-faf42c4edd55.png)

これをやると、PUTでコンテンツ追加時にサーバーとCDN(的な何か)の通信がざっくり半分になる。もうここまで機能もりもりになるとCDNという名前と乖離してくるが、HTTPメソッドに制約をもたせることで、高度なキャッシュ制御が可能であることがわかる。もちろん、PUTを使わなくたって、すべてのエッジキャッシュを自前でカスタムしたサーバーで揃えれば同じことはできるのだが、これがHTTPという標準技術でできるとなれば、今のCDNのように大規模化してそのリソースを柔軟に売ることができる。

# まとめ

Fielding氏が博士論文で指摘しているように、HTTPは単なるEnd-to-Endのトランスポートプロトコルではなく、レイヤーを構成する仲介者が中身を解釈できるプロトコルである。そのメソッドには意味があり、様々な仲介者によって解釈され、そしてそれらのキャッシュによりシステム全体のスケーラビリティを得ることができる。RESTはそのスケーラビリティを稼ぐネットワークアプリケーションのアーキテクチャスタイルであり、そしてそれを実現するためにHTTP/1.1が作られた。RESTの発想は未来のHTTPの道標にもなるだろう。