シリーズ判定最前線

10 日ほど前から amakan books の再設計に着手しており、最近ようやく実装を終えた。いまデータ移行処理を実行している最中で、20時間ぐらい掛かるのでゆっくり待ちながらこの記事を書いている。変更理由についておさらいするという意味も含めて、変更内容と意思決定の背景について書き記しておく。

テーブルの変更

旧 DB スキーマ では以下のようなテーブル群を利用していた。

  • amazon_product_link
  • amazon_product_responses
  • author_product_list_memberships
  • author_product_memberships
  • authors
  • evaluations
  • product_links
  • product_lists
  • products

新 DB スキーマ では以下のようなテーブル群を利用するようになった。

  • book_authors
  • book_likes
  • book_product_amazon_links
  • book_product_amazon_sources
  • book_products
  • book_tastes
  • book_work_subscriptions
  • book_works
  • books

amakan anime の登場により Generic な名前が利用しづらくなったので、これを機に book という prefix を付けて分かりやすくした。amakan anime では一連のシリーズを video_works と呼ぶことにしたので、amakan books でもそれに対応して、シリーズを表すテーブルを product_lists から book_works に変更している。

商品と書籍の区別

product という言葉が表すように、以前までのテーブル設計では「商品」と「書籍」の区別が付けられておらず、この歪みが書籍の重複やシリーズ判定ミスなどを招いていた。products には、紙のコミックの商品情報とKindle版の商品情報が統合されて1レコードとして表現されていたので、実質的には Productは関連商品群を表現していた。

新設計では、シリーズ・書籍・商品・データソースの概念がそれぞれ明確に分離されている。例えば「キノの旅 1巻」は、以下のようなデータで表現される。

  • キノの旅というシリーズ (book_works)
  • 1巻を表す書籍 (books)
  • 文庫版、Kindle版それぞれの商品 (book_products)
  • それぞれのデータソース (book_product_amazon_sources)

books にはもはや書籍名は含まれておらず、これはシリーズ名を元に計算される。また books にはもはや発売日は含まれておらず、個々の商品ごとに発売日が記録されている。

シリーズとは何か

旧設計では、シリーズは以下のようなルールを持っていた。

  • シリーズに所属しない書籍も存在する
  • 書籍のシリーズは自身の書籍名とカテゴリと DB の状態から決定される

DB の状態というのは、具体的には「同じカテゴリ内に2冊以上シリーズ名が部分一致する書籍が存在すればシリーズとして成立させる」というルールによるもの。これが新設計では、シリーズは以下のようなルールになる。

  • 全ての書籍が何らかのシリーズに所属する
  • 書籍のシリーズは自身の書籍名とカテゴリからのみ決定される

DB の状態に関わらず、書籍単体からシリーズ名を決定できるので強い。また全ての書籍がシリーズに所属する構造になるので、UI 設計や新刊リストの計算が非常にやりやすくなる。downside として、実態としてシリーズ化されていない本を扱う場合には少し違和感が出るようになってしまったかもしれない。しかしながら、

  • amakan books のコアな機能は、気になるシリーズの新刊を通知する機能である
  • amakan books のメインターゲットは、(シリーズ化されやすい) コミックやライトノベルである

ということを考慮して、今回のような設計に至った。

シリーズの判定方法

旧設計では、以下のようなルールでシリーズが決定されていた。

  1. 新しく書籍が追加されたとき、書籍名からシリーズ名を抽出する
  2. 同じカテゴリに同名のシリーズが存在していれば、その書籍のシリーズを決定する
  3. 同名のシリーズが存在しなかった場合、全ての書籍をシリーズ名の部分一致で検索する
  4. 一致した書籍が既にシリーズに所属している場合、書籍名とシリーズ名の編集距離が近い方を選択する
  5. シリーズ名と対応する書籍が 2 件以上存在する場合、それらの書籍のシリーズを決定する

この方法には、書籍の登録順序次第で最終的なシリーズ判定結果が変わってしまうという問題がある。 例えば「ソードアート・オンライン19 ムーン・クレイドル (電撃文庫)」からは、現状「ソードアート・オンライン」というシリーズ名を抽出することが出来ない。しかし、その前の巻は「ソードアート・オンライン (18) アリシゼーション・ラスティング」という表記であるため、この商品名からは「ソードアート・オンライン」というシリーズ名を抽出できる。そのため、19巻と18巻のどちらが後に DB に登録されるかで、シリーズの内容が変わってしまう。

