パワー系Endless Summer。TumblrでランダムなポストIDを取得

Tumblrにおける、Endless Summerロジックというものをご存知でしょうか？ Tumblrの投稿一つ一つにつけられているポストIDという連番数字があります。Endless Summerは、このポストIDをランダムに選択するためのロジックです。
# なんでそんな事をしたいのか

Endless Summerのそもそものモチベーションは、Tumblrでランダムな時点のダッシュボードを表示したいという思いです。ダッシュボードの一日あたりのポストは有限です。フォロワーの多い人でも一日あたり数千件というところでしょう。つまりその数千件を消化したらその日のタンブリングは終了というわけです。これはとても悲しい事です。

しかしそこで、1年前、2年6ヶ月前、3ヶ月前といった風にランダムな時点のダッシュボードに潜れるとしたらどうでしょう？ 昔のポストならば記憶も薄れているでしょうから、また新鮮な気持ちでダッシュボードを潜り続ける事が出来ます。どこまでも、どこまでも。終わらない夏というわけで、Endless Summer。これはとても素敵な事です。

ランダムなダッシュボードを作るには、ランダムなポストIDが必要です。 ランダムなポストIDさえあれば、後はそのポストIDをAPIに投げてやる事で、その時点のダッシュボードを取得出来ます。だからランダムなポストIDが必要なんです、それも十分にランダムなポストIDが。

# 単純なランダム処理ではダメな理由
ポストIDは、全ユーザーで共通のただの連番です。執筆時点（2017/03/23）のポストIDは 158,700,975,483 番でした。つまりポストIDは、1番 ～ 約1600億番の間のどれかというわけです。では単純に1番 ～ 約1600億番のレンジでランダムな数字を選択すればいじゃないかと思うわけです。こんな処理はどのプログラミング言語でも簡単です。

しかしこの方法ではダメなのです。この方法でランダムなポストIDを生成すると、最近のポストばかりが選択されてしまいます。理由は簡単、Tumblrのユーザーが右肩上がりで増えているから。ユーザーの少なかった昔はポストIDの増加がゆるやかで、最近はすごい勢いでポストIDがカウントアップされているため、確率的に最近のポストIDが選択されやすいのです。ですから、年々増えるポスト数の変化を考慮に入れなくてはならないのです。

# Endless Summerの歴史
Endless Summerに関しては、何人かの方が取り組みをされてきています。その取り組みを私が知っている範囲で紹介したい。

