Rubyでeventmachineを使って高速にメールを送る

またまた某サービスで、今度はメールを送るという要件が発生。
しかも、ユーザごとに異なった内容を送るというもの。


テンプレートはErubisを使うとして、
メール送信はどのライブラリを使おう。tmailかなぁ?
Google検索すると、eventmachineってものがあるらしい。
Rubyから非同期でガンガンSMTPサーバに接続できるようです。ひゃっほい!


というわけで、ためしにeventmachineを使って
メールを送信するテストコードを書いてみた。
一応並列に20個コネクションを張っている…はず。
テストが怪しいので、ご利用時は検証のこと。
eventmachineの挙動全く理解してねっす。
eventmachineにはSMTP clientだけでなくSMTP serverの実装もあるので、
テスト時にはそれを使うといいと思う。


ちなみに、EM::Protocols::SmtpClient.sendでGoogle検索したら
2件くらいしか引っかからない…
情報少ないなぁ。

#!/usr/bin/ruby
# UTF-8前提です
$KCODE = 'u'

require 'nkf'
require 'eventmachine'

class SendMailWithEventMachine
  attr_reader :successes, :errors

  def send_mail(domain, host, port, starttls, from, queue_threshold, &blk)
    @successes = 0
    @errors = 0
    @on_queue = 0
    @all_queued = false

    @domain = domain
    @host = host
    @port = port
    @starttls = starttls
    @from = from

    EM.run {
      queue_threshold.times {
        send_mail_queue(blk)
      }
    }
  end

  private

  def send_mail_queue(blk)
    unless @all_queued
      if data = blk.call
        if (smtp = EM::Protocols::SmtpClient.send :domain => @domain,
          :host => @host,
          :port => @port,
          :starttls => @starttls,
          :from => @from,
          :to => [data[:to]],
          :header => {'To' => data[:to],
                      'Subject' => NKF.nkf('-jW --mime', data[:subject])},
          :body => NKF.nkf('-jW', data[:body]),
          :verbose => false)
          @on_queue += 1
          smtp.callback {|r|
            @on_queue -= 1
            @successes += 1
            send_mail_queue(blk)
          }
          smtp.errback {|e|
            @on_queue -= 1
            @errors += 1
            send_mail_queue(blk)
          }
        end
      else
        @all_queued = true
      end
    end
    if @all_queued and @on_queue == 0
      EM.stop_event_loop
    end
  end
end

mails = [{
  :to => 'victim1@example.com',
  :subject => 'さっき天一で',
  :body => '無料券使ったら',
},
{
  :to => 'victim2@example.com',
  :subject => '並盛だけですと言われ',
  :body => 'きちんと抗議したよ!',
}]

i = 0
sm = SendMailWithEventMachine.new
sm.send_mail(
  'example.com', 'mx1.example.com', 25, false, 'spammer@example.com', 20) {
  # mysqlのfetch_rowなどを想定
  next if i >= mails.length
  i += 1
  mails[i - 1]
}

Senna 2.0の展望と、Tritonnで問題が発生している人向け情報

Senna 2.0βのリリースが見えてきました。
去年の夏に出すと言っていましたが、紆余曲折あっての現状です。


ライバルのTokyo Cabinet/Tokyo Dystopiaについては、
ストレージと全文検索インデックスを分割する方向性です。


今までのSennaはTokyo Dystopiaに近いものでしたが、
Senna 2.0では逆にHyper Estraierのほうに近づく感じになっています。
それぞれ特色が出て面白いですねー。
今回は転置インデックス部分にもかなり手が入っているので、
Senna/Lucene/Tokyo Dystopiaのパフォーマンス比較もやってみたいと思います。
(とはいえ、パフォーマンス比較はそれぞれのライブラリに精通しないと意味のある情報が出せないので、大変ではありますね…)


Senna 2.0 + MySQL 5.1用にプラガブルSennaストレージエンジンも開発中です。
後付バイナリインストールができるとかなり運用上楽ですしね。


