とあるMySQLとチョコレートの話

何回もログを仕込んでようやく原因がわかったバグを潰したメモです。


1ユーザーにつき1個だけチョコレートを作りたいとします。
※今回はDBのunique制約でどうこうするとかいうレベルではなく、チョコレートが2回作られる時点でアウトとします。

以下の挙動をもつAPIをつくります。

リクエストユーザー 挙動
1 まだチョコレートをもってないユーザー チョコレートを作ってあげて、作ったチョコレートを返す
2 チョコレートをもっているユーザー チョコレートを返す


というわけで、以下のかんじで作ってみました。

function get_or_create_choco(user_id) {
  var choco = select_choco(user_id); //DB(master)からチョコレートをとる
  if (choco) {
    return choco;
  }
  else {
    choco = create_choco(user_id); //チョコレートを作ってDBにinsert
    return choco;
  }
}

これはだめぽよです。
リクエストが並列で2回きた場合、create_chocoが2回呼ばれてしまうのです。
というわけで、例えばmemcachedでロックを貼ってみました。

function get_or_create_choco(user_id) {
  var choco = select_choco(user_id); //DB(master)からチョコレートをとる
  if (choco) {
    return choco;
  }
  else {
    var lock = create_choco_lock(user_id) //memcachedで特定のキーでaddしてみる
    if (lock) { //ロック取得成功時
      choco = create_choco(user_id); //チョコレートを作ってDBにinsert
      return choco;
    }
    else { //ロック取得失敗時
   sleep 0.2;
      choco = select_choco(user_id); //DB(master)からチョコレートをとる
      return choco;
    }
  }
}


こうすれば、短期間にきた2回目のリクエストはロックを取得できないので、
0.2秒くらい休んだあとにDBを見に行けば、既に1回目のリクエストでチョコレートは作られているはずなので、
無事に作られたチョコレートを返せるはずです。


もう一工夫。これはかなり頻繁に叩かれる処理なので、ちょっとmemcachedを効かせてみます。

function get_or_create_choco(user_id) {
  var choco = select_choco(user_id); // memdからチョコを取る。なかったらDB(master)からチョコを取る。DBにあったらmemdにセットしてあげる。
  if (choco) {
    return choco;
  }
  else {
    var lock = create_choco_lock(user_id) //memcachedで特定のキーでaddしてみる
    if (lock) { //ロック取得成功時
      choco = create_choco(user_id); //チョコレートを作ってDBにinsert
      return choco;
    }
    else { //ロック取得失敗時
   sleep 0.2;
      choco = select_choco(user_id); // memdからチョコを取る。なかったらDB(master)からチョコを取る。DBにあったらmemdにセットしてあげる。
      return choco;
    }
  }
}

これで完成。
しかし低確率で、なぜかチョコレートを返せないときがありました。

もろもろログを増やしたところ、どうもロック取得失敗時に、select_chocoしてもチョコが取れないようなのです。つまり、2回目のリクエストにおいて、memdにもDBにもチョコがないと言われている。


色々原因を考えました。
・sleepが足りない?
 ・休む時間を伸ばしてあげてもダメ
・1回目のリクエストにおいて、memdやmysqlでのエラーはおきてる?
 ・おきてない
・チョコのmemdへのsetが失敗している?
・memdは複数台あるので、同じkeyなのに別のmemdを見に行ってる?
 ・memdになかったらDBにfallbackするし関係なさそう
・memd evictionしてる?
 ・してない
・memdに「チョコなかったよー」ってネガティブキャッシュしてる?
 ・してない


答えをいうと、REPEATABLE READとAutoCommit=0によるものでした。

AutoCommit=0でmasterに接続しており、コネクションを使いまわしていたので、2回のselect_chocoでトランザクションが継続していました ><
問題となるケースは、一番頭でselect_chocoをしていて空振っています。で、同じconnectionを使いまわしているので、それ以降は他のプロセスがいくらchocoを作っていても、いくらsleepしても「このユーザーはチョコがない」ことが確定してしまっています。シュレーディンガーの猫ですね。
そもそもcreate_chocoでmemdにセットすべきなのにしていないので、(短期間における)2回目のリクエストでは必ずDBを覗きにきてしまい、チョコがないことが確定しているのであぼーんという話。

基本はmaster DBからはちゃんとcommitして抜けているのですが、select_chocoのときだけ単なるselectじゃーんということで、commitしておりませんでした ><

