時間割による画面切り替え

ストーリー

ある時刻になったら、クライアントの画面や動作モードを一斉に切り替えたい。たとえば、サッカーの試合前にスターティングメンバーの情報を表示し、キックオフと同時に応援するインターフェースに切り替える。あるいは、CM になったら、スポンサーの画像を表示する。

ただし、あらかじめ、画面切り替えの時刻が決まっている。

_images/sync-schedule-concept.gif

暗黙の要求

ブロードキャストのインフラを持っている組織が発注元の場合、全クライアントの画面を同時に切り替わることを、非常に容易であると考えていることがある。

解決すべき課題

おおまかな体験を確認するためのラピッド・プロトタイピングでは、クライアントが定期的にサーバーに対してポーリングするのがてっとり早い。「今、このモードにします」というのをその場その場で変更できる。

しかし本番でポーリングすると、単位時間あたりのアクセス数が、ユーザー数 × ポーリング頻度に比例する。10万クライアントが毎秒アクセスすると、秒間10万リクエストが発生し、かつ、ほとんどのリクエストは無駄に終わる。

通信の頻度を下げつつ、特定の時刻にイベントを発生させるしくみとして、あらかじめ画面切り替えの時間割をクライアントが持っておく方法がある。この場合、時間割を取得したあとは、クライアントとサーバーの間で通信が不要になる。

すると、全クライアントの時計を合わせられるか、が課題になる。10時00分00秒に画面切り替えするという情報があっても、各クライアントの時計が大きくずれると、切り替えのタイミングも大きくずれてしまう。

解決策

  • サーバーの現在時刻を取得するAPI用意して、クライアントとサーバーで時刻を合わせる。
  • 時間割にもとづいて画面を切り替える。

時刻合わせ

サーバーに、現在時刻を返すAPIを用意する。複数のサーバーを用意する場合は、NTP で時刻を合わせておく。レスポンスはなんでもよいが、JavaScript で扱いやすいように JSON で {"currentTime": "2016-10-04T15:05:00+0900"} あるいは {"currentTime": 1475561178} (epoch時刻からの経過秒数) などを返す。

ただし通信回線によっては通信かかる時間が無視できないので、少しだけ補正をしておく。ここではウェブブラウザーを使っていることを仮定しているので、HTTPレイヤーでの通信だけを考えると、時刻取得APIを呼び出すときのシーケンスは以下のようになる。

  1. 時刻 t0 に、クライアントが通信を開始する。
  2. 時刻 t1 に、サーバーが T1 を返す。
  3. 時刻 t2 に、クライアントガ受信を完了する。
_images/sync-schedule-timeline.png

クライアントの時計時刻 t1 と、サーバーの時計時刻 T1 の差が、クライアントとサーバーの時計時刻のずれある。しかしクライアントは t1 を知ることができない。

そこで、 (t0 + t2) / 2 = t1 であると仮定し、

dt = t1 - T1 = ((t0 + t2) / 2) - T1

を得る。以降、時間割の 10:00:00 を、クライアントは自分自身の時計時刻 – たとえば 10:00:05 – に補正して動作させる。

切り替え

時間割は JSON ファイルや index.html に埋め込むなどして、ウェブサーバーで配信する。全クライアントが同じデータを受け取るので、使えるなら他のコンテンツと一緒に CDN を利用することで、管理しているサーバーへのトラフィックを抑制できる。

補正された現在時刻のと、画面切り替えの時間割が分かったら、あとは時間がきたときに動かせばい。SetTimeout でもよいし、SetInterval で定期的に確認するでもよい。

注意点

フロントエンド開発者の負担

この方法は、クライアント側で細かい機能実装と調整が多いことを踏まえて、計画する必要がある。

サーバー側は、実装する機能が少ない。いざとなれば、本番時に時刻取得APIのサーバー数を増やすことで、パフォーマンスを得られる。

API 呼び出しのスパイク

イベント開始と同時に、クライアントのアクセスが集中する場合、コンテンツのロードと時刻取得API呼び出しが同時に集中する。このスパイクに対応するキャパシティがないと、リクエスト処理をするまでに待ち時間が発生し、 t1-t0t2-t1 が大きくかけ離れてしまう。

状況にもよるけれど、

  • 安いサーバーをたくさん立てる
  • 時刻取得APIと、レスポンスに時間がかかるAPI(DBアクセスがあるとか)とで、サーバーを分離する。
  • キャッシュして応答を速くする(そのずれを補正する)
  • クライアントガ、時刻取得APIに、アクセスする時間をずらす。たとえばランダムに0〜10秒待ってからアクセスすれば、スパイクの規模が、おおざっぱに 1/10 になる。
  • 何度かアクセスして、補正する。

などの対応ができる。後述するが、1〜2秒くらいまで誤差を抑えられれば、それ以上がんばっても報われない。

向いているコンテンツ

ドラマや録画のバラエティは、事前に切り替え時刻が分かるのでやりやすい。録画と生放送を融合している場合も、切り替えのタイミングがあらかじめ決まっていることが多いので適用できる。

いつ画面が切り替わるか、イベント開始時に分からない場合は使えない。

時間割は CDN に置いておく場合でも、寿命を短めにしておく。当日まで正確な試合開始の時刻が分からないこともある。マスメディアは、アイドルグループが解散するニュース速報を、オリンピックの試合中の画面に被せてくるような種類のメディアである。

時間分解能

サブ秒単位で合わせたいとか思うだろうけど、残念ながら合わない。理由のひとつは時刻合わせの方法の限界、もうひとつはイベントコンテンツ配信の限界。

単純な時刻合わせでは、通信の誤差や、処理の誤差が含まれる。処理の誤差を綿密に計算することはできても、通信の遅れをきれいに補正できない。コンテンツの特性上 NTP のように、時間をかけて時刻合わせをする時間的余裕は与えられない。

デジタル放送テレビでは、放送局から出た放送波が、自宅のテレビに到着し、テレビがデコードをして映像として表示するまで数秒かかる。定常状態に入ってからはパイプライン化されるので遅れないけれど、テレビを付けた瞬間、チャンネルを変えた瞬間から実際に映像が出るまでに遅れがあり、その遅れは機種によって異なる。

開発環境

時間割というグローバルなデータで、クライアントの状態が決定される。そのため、複数のクライアント開発者がいる場合、開発者ごとに時間割を持てるような環境やしくみが必要である。

また、時間割を簡単に変更できるようなツールを用意する。時間割が複雑なJSON構造をしていたり、データが直感的ではない(epochからの秒数など)と、クライアントの開発者が手作業で変更しにくい。特に画面切り替えのテストでは、時間割の変更が頻繁に発生する。

ブラウザーのタイマー

スマートフォンやタブレットのウェブブラウザーは、一旦アクティブではなくなると、タイマーが止まることがある。残念ながら直接解決することはできない。画面を触ったときに、タイマーが止まっているかチェックするようなしくみを使うことがある。そのために、ときどき画面をタップしたくなるような演出があるとよい。

タイムゾーン

内部では時刻処理に常に UTC を使う。国内のサービスなら人間の目に見えるところ、たとえば時間割の作成/閲覧画面や、JSONのデータ(デバッグするとき見る)は JST を使ってもよいけれど、計算するときには UTC を使う。クライアントのタイムゾーン設定は分からないからだ。