Senna 2.0のテストについて、今回はクリアコードさんが協力くださっています。
テストには、クリアコードさんが作っているテストフレームワーク「Cutter」を使っています。
早めからバグが潰せていい感じです。


さて、将来の話はここまで。


現在Tritonn/Sennaを運用している方から、
「落ちるんだけど…」とツッコミいただくことがあります。
ツッコミが貰えて、すごく嬉しいですね。
使っていただいていることが分かりますし、
不具合を直す機会にもなります。


というわけで、Tritonn導入時の問題解決に役立つリストを書いてみました。
すぐ解決したいねん、という人は、住商情報システムサポートサービスをどぞー

Tritonnが落ちるのですが

以下の項目を1つ1つチェックしてみてください。

最新版を使っていない

Tritonnの最新版をお使いください。
旧バージョンには、テンポラリテーブルが削除される場合に落ちる不具合などがあります。
最新版は、COUNT(*)と2indとの組み合わせでカウントがおかしい問題があるようですが、
そろそろ直した版を出すってid:mirさんが言ってたお!

Senna/Tritonnとも現在出ている最新版は安定版なので、
基本アップグレードしたほうが安定します。

  • [追記]

SHOW SENNA STATUSで落ちるのは、
TritonnSennaのバージョン差異が原因です。
Tritonnで指定しているバージョンのSennaを使うことをオススメします。
(最新版では起こらないと思います)


Sennaの昔のバージョンは、
高負荷の場合に落ちつつしかもその後更新がロックする問題がありましたが、
その問題は現在修正されています。

自前でビルドしている

以前からTritonnをお使いいただいている方は、
自前でMySQLでパッチを当ててTritonnを導入されていると思います。


しかし、現在は自前ビルドはオススメしておりません。
配布されているバイナリを使うことをオススメいたします。

OSがi386である

amd64/em64t版のOSをお使いください。


SennammapというAPIを多用しているので、
論理空間不足にかなり敏感です。
落ちなくするようにすることもやろうと思いますが、
論理空間不足での運用は、パフォーマンスがかなり落ちると思います。

  • [追記]32bit版OSでの制限についての詳細と回避策

概算で、Sennaのインデックスの数 * 256M > 2Gとなると危険水域です。


一時的にでもSennaのインデックスの数が上記を超えるとマズいです。
たとえば、別名テーブルを作成してからrenameする、というような運用だと、
インデックスの数が運用状態の2倍となります。


回避策としては、

  • INITIAL_N_SEGMENTSを減らす(上記の256Mは、INITIAL_N_SEGMENTSに比例します)
  • Sennaのインデックスの数を減らす
  • コンテンツ分割をする

などがあります。


MySQLレプリケーション運用の場合には、
スレーブごとにインデックスを付与することができます。
それでインデックスを分割するというのが運用的に楽だと思います。
が、em64t対応CPUが普及し、em64t版OSも枯れてきた現状では、
em64t版OSに変えるのが将来的にはオススメできるでしょう。


インデックスの数が多く、コンテンツの量が多い場合には、
物理メモリも十分な量が必要です。
でも、まずは十分な論理空間の確保が先決です。

上記のどれにも当てはまらんぞコラ!

落ちたときに、mysqlのlogに

0809xx 5:xx:xx - mysqld got signal 11;

といったログが出ると思います。
そこに、

Stack range sanity check OK, backtrace follows:
0xdeadbeef
0xcafebabe
0x09286322

といった16進数値の羅列が出ていませんか?
Tritonnで配布しているバイナリを使っていれば、
この数値の羅列から、不具合の場所を大体特定することができます。


というわけで、この16新数値の羅列をTritonn-devやSenna-devメーリングリストなどに投げていただければ幸いです。
すでに多くの不具合は修正されているため、
残りの不具合についてはかなりのレアケースであることが考えられます。
よって、ツッコミをいただかないと、なかなか修正が出来なかったりするのです…


というわけでレッツツッコミ。4946。

CentOS 5.2でTritonnの最新版のrpmの導入に失敗します!

