続・目指すのはぶっちぎりの速さ! HTML5版 Cacoo で刷新されたデータアーキテクチャを紐解く

こんにちは。Cacooの平山です。現在、僕たち Cacoo チームは脱Flash・ HTML5 化に向けて最終の追い込みの真っ最中です。そんな中ではありますが、前回の「ぶっちぎり」記事では、HTML5化に向けた描画技術としてSVGを選択したことをお伝えしました。今回は、HTML5版のもうひとつの改善ポイント「データ面での刷新」を紹介したいと思います。

Cacoo のデータ形式や構造の刷新・JSONフォーマットへの移行

HTML5版では、描画にSVGを採用するという変更だけではなく、抜本的なデータ形式や構造の刷新も行いました。これには以下3点の理由があります。

  1. Flash版ではAMFというFlashに特化したデータフォーマットを採用している
  2. 図に含まれるデータ量に応じて、パフォーマンスが低下しやすい構造である
  3. サーバー内でデータを処理しにくい

1点目はFlashから脱却するにあたり対応必須な要素です。2点目は、やはりぶっちぎりの速さを目指す上での大きな障害となります。3点目は、後述しますがパフォーマンス面というよりも、将来的な展望に関係するものです。

Flash版のデータ構造

それでは、図のデータがどう変わったのかを見ていきましょう。まずはFlash版です。

cacoo-html5Flash版データ構造外観

Flash版では、AMF (Action Message Format) というFlashでの利用に特化したバイナリフォーマットで図のデータを保持しています。Flash版では、ひとつの図に含まれるすべてのシートや配置されている要素のほとんどが、単一のバイナリデータとして保持されています。

また、一つひとつの図の編集内容は、コマンドという形で、それも1操作1AMFバイナリとして表現され、逐次サーバーのデータベースに保持されます。図を保存するときに、図のAMFバイナリはサーバーのデータベースに更新して保持されるのですが、保存後に行った編集操作は、そのコマンドのAMFバイナリを取得し、再生させることで最後に編集していた状態に復帰させています。

HTML5版のデータ構造

次に、HTML5版です。

cacoo-html5HTML5版データ構造外観

HTML5版では、全体的にJSONフォーマットで図のデータを表現しています。さらに、図の内容を別々のJSONデータに分解して保持しています。図のタイトルなど、全体の概要をもつ「Diagram」、図に含まれる各シートの概要をもつ「Sheet」、各シートに配置されたステンシルなどそれぞれの要素を表す「Shape」です。単一のバイナリにほとんどすべての図の内容を含めているFlash版とは対照的に、細かなデータの組み合わせで一つの図を表現するように変更しています。また、図を編集したときの変更内容は、直接そのJSONデータでサーバーにあるデータベースを更新します。

Flash版とHTML5版のデータ構造の比較

Flash版とHTML5版、新旧のデータ構造を比較した図を示します。

cacoo-html5Flash版とHTML5版のデータ構造比較

前述のとおり、Flash版では一つひとつの要素(Shape)からそれらを集めたシート(Page)、また複数のシートを含んだ図の全体(Book)という入れ子になったデータを、ひとつのAMFバイナリとして表現する構造を持っています。一方でHTML5版では、図、シート、ステンシルなどの要素はすべてが細分化されたJSONデータで構成されており、それぞれはユニークなID(uid)で関連づけられています。

例えば、「何かステンシルをひとつ削除する」といった操作を行う場合、Flash版では図のデータを丸ごと書き換える必要があり、編集操作に合わせて逐次データを更新するということは現実的ではありません。そのため、「ステンシルの削除」といった編集操作自体をコマンドという形でまとめ、その操作を再生することで最終の編集操作に復帰させるという処理が必要になります。

一方、HTML5版では、大まかには削除対象のステンシルのJSONデータを削除し、それを持つシートからuidの関連付けを解除すれば済みます。このように、編集内容に合わせて直接データ構造を逐次書き換えることが可能になり、編集操作を再生させるような必要性が無くなりました。

さらに、Flash版では図に含まれるシート枚数が増えるに従い扱うデータサイズも肥大化していきます。一方で、HTML5版ではシート単位でデータを扱いやすく、図に含まれるシート枚数にあまり依存せず、編集画面のパフォーマンスを維持することができます。