これは説明のための最も簡単な例で、実際には様々な問題が存在している。

  • 書籍を DB に登録するとき以外にもシリーズ再判定ロジックが実行される
  • シリーズ名が汎用的な一般名詞であるが故に無関係な書籍に一致してしまう (例: 「物語」)
  • 2つのシリーズ名が入れ子構造になっている場合に、小さい側のシリーズに含められなかったものが大きい側のシリーズに間違って入ってしまう (例: 「ソードアート・オンライン」と「ソードアート・オンライン プログレッシブ」)

新設計では、以下のようなルールでシリーズが決定される。

  1. 新しく書籍が追加されたとき、書籍名からシリーズ名を抽出する
  2. 書籍のカテゴリと抽出されたシリーズ名から、シリーズを決定する

非常にシンプルになったが、旧設計と比べるとシリーズ判定ミスに対して少し弱くなった。この downside を補うための材料として、

  • シリーズ判定器の amakanize の精度が十分に向上してきた
  • シリーズ決定ルールの簡素化により、利用者にシリーズを修正してもらう設計が可能になった

という反論材料が用意できたので、新設計への移行に踏み切れたという背景がある。

書籍の同一性判定

f:id:r7kamura:20170330075749p:plain
f:id:r7kamura:20170330075749p:plain

例えば上図は書籍の同一性判定に失敗している様子で、食戟のソーマの23巻に対して2つのレコードが存在してしまっている。Amazon の商品情報には「この商品の別バージョンとしてこういう商品があります」という情報が含まれており、旧設計ではこの情報を利用して同じ書籍の Kindle 版と紙版や新装版をまとめていた。しかしこの情報が設定されていない商品も多く、特にジャンプコミックスはこの手の情報が欠けがちで、結構な頻度で重複が発生してしまっていた。ジャンプコミックスのような大手レーベルでも情報が欠けているのは絶望的な状況。

新設計では、

  • シリーズの判定ルールがより決定論的になった
  • シリーズ判定精度が向上した
  • 巻数判定機能を実装した

という変化によって、書籍の同一性判定に使える材料が増え、同じ書籍に対して重複したレコードが生成されてしまう問題がより起きづらくなった。具体的には、同じシリーズ名の同じ巻数の商品であれば同じ書籍だということにしてしまおう、というルールを設けている。

この同一性判定方法の導入によって、Amazon 以外の書籍販売サービスの ID 体系との相互乗り入れが容易になったとも考えている。特に ISBN のような主流かつグローバルな ID 管理体系の存在しない電子書籍などの分野では便利で、DMM や楽天との連携や、例えば動画配信や同人誌販売分野への展開など、複数のプラットフォームに対応したくなったときに効いてくると思う。

UI 上の問題

旧来のデータ構造のために UI を用意する場合の問題として、シリーズに含まれない書籍と含まれない書籍が存在する設計では、書籍の紹介方法や管理方法が難しくなってしまうという問題があった。シリーズ化された書籍というのは「あるシリーズに含まれるある本」というように表現するのが自然だが、対応するシリーズが存在する書籍と存在しない書籍があるという状態の場合、それらの書籍を一緒に紹介するとき、情報の粒度が不均一で表現方法が難しくなってしまう。

全書籍が必ず何らかのシリーズに含まれるようにしたという変更は、UI 設計を容易にするためでもある。

新刊リスト

新刊リストの計算方法も変化し、今回からシリーズの購読という概念が追加された。

旧設計での新刊リストは「過去に2冊以上読んだシリーズの新刊 + 読みたい本」というものだったが、新設計では「購読しているシリーズの新刊 + 読みたい本」という計算方法になった。興味のない本を新刊リストから opt-out するために、1巻で切ったかどうかを判定基準にしていたのを、そのシリーズを購読状態にしているかどうかという判定基準に変えたという形。

シリーズごとに購読ボタンが付いていて、これを押すことで購読できるという仕組みになっている。また、読んだ本を登録するとその時点で自動的にシリーズを購読することになるので、わざわざ新刊リストに表示されてほしい本を購読して回る必要はない。能動的なアクションで自ら購読ボタンを押すというユースケースはあまり想定していなくて、どちらかと言うと opt-out 用の機能であり、かつ新刊リストの計算負荷を軽減するための設計である。

おしまい

問題なく移行が終わると良いですね。