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で落ちるのは、
TritonnとSennaのバージョン差異が原因です。
Tritonnで指定しているバージョンのSennaを使うことをオススメします。
(最新版では起こらないと思います)
Sennaの昔のバージョンは、
高負荷の場合に落ちつつしかもその後更新がロックする問題がありましたが、
その問題は現在修正されています。
自前でビルドしている
以前からTritonnをお使いいただいている方は、
自前でMySQLでパッチを当ててTritonnを導入されていると思います。
しかし、現在は自前ビルドはオススメしておりません。
配布されているバイナリを使うことをオススメいたします。
OSがi386である
Sennaはmmapという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。
ニコニコ大百科開発ブログはじめました。
[xml][libxml2][c]XMLをHTTPSで取得して、XPathで指定された中身をC言語で取り出す方法
某サービスのAPI呼び出しについて、
という新しい要件が発生した。
というわけで、
を更新。今度は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を回ればビルドできます。