RabbitMQ 3.x から Queue のミラーリング方法が変更になった
今まで, Queue 作成時に指定していたミラーリングのパラメータは廃止になり, rabbitmqctl コマンドで指定するように変更なった.
Perl の AnyEvent::RabbitMQ や Python の kombu(celery) で x-ha-policy を指定する方法は, RabbitMQ 2.x までしか通用しない.*1
経緯や詳細は, 下記二つを参照の事.
- RabbitMQ >> Blog Archive >> Breaking things with RabbitMQ 3.0 - Messaging that just works
- RabbitMQ - Highly Available Queues
指定方法
Web UI の Manage Console からも指定できるが, 今回は rabbitmqctl の例を挙げる.*2
Usage は下記の通り.
$ rabbitmqctl set_policy <ポリシーの名前> <パターン> <モード> [<優先度>]
ポリシー名 | ポリシーを一意にし, 更新や削除の操作対象とする. |
---|---|
パターン | キュー名がパターンにマッチした場合, ポリシーの対象とする. 正規表現が使える. |
モード | RabbitMQ - Highly Available Queues 参照の事. |
優先度 | 数値が高い程, 優先. 未指定で 0 が入る. |
指定例
全てのキューをミラーリングする(ものぐさ設定w;).
$ rabbitmqctl set_policy all '^.*' '{"ha-mode": "all"}'
キュー名の接頭辞に ha が付いた場合にミラーリングする.
$ rabbitmqctl set_policy ha-all "^ha\." '{"ha-mode":"all"}'
キュー名の接頭辞に amq が付いた場合(キュー名を指定せず, RabbitMQ に自動採番させた場合) "以外" にミラーリングする.
$ rabbitmqctl set_policy ha-ignore-amq '^(?!amq\.).*' '{"ha-mode": "all"}'
運用中にポリシーの変更が可能らしいが, 検証はこれから行う予定.
運用中にポリシーの変更は可能. また, クラスタリング時の Manage Console のポリシー表記が変だったので, 下記のコマンドで動作確認した方が良い.
$ rabbitmqctl list_queues name policy pid owner_pid slave_pids messages_ready messages_unacknowledged messages
尚, ポリシーは, パターンにマッチする Exchange にも適用されている. これが, どのような影響を及ぼすのかは調査中.
Python の amqplib とか py-amqp で Message を Consume する際, ヘッダに x-death が付与されていると落ちる件
データに謎の 'A' という型が定義されているのが問題. AMQP の Elementary domains を見たのだけれど 'A' が何か見当たらない…。助けて偉い人orz
とりあえず, amqplib 1.0.2 は, 以下の黒魔術で回避可能. kombu 2.5.0 から amqplib に代わり py-amqp がデフォルトで使用されるのだけれど, そちらはおいおい.
from struct import unpack from decimal import Decimal from amqplib.client_0_8.serialization import AMQPReader def _patched_read_table(self): """ Read an AMQP table, and return as a Python dictionary. """ self.bitcount = self.bits = 0 tlen = unpack('>I', self.input.read(4))[0] table_data = AMQPReader(self.input.read(tlen)) result = {} while table_data.input.tell() < tlen: name = table_data.read_shortstr() ftype = ord(table_data.input.read(1)) if ftype == 65: # 'A' これが新しく加わった!! len = unpack('>I', table_data.input.read(4))[0] ftype = ord(table_data.input.read(1)) if ftype == 83: # 'S' val = table_data.read_longstr() elif ftype == 73: # 'I' val = unpack('>i', table_data.input.read(4))[0] elif ftype == 68: # 'D' d = table_data.read_octet() n = unpack('>i', table_data.input.read(4))[0] val = Decimal(n) / Decimal(10 ** d) elif ftype == 84: # 'T' val = table_data.read_timestamp() elif ftype == 70: # 'F' val = table_data.read_table() # recurse else: raise ValueError('Unknown table item type: %s' % repr(ftype)) result[name] = val return result AMQPReader.read_table = _patched_read_table
Python Web Framework Advent Calendar 2012 (9日目) Django Model で Named Scope
前置き
この記事は、2012 Pythonアドベントカレンダー(Webフレームワーク) - connpass の 9 日目の記事となります。
今回は、Rails の Named Scope の真似を Django Model で実現する方法と、それを利用した論理削除の紹介を行います。
Django Model で Named Scope を実現する
そもそも何をしたいのか?
まず、ECサイトやソシャゲー等のユーザ情報から、最近登録したユーザの中から直近のアクセス順に上位5人を取得する例を挙げます。
import datetime from django.utils.timezone import get_default_timezone # 一週間以内の登録を "最近登録した" とみなす dt = datetime.datetime.now() - datetime.timedelta(weeks=1) # utc でも良いけれど、何となくローカライズ dt = get_default_timezone().localize(dt) User.objects.filter(created_at__gt=dt).order_by('-logged_in_at')[:5]
Named Scope を使用すると、次のようになります。
User.objects.by_newbie().order_by_active()[:5]
Named Scope を作る
では、実際の実現方法を紹介します。
import datetime from django.utils.timezone import get_default_timezone from django.db import models from django.db.models.query import QuerySet # Manager と QuerySet で同様のメソッドを使用するので Mix-in Class として切り出す class UserScopesMixin(object): _newbie_term = datetime.timedelta(weeks=1) def by_newbie(self): dt = datetime.datetime.now() - self._newbie_term dt = get_default_timezone().localize(dt) return self.filter(created_at__gt=dt) def order_by_active(self): return self.order_by('-logged_in_at') # QuerySet に Scope を Mix-in する # 継承順は賛否分かれる所ですが、この記事では、社内の目があるので Mix-in Class を後ろに羅列します(w; # 蛇足ですが、私は、私用で Python を書く場合に限り、Mix-in Class を前に羅列する派です class UserQuerySet(QuerySet, UserScopesMixin): pass # Manager に Scope を Mix-in し, 上記で定義した QuerySet を返すようにする class UserManager(models.Manager, UserScopesMixin): def get_query_set(self): return UserQuerySet(self.model) # 上記で定義した Manager を objects に設定する class User(models.Model): objects = UserManager() created_at = models.DateTimeField(auto_now_add=True, index=True) logged_in_at = models.DateTimeField(auto_now=True) @classmethod def get_active_newbies(cls, limit=5): return User.objects.by_newbie().order_by_active()[:limit]
結局、get_active_newbies() を定義するのであれば、Named Scope なんて不用ではないか?と思われるかもしれません。
しかし、get_active_newbies() の様なメソッドを多数定義する場合、スッキリ書けるのでオススメです。
また、Named Scope が癖になっていると、そもそも QuerySet をカスタム済みであるため、他のカスタム QuerySet を組み込む際に労力が減るという副作用もあります。*1
論理削除の実例
物理的にレコードを削除せずに、削除フラグを立てて削除した事にするアレ。
class LogicalDeleteScopesMixin(object): def by_alive(self): return self.filter(deleted_uuid='') def delete(self): self.update(deleted_uuid=uuid.uuid4(), deleted_at=datetime.datetime.now(pytz.utc)) class LogicalDeleteQuerySet(QuerySet, LogicalDeleteScopesMixin): pass class LogicalDeleteManager(models.Manager, LogicalDeleteScopesMixin): def get_query_set(self): return LogicalDeleteQuerySet(self.model).by_alive() # Mix-in Class であるため、object を継承したいが、 # Django Model の制約により models.Model を継承する必要がある。 class LogicalDeleteModelMixin(models.Model): class Meta: abstract = True class RedeletedError(Exception): pass objects = LogicalDeleteManager() # delete_at を有効/無効の確認に利用すると、 # 一意キー制約を設けた際に、一秒以内の delete が使えないため、 # 有効/無効を判断するための UUID フィールドを設ける。 # 初期値に NULL を指定すると、NULL はレコード毎に異なる値と認識されるため、 # UUID フィールドを一意キー制約に含められない。 # そこで、初期値には空文字列を明示的に指定しておく。 deleted_uuid = models.CharField(max_length=255, db_index=True, default='') # 念のため、記録として削除日時を残しておく。 deleted_at = models.DateTimeField(blank=True, null=True) def delete(self): if self.deleted_uuid: raise self.RedeletedError, self.pk self.deleted_uuid = uuid.uuid4() self.deleted_at = datetime.datetime.now(pytz.utc) self.save()
早速、先ほどの User Model で使用してみましょう。
# LogicalDeleteScopesMixin を継承する class UserScopesMixin(LogicalDeleteScopesMixin): pass # 内容に変更がないため省略 # LogicalDeleteScopesMixin を継承して UserScopesMixin を定義したので # UserQuerySet に変更はない。 class UserQuerySet(QuerySet, UserScopesMixin): pass # by_alive() を使用する必要がある class UserManager(models.Manager, UserScopesMixin): def get_query_set(self): return UserQuerySet(self.model).by_alive() class User(models.Model, LogicalDeleteModelMixin): pass # 内容に変更がないため省略
その他
私は、Python 歴 = Django 歴 = 半年未満であり、Django 以外の他の Python Web Framework の知識は皆無という状態ですが、今回紹介させて頂いた Named Scope や、Class Based View の存在から、Django は OO 設計し易いフレームワークだと認識しており、これからも末永くお付き合いできれば嬉しいなぁ〜と考えております。
参考 URL
ZeroMQ Erlang Binding(NIF) の inproc と Erlang の素のメッセージ送信の速度を比較してみた
コードと結果は下記の通り
https://gist.github.com/3193117
Erlang で作ったサーバに LL で作ったワーカーをぶら下げようと考えており、どうせならナウでヤングな ZeroMQ を間に入れてみようと思い立ちました。
ズボラな私は、ZeroMQ にワーカーのロードバランスをして欲しかったので、Erlang の中で Queue デバイスを使用し、そこに複数の Erlang プロセスから inproc でメッセージを送信しまくる予定でした。
しかし、ここまで素のメッセージ送信と速度に差があるなら、Erlang の中で自前でロードバランスした方が良さそうかなぁと…思い直してます。
ちなみに、inproc を使ってみて初めて気がついたのですが、inproc は他のトランスポートと下記の点で異なります。
- bind と connect に使うコンテキストは同じ物でなければいけない
- connect の前に必ず bind を行う必要がある
100マス計算のシートを生成する
パズル教室にて娘の数学的センスをベタ褒めされたものの、計算速度が遅いので100マス計算を家族でやるようにと指示を受けた。
早速、Python で100マス計算シートを HTML 形式で出力するコマンドを作成したのだが、身近に Haskeller が居るのだから Haskell で書いて添削してもらおうと思い立った。ケーキ一切れで請け負ってくれるだろうか?w;
とりあえず、添削前のコードを晒しておく。添削後には、もっと美しく高機能(例えば HTML で出力する機能が加わったり…)になる予定。
import System.Random import System.IO import Data.List gen_cells :: Integer -> Integer -> IO [String] gen_cells min max = do gen <- newStdGen return $ take 10 $ map (\n -> show n) $ randomRs (min, max) gen i2path :: Integer -> String i2path i = "./" ++ show i ++ ".txt" print_table :: String -> String -> [String] -> [String] -> IO () print_table path method x_cells y_cells = do let head = concat $ intersperse " " x_cells let head_line = method ++ " " ++ head let lines = head_line : y_cells withFile path WriteMode $ \handle -> do sequence_ $ map (hPutStrLn handle) lines mk_html' :: Integer -> String -> (Integer, Integer) -> (Integer, Integer) -> IO () mk_html' n method (x_min, x_max) (y_min, y_max) = do x_cells <- gen_cells x_min x_max y_cells <- gen_cells y_min y_max print_table (i2path n) method x_cells y_cells mk_html :: Integer -> IO () mk_html n | n `rem` 4 == 1 = mk_html' n "+" (1, 99) (1, 99) | n `rem` 4 == 2 = mk_html' n "−" (50, 99) (0, 49) | n `rem` 4 == 3 = mk_html' n "×" (1, 99) (0, 9) | otherwise = mk_html' n "÷" (1, 99) (1, 9) main = sequence_ $ map mk_html [1..20]
早速、リファクタして頂いたのでコードを公開
{-# LANGUAGE TemplateHaskell, QuasiQuotes #-} import System.Random import System.FilePath import System.IO import Data.List import Control.Applicative import Text.Hamlet import Text.Blaze.Html.Renderer.String import Text.Blaze data Method = Plus | Minus | Mult | Div type Range = (Integer, Integer) type Cells = [Integer] type Answers a = [[a]] get_calc_fun :: Method -> Integer -> Integer -> Integer get_calc_fun Plus = (+) get_calc_fun Minus = (-) get_calc_fun Mult = (*) get_calc_fun Div = div method2str :: Method -> String method2str Plus = "+" method2str Minus = "−" method2str Mult = "×" method2str Div = "÷" gen_cells :: Range -> IO Cells gen_cells range = (take 10 . randomRs range) <$> newStdGen calc_answer :: Method -> Cells -> Cells -> Answers Integer calc_answer method x_cells y_cells = let f = get_calc_fun method in [[f x y | x <- x_cells] | y <- y_cells] null_answer :: Answers Integer -> Answers String null_answer answers = map (map (const "")) answers i2path :: String -> Integer -> FilePath i2path p i = "." </> (p ++ show i) <.> "html" -- Integer や String は ToMarkup のインスタンス print_table :: ToMarkup a => FilePath -> Method -> Cells -> Cells -> Answers a -> IO () print_table path method x_cells y_cells answers = do let rows = zip y_cells answers writeFile path $ renderHtml [shamlet| !!! <head> <title>100cells <body> <table> <tr> <th>#{method2str method} $forall x <- x_cells <th>#{x} $forall (y, zs) <- rows <tr> <th>#{y} $forall z <- zs <td>#{z} |] mk_html' :: Integer -> Method -> Range -> Range-> IO () mk_html' n method x_range y_range = do x_cells <- gen_cells x_range y_cells <- gen_cells y_range let answers = calc_answer method x_cells y_cells print_table (i2path "a" n) method x_cells y_cells answers print_table (i2path "p" n) method x_cells y_cells $ null_answer answers mk_html :: Integer -> IO () mk_html n | n `rem` 4 == 1 = mk_html' n Plus ( 1, 99) (1, 99) | n `rem` 4 == 2 = mk_html' n Minus (50, 99) (0, 49) | n `rem` 4 == 3 = mk_html' n Mult ( 1, 99) (0, 9) | otherwise = mk_html' n Div ( 1, 99) (1, 9) main = mapM_ mk_html [1..20]
Z会三年生中学受験コース5月のてんさく問題を Python で解いてみた
妻と娘から次の質問をされた。
4けたの数について、それぞれの位の数字を大きいじゅんにならべた数から小さいじゅんにならべた数をひくという計算を行います。
1974 について、この計算を 100 回行った答えを書きなさい。
転職先の会社で Python を使うことになっているが、今まで座学ばかりで一行もコードを書いていなかったので、試しに妻と娘の前でライブコーディングをしてみた。
n = '1974' for c in range(100): n = str(int("".join([x for x in reversed(sorted(n))])) - int("".join(sorted(n)))) print "%s: %s" % (c, n)
reversed が破壊的だった所に少しハマった。
出力結果は下記の通り。
$ python test.py 0: 8262 1: 6354 2: 3087 3: 8352 4: 6174 5: 6174 // 以下略
って事で答えは 6174 でしたと。
転職します
本日(2012年5月31日)をもって、現在お世話になっている会社を退社し、明日から別の会社に入社します。
他所様に伝えるべき何かを持ち合わせてはいないのですが、私に職を紹介してくれたN氏、現職の方々、次職の方々への私信という事で慣れない筆を執りました。
現職について
参入障壁が高く競合他社が少ない、安定した収入を確保できる職場を去るにあたり、様々な葛藤がありましたが、解くべき問題の難易度が低いという理由だけで転職を決めました。ありがちな理由で簡単に詳細を予測できる内容ではありますが、少しだけ補足します。
勤続7年(グループ会社通算で10年)*1の中で、サービスを安定稼働させるために様々な試行錯誤を重ねてきました。あまり詳しい話は守秘義務があるためできませんが、俗にいう上流から下流工程まで様々な事に手を入れてきました。しかし、人間には趣味嗜好があり、私という人間がプログラミングに喜びを感じる性格である都合上、ついプログラミングに比重を置いてしまう傾向があります。過去、本ブログに書き綴った内容も8割以上がサービスの安定稼働という一念あって書き綴ったものですが、ほぼプログラミングに特化した内容となっています。そうやってプログラミングに偏重していくと「現状」のサービスの安定稼働という目標を達成するための十分な技術を獲得しているにも関わらず、プログラミング技術への枯渇感が満たされないので、更にプログラミング技術を追い求め、いつしか「難しい」問題は、社内外の政治的な問題だけになっていました。現行のサービスの限界を越える案件が発生した際、現行のサービスの同等品を Erlang を用いて一人/二ヶ月でフルスクラッチで構築する段階になると、かなり傲慢な考え方ですが、現職に留まる限り、プログラミングで解決するべき難しい問題には出会えない…という考えに確信を持つに至りました。勿論、自ら問題を創出するという方法もあるとは思いますが、その場合、地位*2か企業内起業のどちらかが必要となるので、難しい問題を抱えている職場を探した方が早いだろうと判断しました。
勿論、世の中、きれいごとばかりではありませんし、在職中に不愉快な思いをした事もあります。しかし、今回の転職に前述以外の他意はありません。それが引き金だったのではないかと心配されている方々もいらっしゃるでしょうが、その程度の事で職を放棄したりしません。良い上司・同僚・部下に恵まれ、素晴らしい環境の中で仕事に集中できた事に関しては疑う余地がありません。もし関係があるとしたら、政治的な理由でプログラミングの時間を削られてしまったという箇所のみです。とは言え、辞めるのは確かであり、社内外問わず関係者の方々に多大なるご迷惑を掛けました事を、ここにお詫び申し上げます。誠に申し訳ございませんでした。
また、多々ご迷惑を掛けたにも関わらず、現職の社長が次職の社長に対して、私の事をよろしく頼むという一報を入れてくれたり*3、10年間、本当の意味で私と苦楽を共に*4してきた取締役が、私を修行に出すだけというスタンスで送り出してくれた事により、私の中の罪悪感を打ち消してくれたりと…、転職が決まった後も様々な形でご支援頂き、現職の方々に対しては、本当に感謝の念に堪えません。今まで、本当にありがとうございました。
次職について
N氏の Erlang 雇用を創出するという陰謀に加担する予定です。希望としては、(人的にもサーバ的にも)少ないリソースで大量トラフィックを捌くサービス基盤やログ解析基盤を構築する事に注力したいと考えていますが、現行サービスとの折り合いを見つつ臨機応変に対応したいと考えています。とは言え、研究投資とは名ばかりのお荷物確定の働きっぷりでは信用を得られないので、いきなり (」・ω・)」Erlang!(/・ω・)/Scala!*5 と騒ぐつもりは無く、現職同様に少しずつ侵略していければ良いと考えています。そんな呑気な事では給料を払う意味が無い!と次職の上司にお叱りを受けそうで怖いのですが…、そこは、お手柔らかにお願い申し上げます。
さて、明日の初出社に備えて寝ます。おやすみなさい。