信じられない地震

被害にあわれた方には本当になんと申し上げたらいいかわからないくらい、容赦のない津波でした。巨大なエネルギーの塊が押し寄せ、何もかもさらっていく異様な光景に言葉を失います。とても現実のものとは思えませんでした。

地震当日は早々に仕事を切り上げ、2 時間ほど路線バスを乗り継いで実家に辿りつき、一晩を過ごしました。母は相変わらず抗癌剤がキツく、ぐったりしていましたがその他は位牌が倒れた程度で済みました。

JR の在来線が復旧したあと、昨日夕方に自宅に戻ることができましたが、いまのところ被害と言えそうなのは iMac に繋げている FireWire の HDD が壊れたことぐらいです。あとは、壁面収納の本が全て飛び出たり、コンロの上から鍋がふっ飛んだりしましたが、家族にケガはありませんでした。

娘の恐怖心は相当なもので、余震の度にしがみついてきます。やや赤ちゃんがえりしている模様。それでも、安心して眠れる家があるということが、なんと恵まれていることか。

今日はだらだらと部屋の片付けをして一日を過ごしました。外に出て遊ぶ気にもならず、かと言って、テレビの報道を見ても気持ちが落ち着くわけでもなく、食欲もなく、頭に鉛が詰められたかのような重苦しさと無力感。

今はただ、被災者の方々の一日も早い復旧を願うばかりです。

仕事納め

今日は仕事納めでした。

業績不振は続いてますが、徐々に案件が増えてきているので、来年もガツガツ頑張りたいところです。今は個人的に好きなタイプのプロジェクトを手掛けていることもあり、仕事面での気力は充実しています。

AquaSKK については完全に停滞している状態で、辞書共有サービスも塩漬けになってます。このまま放置するのも気持ち悪いので、来年はなんとか 4.2 のリリースまで持っていって、一旦、区切りをつけたいですね。

今年もお世話になりました。皆様、よいお年を。

近況

我ながら呆れますが、この年になって水疱瘡にかかりました。

二週間前に感染した娘がようやく落ち着いたと思ったら、いつの間にか赤いポツポツが全身に出現。確かに、先週末は微熱や倦怠感で何かおかしいと感じていたのですが、まさか水疱瘡とは思いませんでした。

毎食後に飲む抗ウィルス薬とかゆみ止めを五日分、さらに患部に直接塗る「カチリ」という白い薬を処方されました。このカチリ、見た目と匂いは水性絵の具にそっくりです。乾いて固まると患部を保護する働きがあるとか。

というわけで、今週は外出禁止で自宅待機です。くちさがない知人からは「大人になってからの水疱瘡は痕が残るよ〜」と脅されていますが、今のところ薬が効いているせいか、さほどひどくなっていません。

思いがけずまとまった時間が取れたので、先走り的に大掃除を始めました。つい最近、ようやく HDTV を購入したこともあり、あーでもないこーでもないとレイアウト変更にも着手。

ただ、そうやって部屋を歩きまわっていると、乾燥したカチリの破片がボロボロとはがれ落ちていくので、結局また掃除をやり直す羽目に。

奥さんの白眼視が背中に痛い今日この頃です。

辞書共有サービスの初期登録を改善

ずいぶん時間が空いてしまいました。今月末で 2 年近く続いたプロジェクトにようやくケリがつくので、一息つけそうな感じです。暑さもやわらいできたので、どこか旅行にでも行きたい気分。

辞書共有サービスのほうは、課題になっていた初期登録が解決しそうなので、今日はそのことについて書いてみます。

前回のあらすじと顛末

辞書共有サービスにはユーザー辞書の初期登録が必要だろうということで実装してみたところ、見出し語 4,000 件程度の登録に 8 分もかかることが判明。このままではとても使い物にならないので、辞書ファイルをアップロードした後、登録処理を cron で流すという二段階方式に変更。しかし、結局 cron でも実行時間が制限され、登録処理は無情にも打ち切られるのであった......

ということで、残念ながら、根本的に設計を見直すことになってしまいました。一番時間のかかる処理はデータストアへの書き込みなので、これをなんとか減らす以外に道はなさそうです。

見出し語を集約する