あとはcommitとかコネクション使い回しとかがライブラリでいいかんじに隠蔽されてたのであんまり強く意識してなかったのも敗因です。。


最終的には、
(1)select_chocoではスコープから抜けるときにちゃんとcommitしてトランザクションを完結させる
(2)create_chocoでちゃんとチョコをmemdにもセットする
かんじで修正しました。


皆さんもシュレーディンガーの猫には気をつけましょう。単なるselectもcommitしないと罠になるかもよ。というお話なのでした。

IRC_name_resolver です。
リファクタあわせて3時間くらい。

https://github.com/mosasiru/irc_name_resolver

2013/10/03追記

IRCで、/who
でいけましたね…ちゃんとrealname登録してろよ!って話でした。(完

使い方

登録

register {nickname} {realname} です。

register Ben Benjamin
# inr_bot "registerd: nick(Ben) => real(Benjamin)"

register Benny Benjamin
# inr_bot "registerd: nick(Benny) => real(Benjamin)"

複数のニックネームが登録できます。

正引き・逆引き

nickname から realname を知る

nick? Benjamin
# inr_bot: "Benjamin's nickname: Ben,Benny"

realname から nickname を知る

real? Ben
# inr_bot: "Ben's realname: Benjamin"
削除

ニックネームの対応付けを削除できます。

unregister Ben Benjamin
# inr_bot: "unregistered: nick(Ben) => real(Benjamin)"
その他

pingbotにおくったり、使い方を確認したり。

ping
# inr_bot: "pong"

how
# show usage.

社内用に作りましたが、使えそうならみなさんガンガン使ってpull-reqなげてください><

IRCのnicknameと本名を対応付けるbot作った(IRC_name_resolver)

IRC_name_resolver です。
リファクタあわせて3時間くらい。

https://github.com/mosasiru/irc_name_resolver

使い方

登録

register {nickname} {realname} です。

register Ben Benjamin
# inr_bot "registerd: nick(Ben) => real(Benjamin)"

register Benny Benjamin
# inr_bot "registerd: nick(Benny) => real(Benjamin)"

複数のニックネームが登録できます。

正引き・逆引き

nickname から realname を知る

nick? Benjamin
# inr_bot: "Benjamin's nickname: Ben,Benny"

realname から nickname を知る

real? Ben
# inr_bot: "Ben's realname: Benjamin"
削除

ニックネームの対応付けを削除できます。

unregister Ben Benjamin
# inr_bot: "unregistered: nick(Ben) => real(Benjamin)"
その他

pingbotにおくったり、使い方を確認したり。

ping
# inr_bot: "pong"

how
# show usage.

社内用に作りましたが、使えそうならみなさんガンガン使ってpull-reqなげてください><

Perlなんて大嫌いですし、YAPC楽しみです

Perlなんて大嫌いです。



モジュールの最後の「1;」のダサさったらないし、$@とか諸々の省略形に吐き気がするし、オブジェクト指向は完全に後付けで「bless」でまず脱落しそうになったし、クラスメソッドとインスタンスメソッドの区別もないし、アンスコ始まりをprivate methodとみなす紳士協定だし、インスタンス変数は外からいじり放題だし、$@がグローバル変数なのも罠だし、Test::Moreなどのモジュールはsub{}で頑張ってDSLっぽくしようとしているのがなんだか涙ぐましいし、ググり方次第でインターネット黎明期のしょうもないCGI記事がわんさか出てくるし、そもそもPerlのスローガン「TMTOWTDI(やり方は1つじゃない)」とか大嫌いだし(初心者は酷い書き方ができてしまうし、ベストなやり方を模索する必要があるし、それでいて他人と開発するときは全てのやり方を把握していないと読めない)、会社で使っていなければ習うこともなかったでしょう。


何様って話ですので、名乗ります

僕は大したエンジニアじゃないです。Perlは始めて半年もたってないし、ちゃんとプログラミング勉強したのはここ2年弱。RailsとかCoffeeScriptとかDevise(gem)とか、わかりやすいレールに乗って調子こいてウェブサービスを2,3個リリースした。それでいてライブラリとStackOverFlowの過去ログ頼みだったから全然地力がついていなかったことに入社後気付いた、そんな人間です。低レイヤーの話は全然わからないし、ライブラリも外に公開したことはありません。
RPGで例えるなら装備を充実させるばかりでまったくレベルがあがっていなかったようなかんじ。たぶん最近の駆け出しウェブ系エンジニアの典型的な例じゃないでしょうか。強みがあるとすれば、アルゴリズムについてちょこっと詳しいくらいでしょうか。



そんな大した技術力もない人間がこういった文句を言ったら「言語にとやかく言う奴は結局どの言語もまともにできやしない」「CPANで全部解決できるし、できないならCPAN作れ」とか言う人がわらわらと出てくるでしょうし、真っ当な意見だとは思います。



Perlの現状

ただ、実際Perlの現状は結構悲しいものがあります。
QiitaとStackOverflowで記事数をちょっと調べてみたんですけど、他の言語との流行りっぷりの比較は、ちょっと血の気が引きます。




そんな中で、僕の言うことを素人の戯言として一蹴するのは簡単ですが、「おいおいそんなアグラ書いてていいのかよ」と思ってしまいます。なんだかそういうコミュニティの閉鎖感があるとしたら、すごい勿体無いなと。


YAPC

で、YAPC::Asia Tokyo 2013が始まるわけです。前夜祭も含めればもう始まってますね。Perlの世界的カンファレンスです。初めて参加させていただきます。

USTないの?って言われるたびに「ありません」と答えていたわけですが
その理由は「会場のネットワークが逼迫するから」「会場の運営に集中したいから」とか
色々ありますが、たった1つ明確なのは「会場に来てYAPCの一員となってほしいから」です。

YAPC::Asia にライブ映像配信がないたった1つの理由 | YAPC::Asia Tokyo 2013


とか言ってる場合なのかな、って思うのですよ。
もっと、開いた場にしようよ!って、純粋に思ってしまいます。



でもね

冒頭では言いたいこと言いましたが、実をいうと、なんだかんだでPerlに愛着湧いているわけです。なんだかんだで最早一番慣れた言語になっているし、CPANモジュールもpull-req送ろうと手を加えているし、YAPCでいろんな話が聞きたいし、コミュニティも実際どんなもんかと覗いてみたい。あわよくば混ざりたい。

ただ、もしアグラを書いているところがあるなら、それはどうなの、本当に大丈夫なの、って言いたい。

結局お前何が言いたかったんだよと思うかもしれませんが、YAPC期待していますし、Perlとそのエコシステムが、閉じないでどんどん洗練されていけばいいなと思います!

Gunosyのレコメンド機能に関する検証

こんばんは。学習するニュースサービス、Gunosyに対する批判が随所で書かれていますね。
本記事では、確かなデータをもとに、その批判を1つ1つ検証していきます。

結論

Gunosyは、はてブ記事に限らない、非常に多彩なニュースを配信している。

以下、

  1. 批判派の記事
  2. 検証
  3. 公式見解

の順に見ていきます。

批判派の記事

Gunosyのレコメンドエンジンの仕組み解説

論点1:レコメンド機能はなく、はてブからバズった記事を取ってきているだけなのでは

はてブでバズった記事のみを配信しているのではないか、という批判です。

違う!分析エンジンなんてものはないんだ。そもそも。
はてブユーザーの関心分野の記事しかそもそも引っかかってこない仕組みなんだ。

だから、お前が野球が好きなんてことは、Gunosyはこれっぽっちもわかっちゃいねえ。


そこら辺でバズった記事だから、誰が読んでもそこそこ面白いに決まってる。
そんな記事群からそれっぽいカテゴリに振り分けて配信しているだけだから、何も難しいアルゴリズムや解析エンジンなんてものはないんだ。そんなものは幻想だ。

「自分好みの情報を配信」とか、バカも休み休み言えという話だ。

http://angra.hatenablog.com/entry/2013/05/03/Gunosy%E3%81%AE%E3%83%AC%E3%82%B3%E3%83%A1%E3%83%B3%E3%83%89%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%B3%E3%81%AE%E4%BB%95%E7%B5%84%E3%81%BF%E8%A7%A3%E8%AA%AC
論点2:その結果、似たような記事ばかり配信されているのでは

このブログでは、フェルミ推定っぽい分析により以下の結論を出しています。

つまり、Gunosyの50〜60%のユーザーは、自分と全く同じ記事を読んでいるわけだ。

これで「一人ひとりの好みに応じて配信してます」というのはさすがにキビシイw

多分、Gunosyはユーザーのツイッターフェイスブックの投稿を分析して、キーワードを抽出し、そのキーワードが当てはまるカテゴリにユーザーを放り込んでいる。あとは日々のユーザーのクリックの仕方に応じて、そのカテゴリを組み替えているだけだ。

少なくとも、これくらいのことは数字で分かる。

http://angra.hatenablog.com/entry/2013/05/03/Gunosy%E3%81%AE%E3%83%AC%E3%82%B3%E3%83%A1%E3%83%B3%E3%83%89%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%B3%E3%81%AE%E4%BB%95%E7%B5%84%E3%81%BF%E8%A7%A3%E8%AA%AC

検証

この2つの論点に対し、きちんとデータを取って分析してみました。
この際、endorno君が書いてくれたgunosy-analyzerがあったため、そのまま利用させていただきました。

分析手法は以下となります。

  1. Twitterの検索APIを用い、gunosyのまとめ記事(例:僕の場合)を取ってきます。
  2. 各まとめ記事にて、レコメンドされている各記事をスクレイピングして取得します。
検証1 「レコメンド機能はなく、はてブからバズった記事を取ってきているだけなのでは」

こちらは、endornoが全て検証しています。
各レコメンド記事において、はじめにはてブされた時刻を取得し、配信時刻とどちらが早いかを比較しています。

Twitterでまとめを公開している80人(注1)の5月5日の記事をクロールして、はじめにはてブされた
時間とgunosyが配信される時間(注2)を比較、出力してみた。
出力結果はanalyzed_result.2013.05.05.txtを参照のこと。
結果だけ書くと、はてブより先に配信された記事が1925記事中127記事だった。

注2:デフォルトの8時のままの人が多いらしいので8時で固定。
それより遅い場合ははてブの方が先の可能性もあるけど、配信直前に解析し直すなんて馬鹿な実装してないだろうから無視
おそらく0時配信にしてる人に向けてそれより前に収集は終えてるはずだろうから8時でもフェアじゃないかも

https://github.com/endorno/gunosy-analyzer

はてブに載っていない記事も7%ほど配信されていることが明らかになりました。はてブの丸パクリな訳ではないようです。
また、はてブはリアルタイムにできる一方で、Gunosyはどうしても解析時刻と配信時刻に差があるため、この比較はGunosyにとってアンフェアである(解析と配信の間に誰かが初はてブしてしまう)可能性にも触れています。
僕の経験則ですが、昨今の面白い記事には既にはてブが付いているケースが多いことを考えると、7%という数字は少なくはないと思います。

検証2 「その結果、似たような記事ばかり配信されているのでは」

こちらは、gunosy-analyzerを用いて僕が検証しました。ユーザーごとのレコメンド記事の重複数を調べ、その分布をプロットします。

分布の特徴量は以下になります。

対象人数 80
レコメンド総数 2000
URL数 340
最大レコメンド数 50
平均レコメンド数 5.9
中央値 3
中央値以下の割合 56%

56%の記事は、80人中3人にしかレコメンドされていないことがわかりました。
Gunosyの50〜60%のユーザーは、自分と全く同じ記事を読んでいるわけだ。」こちらの批判は、データからすると的外れであることがわかります。確かに最もレコメンドされている記事は62.5%のユーザーに配信されていますが、むしろこれは例外であり、配信記事全体を見ると非常に多彩であることがわかります。
きちんと分析はしていませんが、分布は冪乗則に従う(ロングテールである)ことが推測されます。

公式見解

ここ最近のGunosy関連の批判についての所感

1.Gunosyは、はてブの再編集である/アルゴリズムなど存在しない?

結論から先に言わせて頂きますと、そんなことは無いです。ただ、我々のアルゴリズムの未熟さにより実際にそういう風に見えてしまう部分はあると思います。

http://gunosy.tumblr.com/post/49731783015/gunosy

真摯に、かつ真っ向から否定しています。

推薦対象となる記事の収集の過程においては,数多くのメディアのRSSを利用する一方で,はてなブックマークの新着RSSもソースの一つとして活用させていただいております.

世の中には星の数ほどのウェブメディアがあり,それらの最新の情報を効果的に集める手段としてはてなブックマークの新着情報が有用であると判断しこのような形をとっております.

さらにスコア付けの際、ユーザー様と記事の関連性の高さに加えて、記事の最低限の質を担保するためtweet数、like数、はてなブックマーク数なども考慮しています。なぜかというと、「興味有りそうだったから読んでみたけど、内容が薄かった」というような体験を減らしたいからです。

http://gunosy.tumblr.com/post/49731783015/gunosy

記事の質を担保する手段の1つとして、はてブを利用していることは正直に認めています。ある程度仕方がないと僕も思います。
仮に一切記事の質を担保しないニュースメディアがあったとして、本当に魅力的か?という疑問があります。7%の記事ははてブと被っていない訳で、その気になればはてブとあまり被らないニュース配信も簡単にできるでしょう。しかし、果たしてそのサイトは魅力的でしょうか?
恐らく、はてブとの被りはGunosy運営陣も気付いていたと思います。それでも、血の滲むようなユーザー行動の分析と試行の末、今の形に行き着いたのだと思いますし、これからも発展していくのかと期待しています。

また、多くの人に同じ記事が配信されるニュースがあるのは改善の余地はございますがある程度仕方が無いことだと考えています。多くの人に受け入れられる素敵な記事は存在すると思います。また多くの人に興味を持たれる話題が存在することも事実です(たとえばiPhoneの記事)。さらには現在Gunosyのユーザー層としてIT系アーリーアダプター層が多いことも記事が偏ってしまう原因だと考えております。一方で個人のニッチな興味をつく記事も配信されるロジックになっております。(たとえばユーザー様の0.1%や0.01%しか配信されないようなニッチな記事も多く存在しております)。そのため25記事全体で見た場合十分に多様でかつ個人にあった構成になっていると考えています。(もちろん改善点は多く、より多くの方に満足いただけるよう今後さらなる改善を目指していく所存ではあります)

実際にGunosyを使っている友人の方がいれば見比べてみてほしいのですが、配信されている記事が25記事とも同じであるというGunosyユーザー様はほとんど存在しません。実現したい世界はあなただけの新聞というイメージです。そこには1面には個人の趣味嗜好にどんぴしゃな記事がきて、中面には多くの人に興味がありそうで尚かつ本人も興味がある記事がくる、そんな世界です。

http://gunosy.tumblr.com/post/49731783015/gunosy

こちらの公式見解を、本記事では確かめることができました。

また、もうひとつ本件に対して、付記したいのは、Gunosyで配信されると、はてブがつく、という事実があります。

現在ご利用頂いている層がリテラシーの高いアーリーアダプターの方が多いため、Gunosyで配信されるとほぼ新しくはてブが付きます。

なので、すでにその記事見たことあるよという批判に対しては素直に、省くためのロジックが入っていなくて申し訳ございませんという気持ちです。現在解析がきちんと完了するよう1日単位での配信しかできておりませんが、今後はこの解析を早くすることでリアルタイム性の高い配信を実現したいと考えています。そうするとこの記事昨日見たよと体験も減らせると考えています。まだまだ未熟ですが鋭意努力していきます。

http://gunosy.tumblr.com/post/49731783015/gunosy

Gunosyで配信されてしまうことにより、はてブが加速してしまう効果にも触れています。

追記

Gunosyは多くのユーザに同じ記事を配信しているのか検証してみた
こちらに本記事の検証2と全く同じ分析がなされていました。
ユーザーのサンプリング法が異なるため、相互の記事の信憑性が高まっています。合わせてご覧ください。

Twitterの友人関係からコミュニティ解析をし、リスト作成を補助するサービスを作りました。

http://smartlist.bz
自動であなたの属するコミュニティを解析し、ビジュアライズした上でリスト作成ができます。
便利なので、ぜひ試してみてください!

呟き

  • 卒論で研究している、複雑ネットワークのコミュニティ分類手法の高速化を利用しています。
  • コミュニティ分類抜きにしても、実働で2週間ほどかかってます。中でもUIデザインは非常に苦労しました \(^o^)/ JQuery PowerTipでわかりやすくなったかな。
  • Twitter APIにも非常に苦労しました。詳しくはTwitterAPI devise連携/グラフ可視化/データの効率的格納/API高速化 - Qiitaを参照。並列処理で速くしたりしてます。
  • 注意:2012/01/18現在、取得するフォロー数を1000人に制限しています。

技術的なこと

標準的なRailsの構成ですが、データ格納のためにRedisを利用してます。
コミュニティ解析ではPythonのNetworkXライブラリを利用しています。