akiyan.comのブログを見て、導入方法を書かねば…と思い僕も実際試してみました。

しかし、Tritonnで配っているrpmを導入すると、

/etc/init.d/mysql start

が失敗するんですね。僕の手元の環境でも再現しました。
Tritonn作者であるid:mirさんにお伝えしておきました。


ちなみに、libmysqlclient.soが見つからない問題は、
mysql-devel的なパッケージをどこからか持ってくることによって解決することが出来ると思います。
が、これについてもtritonn側で配布してくれたら親切だとは思います。

ニコニコ大百科開発ブログはじめました。

ニコニコ大百科の開発関連についてはブログ分けました!


EeePCをサーバとして使い倒す方法や、
libeventのevhttpとvarnishの組み合わせ、
repcachedを使う上でlibmemcachedに当てなければいけないパッチ、
Rubyのlibmemcached bindingsに当てるパッチなどの情報については、
上記ブログに書いていこう…かと思ったのですが…


上記ブログはサービス寄りのほうがいいかな、
と今考えを改めました…
というわけで、どちらのブログもよろしくお願いいたします!

[xml][libxml2][c]XMLをHTTPSで取得して、XPathで指定された中身をC言語で取り出す方法

某サービスのAPI呼び出しについて、

  • API呼び出しをSSL化する
  • UserAgentに特定の文字列を入れる

という新しい要件が発生した。


というわけで、

を更新。今度はlibcurlを使った。


実用上は、
curlインスタンスcurl_responseのバッファなどは使いまわすべきです。


いつものごとくツッコミを待つ。
今頃になって、文字列格納用バッファ関数群がlibxml2にあるような気もしてきた…

sudo aptitude install libxml2-dev libcurl-openssl-dev
gcc -o test -I/usr/include/libxml2 test.c -lxml2 -lcurl
#include <stdio.h>
#include <string.h>
#include <curl/curl.h>
#include <libxml/xpath.h>

#define URL "https://dokokako.api/"
#define XPATH "/xpath"
#define USER_AGENT "nicowiki"
#define BAIRITU_DON 8

typedef struct {
  char *data;
  size_t length;
  size_t allocated;
} curl_response;

size_t
curl_callback(void *ptr, size_t size, size_t nmemb, curl_response *cr)
{
  if (!size || !nmemb) { return 0; }
  if (!cr->data) {
    if (!(cr->data = malloc(size * nmemb * BAIRITU_DON))) {
      return 0;
    }
    cr->allocated = size * nmemb * BAIRITU_DON;
  } else if ((cr->length + size * nmemb) >= cr->allocated) {
    char *b;
    if ((b = realloc(cr->data, cr->allocated * 2))) {
      cr->data = b;
      cr->allocated *= 2;
    } else {
      free(cr->data);
      cr->length = cr->allocated = 0;
      return 0;
    }
  }
  memcpy(cr->data + cr->length, ptr, size * nmemb);
  cr->length += size * nmemb;
  cr->data[cr->length] = '\0';
  return size * nmemb;
}

int
main(int argc, char *argv[])
{
  CURL *curl;
  xmlDocPtr doc;
  xmlNodeSetPtr nodes;
  xmlXPathContextPtr ctx;
  xmlXPathObjectPtr xpobj;
  curl_response cr = {NULL, 0, 0};

  if ((curl = curl_easy_init())) {
    curl_easy_setopt(curl, CURLOPT_URL, URL);
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3);
    curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &cr);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, USER_AGENT);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_callback);
    if (curl_easy_perform(curl) != CURLE_OK || !cr.data) {
      return;
    }
    curl_easy_cleanup(curl);
  }
  if ((doc = xmlParseDoc((xmlChar *)cr.data))) {
    if ((ctx = xmlXPathNewContext(doc))) {
      if ((xpobj = xmlXPathEvalExpression(
                     (xmlChar *)XPATH, ctx))) {
        if (!xmlXPathNodeSetIsEmpty(xpobj->nodesetval)) {
          xmlNodePtr node = xmlXPathNodeSetItem(xpobj->nodesetval, 0);
          if (node->content) {
            printf("%s\n", node->content);
          }
        }
        xmlXPathFreeObject(xpobj);
      }
      xmlXPathFreeContext(ctx);
    }
    xmlFreeDoc(doc);
  }
  xmlCleanupParser();
  free(cr.data);

  return 0;
}