見出し語一つに 1 エンティティを使う素朴な設計では書き込みが多すぎるわけですから、1 エンティティに複数の見出し語を集約することを検討してみます。そのためにまず、集約される側のクラスと、集約する側のクラスを定義します。

class SKKEntry:
    # 見出し語と候補群、更新日時などを保持する

class SKKEntryCollection:
    # SKKEntry を辞書(dict)として保持する

次に、SKKEntryCollection をデータストアに格納するためのモデルプロパティを定義します。

class SKKEntryCollectionProperty(db.Property):
    data_type = SKKEntryCollection
    
    # 書き込み時変換
    def get_value_for_datastore(self, instance):
        base = super(SKKEntryCollectionProperty, self)
        value = base.get_value_for_datastore(instance)
        return db.Blob(pickle.dumps(value))
    
    # 読み込み時変換
    def make_value_from_datastore(self, value):
        return pickle.loads(value)

SKKEntryCollection がデータストアに書き込まれる時、pickle でシリアライズされ、バイナリデータになります。データストアから読み込まれる時にはバイナリデータから SKKEntryCollection が復元されます。これを使って最終的なモデルを定義することができます。

class SKKDictionary(db.Model):
    owner = db.StringProperty(required = True)
    collection = SKKEntryCollectionProperty(default = SKKEntryCollection())

実にすっきりとしたこのモデルの致命的な欠点は、検索条件に owner しか利用できないことです。

例えば、ある日時以降に更新された見出し語を含むエンティティを検索する、といったことができません。かわりに、各エンティティを列挙して collection のメソッドを逐一呼び出すループが必要になります。これは虚しい。けれど、仕方がない。トレードオフですからね。

独自のプロパティに対するリテラルや比較メソッドを定義できるようになるといいのでしょうが、そこが開放されるのは、かなり先のような気がします。

初期登録の実装

エンティティに見出し語を集約するにしても、上限は必要です。エンティティのサイズにも制限があるからです。そこで 1 エンティティあたり仮に 16384 個まで集約することにし、この上限を超えた場合には新しいエンティティを用意する方向で考えてみます。

このあたりの処理は煩雑になりそうなので、ひとまず初期登録をサポートするクラスとして実装してみます。

class WorkDictionary:
    def __init__(self, owner):
        self.owner = owner
        self.collections = {}
        
        query = db.Query(SKKDictionary)
        query.filter('owner = ', owner)
        
        for model in query:
            self.collections[model.key()] = model.collection
    
    def update(self, entry):
        # マージ可能ならマージ
        for collection in self.collections.itervalues():
            if entry.word in collection:
                collection.update(entry)
                return
        
        # 追加可能なら追加
        for collection in self.collections.itervalues():
            if len(collection) < 16384:
                collection[entry.word] = entry
                return
        
        # 新しいコンテナを用意して追加
        collection = SKKEntryCollection()
        collection[entry.word] = entry

        # ドイヒー
        self.collections[len(self.collections)] = collection
    
    def save(self):
        for (key, collection) in self.collections.iteritems():
            model = None

            # ドイヒー            
            if type(key) is not int:
                model = SKKDictionary.get(key)
            
            if model is None:
                model = SKKDictionary(owner = self.owner)
            
            model.collection = collection
            model.put()

定数が埋め込まれていたり、self.collections の扱いが乱暴だったり、色々と投げやりな点はありますが、このクラスを使うと初期登録のアップロード処理は以下のようになります。

    def post(self):
        user = users.get_current_user()
        post_data = self.request.get('user_dictionary')
        lines = filter(lambda x: not x.startswith(';'), post_data.splitlines())

        work = model.WorkDictionary(user.user_id())
        
        for line in lines:
            (word, delim, candidates) = line.strip().partition(' /')
            entry = model.SKKEntry(word, self.split_candidates(candidates))
            work.update(entry)
        
        work.save()
        
        ...

これを実際にローカルで動かしたところ、4,203 件の初期登録が 1 秒以内にまで短縮されました。データストアへの書き込みがいかに重いか、ということがわかりますね。

ちなみに、この処理を繰り返し実行してもパフォーマンスは一定でした。2 回目以降は候補のマージが発生するので多少遅くなるかと思っていたのですが、この程度の件数ではほとんど影響しないようです。

まとめ