### ■ Endless Summerの誕生
[endless summer on dashboard](http://taizooo.tumblr.com/post/60322576)
Endless Summerという名前の初出は、2008年のAutoPagerizeのUserScriptとしてだったようです。これは、継ぎ足すページ数をランダムにする事で様々な時点のダッシュボードを表示することが目的だったようです。ランダムなポストID取得とはちょっと別の話です。

### ■ 年毎のポストIDの変化を考慮に入れたEndless Summer
[ex gkojaxlabo http://kagurazakaundergroundresistance.tumblr.com/ (最古の tumblr bot)のアーカイブから年月日を基準にランダムな post_id を生成する習作](http://d.hatena.ne.jp/taizooo/20120326/1332725316)

こちらは、年毎のポストIDの増加を考慮に入れたロジックです。ランダムなポストIDを取得するという意味でのEndless Summerの起源はこれだと思います。すべてはここから。

こちらのロジックは比較的シンプルで、まず年毎のポストIDのレンジをパラメータとして準備します。2008年は、22716605 ～ 67666062、2010年は、309781569 ～ 2542121207といった具合です。そして、まず最初にランダムに年を選択します。Tumblrのサービス開始は2007年なので、2007年から現在まででランダムな年を決めます。その後、その年のポストIDのレンジでランダムなポストIDを選択するのです。例えば、 2010年が選ばれたとしたら、 309781569 ～ 2542121207の間でランダムな番号を選択します。この方法の良い点は、年毎の出現確率が等しいという事です。問題点は、その年の後半が選択されやすいという事。その年だけでみてもポスト数は増加するので、どうしても後半の方が選択されやすいのです。

### ■ Exponential Endless Summer
[指数関数フィッティングでもう少しランダムに Endless Summer](http://tumblr.g.hatena.ne.jp/saitamanodoruji/20120604/1338776265)
[Exponential Endless Summer を改訂しました (version 2013)](http://tumblr.g.hatena.ne.jp/saitamanodoruji/20130725/1374700510)
[Exponential Endless Summer version 2013.08](http://tumblr.g.hatena.ne.jp/saitamanodoruji/20130831/1377961157)
[Exponential Endless Summer 2014](http://tumblr.g.hatena.ne.jp/saitamanodoruji/20140831/1409496698)

こちらは、ポスト数の指数関数的な増加を考慮に入れたロジックです。ポストIDのレンジ毎に調整用のパラメーターをもって、出現確率を平らにならそうというアプローチです。これにより、年末に偏っていた既存ロジックに比べ、よりランダムな日付が選択されやすくなっています。

# パワー系 Endless Summer

ここまで紹介したロジックは、ランダムなポストIDを計算するためにパラメーターが必要になります。Exponential Endless Summerの2014年版ロジックでは、47個のパラメーターを使用しています。このパラメーターは日々調整が必要です。作ったらそれで終了というわけではありません。もし2017年版のExponential Endless Summerロジックを作るとしたら、47個よりもっと多くのパラメーターが必要になるはずです。

このロジックを見た時私思ったんですよね。50個、60個と日々パラメーターを増やさなければならないなら、もっとパラメーターが多くても許されるんじゃないかと。例えば8万個ぐらい。

もう年毎のポストIDとか、指数関数的増加に対応したパラメーターとかではなく、いっそのこと全部保存すればいいと思ったのです。毎日のポストID、なんなら毎時のポストIDを全部保存しておけばいい。サービス開始の2007年から現在までの毎時のポストIDをすべて保存してパラメーターとして使うロジックを提唱したい。名付けて「**パワー系 Endless Summer**」。現在のパワー系 Endless Summerのパラメーター数は82,687個、毎時一個づつ増加中です。

### ロジック概略
Tumblrのサービス開始の2007年から現在までの毎時のポストIDをあらかじめバッチで取得しデータベースに保存します。そして、その期間のランダムな日時を選択します。後はその選択された日時のポストIDをデータベースから取得するだけです。

### データベース、テーブル構成例(MySQL)
```
mysql> desc hourly_post_id;
+---------------+---------------------+------+-----+---------+-------+
| Field         |                Type | Null | Key | Default | Extra |
+---------------+---------------------+------+-----+---------+-------+
| ymdh          | int(10)             |  NO  | PRI | 0       |       |
| post_id       | bigint(13) unsigned |  NO  |     | NULL    |       |
| post_datetime | datetime            |  NO  |     | NULL    |       |
+---------------+---------------------+------+-----+---------+-------+
```
| カラム名      |      備考    |
|:--------------|:-------------|
| ymdh          |     YYYYMMDDHH形式で年月日時を保存。<br>例： 2017031010     |
| post_id       |    その時刻のポストID    |
| post_datetime |    実際にそのポストが投稿された日時     |


### フォローしまくる
適用なTumblrアカウントを用意し、そのアカウントで有名そうな人をフォローしまくります。昔からTumblrをやっていそうな人がいいです。

### 最古のポストを取得する
TumblrのDashboard APIに対して、since_id=1でまずクエリを投げます。具体的には下記のようなクエリ。

```
http://api.tumblr.com/v2/user/dashboard?since_id=1
```

これによって、自分のダッシュボードから最古のポストを取得出来ます。そして取得したAPIからのレスポンスを順番に走査して毎時のポストIDを取得します。

### ポストIDの特定の方法
|             id|          date|
|:--------------|:-------------|
|145768172020   | 2016-06-11 19:59:06|
|145768174455   | 2016-06-11 19:59:40|
|145768190930   | 2016-06-11 19:59:44|
|145768195995   | 2016-06-11 20:00:10|
|145768211230   | 2016-06-11 20:00:18|
|145768243325   | 2016-06-11 20:00:42|

もしこんなAPIからのレスポンスがあったとすると、2016年06月11日 20時のポストIDとして、 20時を超える直前の 145768190930 を選択します（決めの問題なので直後の 145768195995を使ってもOK）。

※ レスポンスのdateカラムはGMTで日時が帰ってくるので注意。タイムゾーンを考慮して変換のこと。

### データベースに保存する
特定したポストIDを以下のようなクエリでデータベースに保存

```
INSERT INTO hourly_post_id (ymdh, post_id, post_datetime) VALUES(2016061120, 145768190930 , '2016-06-11 19:59:44');
```

### 繰り返す
レスポンスで帰って来たデータをすべて走査し終わったら、レスポンスの中で最も新しいポストのポストIDをsince_idにセットしてコールし、毎時のポストIDを特定することを繰り返す。

```
http://api.tumblr.com/v2/user/dashboard?since_id=145768243325
```

### 定期的に実行するようにセットする
このように、過去から現在に向けて毎時のポストIDを延々特定します。バッチが現在まで到達したらバッチは一旦停止。それ以後はcronかなにかで毎時にバッチを起動し、一時間に一回最新のポストIDを取得するようにします。

### 精度を上げるには
この方法は、毎時のちょうどいい辺りに誰かがポストしてくれているということを期待しています。そのため精度を上げたい場合はフォローを増やしてください。ユーザーの少なかったサービス開始頃の精度は、どうしても精度が低くなります。その場合は、下記のように最古 ＋ リブログ情報付きでAPIをコールし、最古のポストをリブログしているユーザーをフォローすることで昔に活動していたユーザーをフォロー出来るので精度を上げられます。

```
http://api.tumblr.com/v2/user/dashboard?since_id=1&reblog_info=true
```

# このロジックのメリット
既存のEndless Summerロジックが持っていた、選択される日が偏るという問題が原理的に発生しません。 サービス開始から現在までのすべての時間の出現確率が一定です。

一度バッチを稼働させてしまえば、定期的にロジックのパラメーター調整という忘れがちな作業が不要です。ある意味、毎時調整しているわけですから。

そしてこれが一番重要かもしれませんが、毎時のポストIDを保存している副産物として、日時を指定してのポストIDの取得が出来ます。それを元に好きな日時のダッシュボードをピンポイントで生成出来るようになります。