ツイートを長期間収集する実験(インフラ準備編)

　はじめまして、yuhkan と申します。一応仕事もPGですが、仕事に関係なく趣味のプログラミングネタで書いてみたい＆~~お知恵拝借したい~~コメントいただきたいと思い、拙いながら投稿させていただきます。
　へっぽこPGのどんくさいやり口から、「ああ、アホはこんな考え方するんだなあ」「もっとまともな方法もあるだろうに、アホなことやってるなあ」とお笑いいただければ幸いです。
# 自己紹介
* へっぽこプログラマ。応用情報とれたけど、多分偶然。
* C → VC++ →C# のWin一辺倒。 IDEが無いと死ぬ。
* SQLだけは細々長々触り続けてた。
* ジェネリック、テンプレート、Web技術ほぼゼロ。LINQもさわりだけ。<br>学ぶ機会も触る機会もなかった……
* テキストマイニングとか機械学習とか、そういうのやってみたい。
* あと、買い専<sub>(隠語)</sub>。のはずだったが本も作り出した。

# コトの経緯
　去年のことになりますが、本<sub>(隠語)</sub>を１冊作ることになったのです。
　半期に一度のイベント<sub>(隠語)</sub>用に提供されるデータベースを１０年分ほど全部ひとまとめに統合して、同名の店舗<sub>(換言)</sub>がどの位いるかとか、同名の店舗<sub>(換言)</sub>がどのカテゴリ<sub>(換言)</sub>に集まっているかとか、それの年ごとの推移とか、そんな感じの簡単な統計っぽい本ができまして、おかげさまで内輪的には好評をいただきました。
　味を占めたことも有り、次も何かやりたいなあ、次は何をやるかなあ、とかを考えたとき、実のところ、現在の手持ちデータでそれ以上書けるネタが無いな、 と言う事に気づきました。
 
1. あまり深いところに突っ込むと恨みを買うかも知れない。
2. 過去のデータをほじくり返しても限度がある。
3. この冬、カテゴリ<sub>(換言)</sub>分類に大変更があって、過去との互換性が無くなった

　特に３番目の問題が大きく、来期以降のデータで「過去との比較」が出来なくなってしまったのが大きな問題です。元ネタのデータベース自体は、基本的に半期の提供ごとで独立のもの、というタテマエなので、構造やらなにやら、すべてがいきなり切り替わっても文句つけるわけにもいきません。
　手持ちのネタでどうにかするのは早々に諦めることに。じゃあ、何をするかな、とか考えていて、以下のようなかんじに。
 
 1. 店舗<sub>(換言)</sub>の紹介や宣伝は、データベース<sub>(換言)</sub>上のそれはすでに時代遅れ気味。
 2. 最近はほとんどがTwitter上で行われており、最新情報を得るなら店主<sub>(換言)</sub>をフォローしておくのが常識である
 3. 当然、話題になる店舗<sub>(換言)</sub>の情報もTwitter上に集まる。期間<sub>(換言)</sub>中のアレコレの事象情報も、とんでもない量が集まるはずである
 4. これは、テキストマイニングのネタ(自習教材としても、本のネタとしても)として最適では無いか？

　そんな理由から、見切り発車気味に**「Twitterのツイートを取得して分析する」**を次の本の主題にすることに決めました。
 期間前後を挟んで情報を取得するため、冬は本作るの中止。<small>結果発表は５月<sub>(隠語)</sub>ねらいにするかな。</small>
 
# やりたいこと
1. **TwitterのPublic Streamから、特定のワードを含むツイートを(API仕様の範囲で)全部取得する。期間は３ヶ月。**
    2. インフラ準備　<- Now!
    3. プログラム準備
    4. 動作テストと動作
5. 取得したデータを解析したりして、何か愉快なネタが出来ないか探す

# 基礎調査
　まず、「いつまでに」「何が出来ること」「どの程度動く必要がある」あたりを、検討する必要があります<small>……あるよね？</small>
 
 * とりあえず、**10月後半にある当選発表<sub>(隠語)</sub>段階から情報の取得を行いたい。**<br>取得の終了は会期終了後2週間程度、1月10日以降とする。
 * 後からの分析に際して、何が必要な情報になるかわからないので、「**送られてくるものは全部保存**」する。　
 * **概ね３ヶ月(10月後半～1月前半)間、取得を一切止めたくない**。ずーっと動き続けているのが理想。

　当選発表<sub>(隠語)</sub>は10月末なので、それまでに取得部分だけはすべて作る必要があります。<br>逆に、分析等の処理については後回しにして問題無い、と言えます。
　何でもかんでも保存する、という方針を踏まえると、ストレージの容量についてはかなり重要な用件になります。
　また、24h/365d稼働(実際には90日連続で動けば良い)のために、ハードについても検討しなければいけません。

　もっとも個人の趣味でやることですから、24h/365dなんて基本的に保証出来るモノでも無いですし、**無理無茶無謀にならない範囲でやる**、というのが大前提になります。
 
## どの位の容量が必要?
　なんにせよ、具体的にストレージがどれだけ必要かを推定する必要があります……<small>あるよね？</small>
　実運用のことはおいておいて、ひとまず<a href="https://github.com/CoreTweet/CoreTweet/wiki/Home(%E6%97%A5%E6%9C%AC%E8%AA%9E)">CoreTweet</a>を使って、ツイートを取得、JSON形式のデータを<a href="https://system.data.sqlite.org/index.html/doc/trunk/www/index.wiki">System.Data.SQLite</a>をつかって、Sqlite形式のDBにINSERTし続ける、という実証実験用プログラムを作りました。
 
 　<small>順番前後しますが、検討の段階でREST形式での取得は早々に切り、Public Streamから取得することに。API制限にかかって死んだ、とか、絶対起こるだろうし……</small>