以上、このようなデータ構造の変更により、以下のようなパフォーマンス上の利点が得られるようになりました。

  • 図の操作をコマンドとして表現することが不要になり、編集再開時の待ち時間が短縮できる
  • 編集中のシートに関連するデータだけを選択的に扱うことができるため、編集時に必要なデータ容量を削減できる。また、編集中に取り扱うデータ容量も少なくて済み、メモリ消費量も抑えられる

なぜデータフォーマットとしてJSONを採用したのか?

HTML5版ではデータフォーマットとしてJSONを採用していますが、ご存知の通りJSONはテキストでの表現であり、Flash版で採用しているバイナリ形式よりもデータサイズやパフォーマンス効率の面で劣ると思われる方もいらっしゃると思います。

僕たちも開発初期段階でMessagePackなどバイナリ形式のフォーマットを検討したのですが、以下のような理由でJSONの利用に落ち着いています。

  • JavaScriptネイティブな標準的なバイナリ形式が存在しない。よって、バイナリデータをJavaScriptでパースする必要性があり、その処理がオーバーヘッドになる。
  • 単純なデータ転送量ではバイナリ形式が有利だが、HTTPによる通信はgzipで圧縮可能であるMessagePackと、gzip圧縮したJSONデータの伝送量を比較したが、MessagePackにそれほど大きな利点は見られなかった
  • CacooではデータベースにPostgreSQLを利用しているが、PostgreSQLにJSONを取り扱う機能があり、バイナリでデータを格納するよりも今後の拡張性やメンテナンス面での利便性が得られる思われた

もし、将来JavaScriptネイティブなバイナリ形式が標準化されれば、そのデータフォーマットを採用する可能性はありますが、現時点ではデータフォーマットとしてJSONが適していると考えています。

IndexedDBによるキャッシュデータの有効活用

さて、HTML5版ではもう一歩パフォーマンスを改善するために、HTML5の要素技術であるIndexedDBも活用しています。おおざっぱに言ってしまうと、IndexedDBはブラウザ内にあるデータベースで、そこに任意のデータを保持し再利用することができる技術です。ここからは、HTML5版でどのようにIndexedDBを活用しているかを見ていきましょう。

差分データの取得で編集を開始

HTML5版では、図のデータをIndexedDBにも保持することで、サーバーから取得するデータを最小限に抑えるようにしています。具体的には、シートごとの単位で、IndexedDBとサーバーにあるデータベースの差分のみを取得し、編集画面に反映するようにしています。もし、前回の編集時から誰もそのシートを更新していなかったとしたら、原則、サーバーからほとんど何のデータも取得しないで編集を再開できます。この、差分を取得するという処理も、先ほど説明したJSONデータで細分化させるというデータ構造の変更により可能になりました。

cacoo-html5シーケンス番号を元にした差分データ取得

図を構成する各JSONデータには、データ更新を行う際に連番の「シーケンス番号」が付与されます。IndexedDBにも最終のシーケンス番号が保持されており、その番号以降のJSONデータが存在する場合、それを更新された差分データとして取得します。この図で例えると、あなたが10番目までの図の編集を行った後、別のユーザーが同じ図を11番目~15番目の操作を行った状態を表しています。あなたが編集を再開したときには、残りの11から15の更新データのみを取得すれば、共同編集者の更新内容を取り込んだことになります。

消費メモリ量の最適化

さらに、IndexedDBを活用することにより、編集中に消費するPCのメモリ容量の最適化も行っています。Cacooの図の編集では、背景シートなどは除き、現在編集中でないシートの情報は不要です。Flash版では、単一のAMFバイナリに図のほとんどすべてのデータを保持しているため、編集開始時には、編集中でないすべてのシートも含めてメモリに展開しています。これが、Flash版で図に含まれるシート枚数などデータ量が増えるとパフォーマンスが低下しがちである一つの要因です。この問題を回避するため、HTML5版では現在編集中のシートに関連するデータのみメモリに展開し、他はIndexedDBに保持させています。こうすることで、シート枚数によらずパフォーマンスを維持できるように工夫しています。

IndexedDBを利用する上でのユーザーインタフェース側の工夫 ~ 体感速度を損なわないデータ更新タイミングのツボ

IndexedDBを利用することで、データの取り扱いが多層的になるのですが、HTML5版はその点に関しても体感的なパフォーマンスを低下させないように工夫をしています。

