状態更新・状態遷移の難しさ

キャッシュでユーザーが損をする図

コンピュータのシステムの話で、単にキャッシュってめんどい、という話だが、情報科学を学んだときにあまり深刻に考えていなかったが重要な問題だと思うので述べる。

状態更新・状態遷移のシチュエーションと最新状態の参照について

データソースAと、Aを活用する(たとえば表示する)アプリケーションC
があり、ユーザーはCをある頻度で利用するとする。(たとえばツイートの蓄積されているサーバがAで、WEB版ツイッターのインターフェースがC)

もしいくらでもAへのアクセスが可能であれば、Cへのアクセスの度に随時Aへ取得リクエストをかければよい話だが、一般的にAは例えば外部のAPIであったり、DBサーバだったり、ファイルストアだったり計算サーバだったりする。はたして”いくらでもAへのアクセスが可能”だろうか。実はそのような場面は結構少ない。CとAが同じ物理サーバでかつ、参照コストがかからない(高速アクセス可能なローカルストレージを参照など)場合などであれば問題ないが、そうでない場合が多い。

 

そして至極当然のことだが、これらの対応のために、データソースAが返すべきレスポンスを蓄積して高速・低コストに返す「キャッシュB」が生まれることになる。キャッシュBを介すことで、大量アクセスに対応できたり高速にレスポンスできたり、そもそも静的に保存しているため計算コストが安く済むなどの利点がある。

 

しかし、キャッシュを導入するということは、キャッシュ保持時間=更新頻度というものがついて回る。つまり”常に最新の値を取得できる”ということが無くなり、情報最新性への妥協であったり、時差を考慮したシステムの開発が必要になる。

概念として、そして巨大サービスにおいてそのようなものがあるのは学生のころから知ってはいた(DNSWebブラウザのキャッシュなど)が、いざシステムを開発・運用するとなったときに、この問題は至る所で壁となる。

更新頻度≒更新遅延

一般的にデータの受け渡し・状態遷移の伝播は1段階ではない。多段階になり、そのホップ数が多いほど、更新頻度が低くなる。最新データの取得を希望する場合、更新遅延が長くなるともいえる。今回は主にこの、最新データの取得を希望する場合・リアルタイム性が求められるサービスをつくっている立場から述べる。


複数のホップ数があることをかみ砕くと、

1ホップ目、2ホップ目、の更新頻度がT1,T2,...Tn であった場合、最終的な最大の更新遅延はT1+T2+...+Tnとなる。最小の更新遅延は max(T1,T2,...,Tn)となり大したことないが、タイミングを正確に合わせない限り、最大の更新遅延を引くことは多々ある。ご覧の通りホップ数が増えるごとに最大の更新遅延はどんどん伸び、ユーザー体験は損なわれていく。(一方サーバ管理者はサーバ機能の細分化ができてうれしいことが多い)たとえば最新のニュース記事を見たいのに20分後にならないと見られない、というようなことが起きる。

 

どのような場合に、更新頻度が長くなるのか、いくつか例を挙げる。

例1 外部RSSを参照して、記事の情報を取得する

取得先RSSは静的なファイルではなく動的に生成する場合、レスポンスに時間がかかるし、何より先方のサーバに著しく負担をかけるためアクセス頻度を下げろという要求をされていることもある。
→更新遅延はあっても更新頻度は確保できる という場合もあるが、それすら妨げられる。→先方のシステムが悪いともいえる。

例2 自社の記事管理CMSから、最新の記事5件を取得する

コンテンツ蓄積とともにDBは肥大化していくことや、低頻度のアクセスを想定した廉価なクラウドサービスを利用していることが影響し、高頻度でのアクセスは避けたい・低廉な仮想サーバのためレスポンスに数秒かかることが多々ある。それらの理由からバッファをもって5分単位での更新にするなど。
→DBの設計の難しさの話と、オンプレ至上主義の話につながるが、現実的に出会うことの多い問題である。そして安易に5分単位で更新時間を決めることの是非も・・

例3 いわゆるふつうのCDN

CDNはキャッシュそのものであるから当然ではあるが、CDNを介したデータ取得ということは、TTLが決められており、その事前設定した時間だけ更新頻度が遅くなる。ただ、CDNを使う目的はあくまでアクセスの分散であるから、別に更新頻度を下げる・古い情報の露出を許容しているからというわけではない。
→例えば誤った情報の修正・ネガティブキャッシュの存在など、キャッシュ機能が運用の邪魔をすることがあり、その場合キャッシュの開放(パージ)や、インデックスファイルのみTTLを短くするなどの方法をとるが、リアルタイム性を求めるサービスとCDNの相性の悪さがあることは主張したい。

例4 大きなファイルを取得する・計算する必要がある場合

ちょっと毛色は違うが、大きなファイルを名前を変更せずに更新する事を考えると、リモートサーバから取得している途中はファイルとして破綻しているので、使い物にならない。なので最近のウェブブラウザの挙動でも見られるが、一時ファイルとして別名でダウンロードを行い、ファイル完成後にリネームを行い上書きすることが必要になる。ダウンロードにかかる時間が遅延時間に直結するのはもちろんのこと、この「ダウンロード中のアクセス」を正しく対応しなければ、システムのエラーにつながるのも注意したい。一方でサーバ(Linuxなど)の「リネーム」は”十分一瞬”に終了するからユーザーに影響を与えることはない、のは興味深い。”リネーム中にアクセスしたからファイル破損扱いになった!”ということは起きうるのかもしれないが、十分に無視できる事象といえる。


◆私の驚き

単に情報=データを”すぐに”別の場所=サーバから持ってくるというのがこんなにも困難なことだったのかということに驚く。そしてそれは世の中ほとんどすべてのサービスに影響するものになるから、もっとこれに関する学問やアーキテクチャの議論があっても良いのではと思う。いや、当然すでにあるのだろうけれども少なくとも学生の時にこんなこと考えもしなかった。

 

◆色々経験を経ての教訓

・システム(データフロー)をシンプルにすること。役割などの区別から、多段階での処理をしがちである。一般的に複雑な問題は分割せよと言われている。しかし更新遅延を考えるとそうも言ってられない。一番問題を簡単化するのは、データフローをシンプルにすること。

・あらゆる段階において高速なレスポンスを求めること。普通のサービスで異常が起きても無いのに数秒もかけてレスポンスしているのは異常だし、避けるべき。細かなアルゴリズムの工夫や無駄な処理の削減できることは多々ある。動画像処理やAIなどで計算時間がかかるものもあるが、かなり考慮したシステム設計が必要になる。

・キャッシュサーバの保持時間はあまやかしてはいけない
”キャッシュサーバだから保持時間が長いのは当然でしょ笑”というような甘えた設計では、多段階でデータの受け渡しをすることでかなりのボトルネックになってしまう場合がある。しっかりと処理能力を鑑みて、CPUにはがんばってもらって、できるだけ短い保持時間にするべきである。一見保持時間がいくら長くても良いだろうと思っていても、システムの遠いところで影響を与えることがある。

 

◆おわりに

かなり自分の関わっているプロダクト(コンテンツ管理とその表示WEBインターフェース、他社APIとの連携 など)を基準に偏った内容で、前提設定が足りない部分が多々あると思うが、具体例などはいくらでも出せるので、気になったことがあれば答えられる。