MIDIファイルをはてなのメロディ記法やピコカキコに変換する(mid2flmml)

ニコニコ大百科MMLで音楽を投稿できるサービスを始めたはいいが、
MMLって難しいですよねー。
というわけで、MIDIからMMLへの変換をするソフトを作ってみた。


mid2mmlというソフトウェアがあるが、やはりFlMMLとは文法がちょっと違う。


こういうのを書くときにはPythonで。
各種ライブラリが充実しているし、
あとでWin32用のフロントエンドを付けたくなったときもすぐ出来るし。
PythonInMusic
今回は、midi.pyというパブリックドメインなSMFパーサを使いました。


適当にドラえもんの魔境編中盤のnsfをnsf2midiして、それをmid2flmmlで変換してみた。
Ticks->n分音符の変換がやはり難しい。というか、割り切れてないからズレるのは当然だな。
全体を把握してのテンポ調整なんかをしないとダメだなー。
とはいえ、音階はちゃんと取れていていい感じ。

T30
@V127O4B68R160A68R160<E68R160>B34R80A68R160<E68R160>B68R160B68R160A68R160<E68R160>B34R80A68R160<E68R160>B68R160B68R160A68R160<F+68R160C+34R80>A68R160<F+68R160C+68R160>B68R160A68R160<F+68R160C+34R80>A68R160<F+68R160C+68R160>A68R160G68R160<D68R160>A34R80G68R160<D68R160>A68R160A68R160G68R160<D68R160>A34R80G68R160<D68R160>A68R160A68R160G68R160<E68R160>B34R80G68R160<E68R160>B68R160A68R160G68R160<E68R160>B34R80G68R160<E68R160>B68R160B68R160A68R160<E68R160>B34R80A68R160<E68R160>B68R160B68R160A68R160<E68R160>B34R80A68R160<E68R160>B68R160B68R160A68R160<F+68R160C+34R80>A68R160<F+68R160C+68R160>B68R160A68R160<F+68R160C+34R80>A68R160<F+68R160C+68R160>A68R160G68R160<D68R160>A34R80G68R160<D68R160>A68R160A68R160G68R160<D68R160>A34R80G68R160<D68R160>A68R160A68R160A68R160A68R160<A68R36A68R160E68R160>A68R160A68R160A68R160A68R160<A68R160A68R160E68R160>A68R40O6F+60R240E60R240F+60R240E60R21>B60R240<D60R240F+60R240E60R240F+60R240E60R21>B60R240<D60R240E60R240D60R240E60R240D60R21>A60R240<C60R240E60R240D60R240E60R240A60R240E60R240D60R240>A60R40<F+60R240E60R240F+60R240E60R21>B60R240<D60R240F+60R240E60R240F+60R240E60R21>B60R240<D60R240E60R240D60R240E60R240D60R21>A60R240<C60R240E60R240D60R240E60R240A60R240E60R240D60R240>A60
;