HTML5版では、図の編集時にサーバーのデータベースの更新を逐次行っているという説明がありましたが、通常のWebアプリケーションであれば次のシーケンス図のような流れを想像するかもしれません。

cacoo-html5標準的なWebアプリケーションフロー

このシーケンス図では、サーバーへのデータの更新が成功してから編集画面に反映させるような流れになっています。こうすることでデータの一貫性は保ちやすくなるのですが、反対に画面へのフィードバックがされるまでに長い時間がかかってしまいます。Cacooでは図の編集という細かな操作が連続することを想定する必要があり、フィードバックの遅延は体感的なパフォーマンスに悪影響を及ぼします。

そこで、HTML5版では次のシーケンス図のような流れで処理を行っています。

cacoo-html5HTML5版でのUIとデータ処理フロー

HTML5版では、IndexedDBを直接扱わずに、それをラップするようなメモリキャッシュの領域を持たせて、そこと編集機能のデータのやり取りを行うようにしています。

編集内容は一旦メモリキャッシュに書き込まれ、即時画面にも反映されます。その後、サーバーのデータベースの更新を行い、それが問題なく成功すればキャッシュからIndexedDBに書き込む、という流れをとっています。こうすることで、編集操作が画面にフィードバックされるまでの時間を極力短縮し、体感的なパフォーマンスを低下させないように工夫しています。開発段階ではサーバーのデータベースの書き込みまでにサーバーとの通信も含め、0.5秒前後必要な場合がありましたが、編集の操作のたび逐次待たされると体感的なパフォーマンスはかなり悪化してしまうことが分かりました。それを回避するという部分も含め、このような処理の流れに改善させました。

ただし、ここで説明したような処理の工夫は体感的なパフォーマンスの面では必須なのですが、データを多段的に扱う必要があり複雑度が増します。特に、クライアント(ブラウザのIndexedDB)とサーバーのデータベースとの整合性を担保するのに十分な注意を払う必要があります。さらに、Cacooでは一つの図を複数人で同時に編集できる共同編集の機能もあるため、複数の編集内容が競合する場合があり、画面に反映済みの状態やメモリキャッシュの内容などを戻す必要があります。メモリキャッシュ部分を使って、IndexedDBへの複数の更新をトランザクションのようにまとめて処理できるようにするなどの工夫はしていますが、このあたりは非常に開発の難易度が高い部分になっています。

Cacoo の将来的な展望

最後に、ここで紹介したデータアーキテクチャに関連するCacooの将来的な展望についてまとめます。

今まで実現が難しかった機能の実装

データを細分化して扱うことができるようになったため、例えば複数の図に含まれるシートをひとつの図にまとめたり、逆にひとつの図に含まれるシートごとに複数の図に分けたりといった機能を実装できる可能性があります。

図の内容を操作するAPIの提供

図のデータを細分化し、その単位で直接書き換えることができるようになったので、図の内容を操作するAPIを提供できる可能性があります。例えば、最初から特定の画像を取り込んだ状態の図を新規で作成したり、データベースのDDLからER図を作成したりといったことができるようになるかもしれません。

共同編集をどうAPIでもサポートするか、といった問題があるため容易ではありませんが、将来図の作成や更新が可能なAPIを提供できる可能性があります。

オフラインへの対応

実はIndexedDBにデータを保持するという構造は、将来的な”オフライン編集機能”実現のための布石でもあります。どのようにオフライン対応を行っていくかの具体的な検討はまだですが、すでにHTML5版はIndexedDBからのデータの読み込みが前提になっているので、以前よりも実現性はぐっと高まっています。

以上、HTML5への移行はデータの面でも困難な挑戦なのですが、僕たちは「ぶっちぎりの速さ」を求め、この難関に打ち勝つべく、現在必死で開発を進めています!HTML5版の登場まで、今しばらくお待ちください!

今回の記事のように、クライアント側の技術に興味を持たれる方はもちろん、Cacooをはじめ様々な分野で活躍できるエンジニアを大々募集中です!Cacooの開発体制について興味のある方は、こちらの取材記事でご紹介しているのでぜひご覧ください。

開発メンバー募集中

より良いチームワークを生み出す

チームの創造力を高めるコラボレーションツール

製品をみる