見出し語を集約してデータストアへの書き込みを減らすことで、パフォーマンスが劇的に向上しました。しかし、そのためには本来ならやらなくても良いはずの泥臭い処理が必要になります。痛し痒しといったところでしょうか。

次は、このモデルに合わせて同期サービスを修正したり、ドイヒーなところを直してみようかと思います。

つぶやくよりも、キスをしよう

今のところ僕は Twitter をやってません。それは別にご大層なポリシーがあるわけではなくて、ただ単に、今はそういう気分ではないからです。Twitter アカウント開設のお誘いメールを頂いたりするようになって、申し訳ないなと思ったりもするのですが、そういうわけで、ごめんなさい。

最近は、仕事を離れたら計算機からも離れるという生活スタイルになりつつあります。AquaSKK 関連の開発だとか、調べ物はやりますけど、以前ほど没入することはないです。移動中にネットワークを使うなんてこともまずありません。会社で tweet するわけにもいかないので、そうすると自然と、まあいいか、という気分になっていきます。

そういう意味ではちょっとベクトルは違うんですけど、The New York Times のコラム「Tweet Less, Kiss More」の主張に近いところがあるのかもしれません。

http://www.nytimes.com/2010/07/17/opinion/17herbert.html?_r=1

僕の場合は結果的にこうなってるだけですけど、今のところ、それで充分なのです。

こうやって人は時代から取り残されていくのかもしれませんね ;-)

辞書共有サービスの進捗

辞書共有サービスの開発に着手して随分時間が立つので、そろそろ最近の状況についてお知らせします。

今回開発しているのは、大きく四つの部分に分かれます。

  1. 辞書共有サービス本体(google app engine)
  2. 同期クライアント(python)
  3. AquaSKK 用アダプタ
  4. Emacs 用アダプタ

1. 辞書共有サービス本体

今のところ、以下の機能を実装しています。

  • トップページ
  • 接続トークンのダウンロード・再発行
  • ユーザー辞書の初期登録
  • cron ジョブ
  • 同期クライアント向け Web サービス(登録、取得)

基本的に app engine は辞書データを蓄積するのが目的なので、華美な見た目もなく、ひたすらシンプルな感じです。今の問題点は辞書の初期登録処理に時間がかかりすぎることです。

ローカル環境で試しただけですが、3,600 エントリ程度で 8 分前後かかります。なので、辞書ファイルを一旦アップロードした後、別途 cron で登録処理を走らせるという二段階の仕組みになっています。

本番環境でもパフォーマンスが悪いようなら、初期登録の方法そのものを根本的に考え直す必要が出てくるかもしれません。タスクを細分化するなどして CPU 使用時間を平均化しないと、制限に引っかかるような気がします。

2. 同期クライアント

実装はほぼ完了しています。

  • 各種 SKK 実装向けのインタフェース(検索、補完、登録、削除)
  • 辞書共有サービスとの連携

SKK 実装にインタフェースを提供しつつ、非同期で辞書共有サービスと連携します。ローカルのデータは SQLite で保持します。SQLite をまともに使うのは今回が初めてだったのですが、セットアップは簡単だしパフォーマンスも良いしで、好印象を持っています。素晴しい世の中になったものです。しみじみ。

3. AquaSKK 用アダプタ

AquaSKK と同期クライアントを橋渡しする部分です。AquaSKK 内部ではユーザー辞書のように振舞います。基本的なやりとりは実装済みなので、あとはユーザー辞書と切り替えできるようにする程度ですね。

4. Emacs 用アダプタ

ddskk と同期クライアントの通信に必要になります。未着手です。ddskk のユーザー辞書と切り替え可能な方向で行くのか、フックとして補助的な感じで行くのか、決めかねています。

まとめ

充実したライブラリと SQLite のおかげで、コードは単純になっているし、初期登録を除けば、まあ順調だと思います。python の流儀もだいぶ飲み込めてきました。他の言語との概念的な共通点が多いので、言い回しに慣れれば、それほど苦労しないかなと。あと、SocketServer.ThreadingTCPServer のような部品もあるので、一通りドキュメントに目を通しておくことが大事ですね。

進捗が悪いのは、気が向いた時だけ作業する、という最近のスタイルのせいです。毎日必ず時間を取るようにすれば改善されるんでしょうけど、人生というのは、ままならないものなのですからね ;-) 母の病気以降、思うところがあり、時間の使い方については試行錯誤が続いています。