T30
@V127O4B68R160A68R160<E68R160>B34R80A68R160<E68R160>B68R160B68R160A68R160<E68R160>B34R80A68R160<E68R160>B68R160B68R160A68R160<F+68R160C+34R80>A68R160<F+68R160C+68R160>B68R160A68R160<F+68R160C+34R80>A68R160<F+68R160C+68R160>A68R160G68R160<D68R160>A34R80G68R160<D68R160>A68R160A68R160G68R160<D68R160>A34R80G68R160<D68R160>A68R160A68R160G68R160<E68R160>B34R80G68R160<E68R160>B68R160A68R160G68R160<E68R160>B34R80G68R160<E68R160>B160R68B68R160A68R160<E68R160>B34R80A68R160<E68R160>B68R160B68R160A68R160<E68R160>B34R80A68R160<E68R160>B68R160B68R160A68R160<F+68R160C+34R80>A68R160<F+68R160C+68R160>B68R160A68R160<F+68R160C+34R80>A68R160<F+68R160C+68R160>A68R160G68R160<D68R160>A34R80G68R160<D68R160>A68R160A68R160G68R160<D68R160>A34R80G68R160<D68R160>A68R160A68R160A68R160A68R160<A68R36A68R160E68R160>A68R160A68R160A68R160A68R160<A68R160A68R160E68R160>A68R68O6D60R240C60R240D60R240C60R21>G60R240B60R240<D60R240C60R240D60R240C60R21>G60R240B60R240<C60R240>A+60R240<C60R240>A+60R21F60R240A60R240<C60R240>A60R240<C60R240E60R240C60R240>A60R240F60R40<D60R240C60R240D60R240C60R21>G60R240B60R240<D60R240C60R240D60R240C60R21>G60R240B60R240<C60R240>A+60R240<C60R240>A+60R21F60R240A60R240<C60R240>A60R240<C60R240E60R240C60R240>A60R240F60
;

T30
@V90O5E32R96F+32R96G32R96B32R96<E16R48E32R96E32R96D+20R68>B60R240<D+20R68>B60R240<D+60R240E60R240F+60R240E60R240D+32R19>D32R96E32R96F32R96A32R96<D16R48D32R96E32R96C+20R68>A60R240<C+20R68>A60R240<C+32R96>B60R240<C+60R240>A32R19E32R96F+32R96G32R96B32R96<E16R48E32R96E32R96D+20R68>B60R240<D+20R68>B60R240<D+60R240E60R240F+60R240E60R240D+32R19>D32R96E32R96F32R96A32R96<D16R48D32R96E32R96C+60R240D60R240E20R68D60R240C+60R240>A60R240<C+60R240D60R240E60R240F+60R240E60R240D60R240C+60R40O4E60R240D60R240E60R240D60R240D60R240D60R240G60R240G60R240E60R240D60R240E60R240D60R240D60R240D60R240G60R240G60R240<D60R240C60R240D60R240C60R240C60R240C60R240>F60R240F60R240F60R240F60R240F60R240F60R240F60R240F60R240F60R240F60R240E60R240D60R240E60R240D60R240D60R240D60R240G60R240G60R240E60R240D60R240E60R240D60R240D60R240D60R240G60R240G60R240<D60R240C60R240D60R240C60R240C60R240C60R240>F60R240F60R240F60R240F60R240F60R240F60R240F60R240F60R240F60R240F60
;

いつものごとく完成度1割くらいでCodeReposに放りこんでおきました。

フロントエンドとかnsf/spc対応とかに期待したいところ。ぽこぺん。

ピコカキコ機能リリース。

ニコニコ大百科MMLでメロディを書き込める「ピコカキコ」機能をリリースしました。
JASRACと音楽著作物利用許諾についての契約を結んでいるので、
JASRAC管理楽曲についても投稿可能です。


はてなMML記法(メロディ再生記法)と互換性があるので、
今までに投稿したMMLについても再投稿どうぞー!
(FlMMLを使っているから当たり前ではあるんですが…)


JASRACとの契約締結にあたって、
はてなMML部でJASRACに利用許諾申請してみたいのですが
の議論が大変参考となりました。ありがとうございました!!


というわけで、頑張ってねー!。わくわく。
http://d.hatena.ne.jp/satoru_net/20080901/1220253796


ちなみに、ピコカキコと同じバイナリは
Google codeとCodeReposとlibsparkを回ればビルドできます。

SennaのTracが公開されております。

そういえば(安定剤)、lighttpのTracサイトで思い出しましたが、
SennaTracが稼動し始めました。


まだ情報が少ないですが、Ticketを切って開発を進めていこうと思っています。
宜しくお願いいたします。


ちなみに、
以前から公開しているニコニコ大百科Tracですが、
まったくToDoが減る気配がありません。(・3・)アルェー