```
        private void btnConnect_Click(object sender, EventArgs e)
        {
            // 前処理省略
            StreamingConnection = token.Streaming.FilterAsObservable(track: txtKeyword.Text)
	            .Where((StreamingMessage m) => m.Type == MessageType.Create)
                .Cast<StatusMessage>()
	            .Subscribe((StatusMessage m) => this.addData(m));
            // 後処理省略
        }
        private void addData(StatusMessage src)
        {
            // 前処理省略
            using (SQLiteCommand com = conn.CreateCommand())
            {
                com.CommandText = "INSERT INTO Tweets( json) VALUES(@jsonv)";
                com.Parameters.Add("jsonv", System.Data.DbType.String);
                com.Parameters["jsonv"].Value = src.Json.ToString();
                com.ExecuteNonQuery();
            }
            // 後処理省略
        }
```        
  <small>(初めてさわったReactive Extensionてのがえらい便利。)</small>
  
  　ひとまず本来想定しているキーワードに近しいとあるキーワードで１日動かしてみたところ、**２万ツイート、200MB/日**という結果が出ました。
   <span style="font-size:small">(公式RT含む。というか、RT9割、ツイート1割だった)</span>
   
   * 単純な想定として、90日取得したら180万ツイート、1.8GB必要になる。
   * が、なんのイベントも無い平常時の１日のツイート量を見て、「これだけあれば足りる」とは言えない。<br>そもそも、今回のキーワードは本来想定のキーワードの内に含まれるワードなので、より多いツイートが流れるはず(**当然の推測**)
   * 今回取得したキーワードのカテゴリ<sub>(換言)</sub>の店舗<sub>(換言)</sub>数は、全体の店舗<sub>(換言)</sub>数からみて10%未満。<br>ただし、Twitterで情報発信していない店舗<sub>(換言)</sub>の率を想定に入れると、２割程度と推定できる(**過去のデータからの推測**)
   * 最大ピーク時はさらにその倍は押し寄せる可能性がある。ただし、その時間は２時間は続かないだろう(**経験則**)。
   * ただし、店舗<sub>(換言)</sub>よりも客<sub>(換言)</sub>の数の方が10倍以上多い。ずっとつぶやき続ける人はいないだろうから、全体で均して２倍程度になる、と推定する(**希望的観測**)

　以上の想定にマージンを加え、**2000万ツイート、200GBを保存する**、という前提で進めることにしました。 (10/05変更：単純な計算が間違ってました(死 20GB→200GB)
　１日/１ヶ月単位に均せば、プロバイダ転送量の制限とかにはかかりそうに無いのが救いです。最大ピーク時でも、問題なし。
 
## どこに保存する?
 　これがお仕事なら、AzureだのAWSだの、クラウドストレージにしようぜ、とスナック感覚で言う(そして後から頭を抱える)所ですが、**200GBぶち込んで、場合によってそこで計算**とか、お金の足りない個人ではパケ死では済まない話になりかねないのでとりあえず却下。
 　幸い、手持ちの機材の中に高機能NASの<a href="https://www.qnap.com/ja-jp/product/model.php?II=143">QNAP TS-451</a>がありました。中のOSは実質Linuxで、１年ほど稼働させてみてもフリーズとか起こさない、大変優秀なストレージ。空き容量もテラ単位で残ってるから安心だし、UPSも繋いでるから回線ごとぶち切れなければ大抵のことにはなんとかなりそうです。
  <small>バックアップだの障害対処だのを始めたら、個人でなんとかなる範囲を超えるので妥協。</small>

　TS-451の利点として、いろんなソフトを追加導入して、機能を増やせる、というのがあります。容量上、データベースの利用は規定事項なので、DBについてもついでに選定。

* サンプルに使ったSQLiteはファイルベースなので、経過の外部観察(複数クライアントの同時接続)が出来ないので却下。
* MySQLはダメではないが、QNAPのOSにも結びついているので、お遊びのデータ格納に使いたくない。
* せっかくJSONで取得するのだから、そのまま加工が出来るのが理想。
* あと、日本語でのサンプルが多いこと。(重要)。

　以上の理由から、方々ググりまくって、MongoDBを利用することにしました。QNAPの機能として、MongoDBを追加することも出来たのですが、なにやら不可思議な不具合が発生したため、Dockerのコンテナを動かすことが出来る「Container Station」という機能で、Docker上でMongoDBを動かすことに。検討の際には<a href="http://qiita.com/dotKomu/items/13f9223f2957436f3019">こちらのサンプルが参考になりました。</a>1000万件登録したり処理したりとものすごい高速のようなので、これならさらにその２倍ぶち込んでも大丈夫そう。
 
## どうやって保存する?
　個人での情報収集に際し、**転送量の問題はなし**、**ストレージの問題もなし**、**データベースも準備OK**。一番のハードルになりそうな、インフラ周りの箇所はクリアしたと言える……<small>んじゃないかな</small>。
 
 　次にやらなきゃいけないのは、収集用のプログラム(実運用)な訳ですが……

 * 24h動かしっぱなしに出来るWinマシンが手元に無い
 * Windows Updateとかいきなり動いて再起動死とか目も当てられない(対策は出来るが)
 * DBサーバとプログラムを動かすマシンを分けると、宅内LAN内でデータが行ったり来たりして、普段のスループットに影響が出る

　Dockerも動くことだし、NAS上でプログラムを動かすことにしましょう。テキストマイニングとか始めるなら、技術資料もサンプルも多いのはPythonだなあ。こりゃあ、Python使わざるを得ない。
 
 
 
　……ん？ でも自分、Pythonとかさわったこともないのだけども……
 
 
 　最初のリミット、当選発表<sub>(隠語)</sub>まで３週間……。
 (続く)