なお、辞書共有サービスをリリースする時には、AquaSKK に同期クライアントを同梱するつもりです。当然、AquaSKK 内部から同期クライアントも起動できるようにし、手間を軽減したいです。

なんとか、今年中にリリースできればいいなと思っています。

読書: DRiVE

長期的な生き甲斐は、お金よりも、自立と成長と貢献への欲求を満たすことで高まるよ、というお話。

Drive: The Surprising Truth About What Motivates Us

Drive: The Surprising Truth About What Motivates Us

「もし〜できたら、〜をあげるよ」といったふうに、お金やご褒美などをチラつかせて人を駆り立てるのは、アメとムチで知られる一般的なマネージメント手法です。著者はこれを Motivation 2.0 と呼び、こういった外的動機が適切な場合と、そうでない場合を説きます*1

例えば、ある種の単純な作業に対しては、Motivation 2.0 は有効に機能します。しかし、非定型的で創造的な仕事に対しては、うまくいかない。それどころか、悪い結果を生み出すケースもある。

現代社会では、単純作業が外部委託されたり、IT の普及により自動化された結果、頭を使う仕事の比率が高まっています。こういう状況の中で、古い Motivation 2.0 を使い続けるのはふさわしくない。そこで著者は、各個人の内側から生じる動機を行動の原動力としていこうという Motivation 3.0 を提唱します。

ある行動に対する内的動機を高めるには三つの条件があります。一つは、行動が自立的であること、ある結果を達成する過程における自由度が高いこと。次に、ある行動に習熟することでより良い結果を生み出せること、上達による成長を実感できること。そして最後に、その行動の目的や結果が社会貢献や他者の幸せに関わっていること。

この三つの条件を満たせるような環境を作り、各個人の内的動機を信頼・尊重することで、外的動機では為し得ないような結果を達成することができるとしています。20% ルールなどはまさに、これを実践しているわけですね。

AquaSKK のメンテナとして、自立、成長、貢献の各側面における充実は感じているところなので、個人的には、大いにうなずける内容でした。ただ、Motivation 3.0 が向く職場もあれば、そうでないところもあると思います。特に日本では、個人の自立性よりも協調性を求められる場面が多いんじゃないでしょうか。難しいところですけど、Motivation 3.0 的な感覚を持つ人が増えてくれば、だんだんと変わっていくんでしょうね。

素朴な疑問としては、ウォール街のような場所では、Motivation 2.0 が未だに強力に機能しているように見えることです。最近では High Frequency Trading System などのコンピュータによる超高速売買が台頭してきているようなので、将来的には変わっていくのかもしれませんが、高額報酬を目当てにガンガン働いて成果を出していく人は、いつの時代も一定数いるのかなと。

それから、既得権益に群がる人々というのも、いかにして既得権益を守るか、という方面において実に素晴しい頭のキレと粘り強さを見せていると思います。その背後には Motivation 3.0 とは違った秘密が作用していると思うのですが、本書ではそのあたりの疑問に答えていません。

関連して、内的動機の存在は科学的に証明されていないよ、という研究者の話もあったりします。

http://www.sciencedaily.com/releases/2005/05/050509173611.htm

人の行動原理は実に様々なので、内的動機と外的動機の二つで全てが説明できるわけではない、冷静になりましょう、といった内容。

そしてこちらは、DRiVE の中でも紹介されている Motivation 3.0 的企業 Zappos 創業者が語る、Amazon への身売りに関する顛末。企業文化を守るため、長期的な視野に立って決断をする。なかなかカッコイイですが、起業文化も行き過ぎると宗教と区別がつかなくなりそうな感があります。

http://www.inc.com/magazine/20100601/why-i-sold-zappos.html

七夕には邦訳も出るようですので、日本語が得意な自己啓発マニアは手に取ってみるのも良いかもしれません。

モチベーション3.0 持続する「やる気!」をいかに引き出すか

モチベーション3.0 持続する「やる気!」をいかに引き出すか

時間がないなら、紹介動画でも十分楽しめます。

*1:ちなみに Motivation 1.0 というのは、生命を脅かす危機を回避しようとする行動原理のことだとか