愛と勇気と缶ビール

ふしぎとぼくらはなにをしたらよいか

PostgreSQLのLISTEN/NOTIFYでJSONデータを飛ばす

PostgreSQLではJSON, JSONBデータ型のサポートがあります。MySQLも5.7からあるようです。

アプリに対してちょっとした通知を飛ばしたくなったのですが、ちょっとした通知のために別のミドルウェアを導入するのは明らかに筋が悪いのでPostgresのLISTEN/NOTIFYを使うことにしました。

PostgresのLISTEN/NOTIFYはtext型しか渡せないので、ここは構造化されたデータををJSONなどで渡したくなるのが人情というものです。

以下のようなTRIGGERと

DROP TRIGGER IF EXISTS my_table_inserted ON my_table;

CREATE TRIGGER my_table_inserted
    AFTER INSERT ON my_table
    FOR EACH ROW
    EXECUTE PROCEDURE on_my_table_inserted();

以下のようなFUNCTIONで人情を達成します。

CREATE OR REPLACE FUNCTION on_my_table_inserted() RETURNS trigger AS $$
BEGIN
    PERFORM pg_notify('my_table_inserted', json_build_object(
        'id', NEW.id,
        'data_1', NEW.data_1,
        'data_2', NEW.data_2
    )::text);
    RETURN NEW;
END;
$$ LANGUAGE 'plpgsql';

結局のところ送っているのはtext型なのですが、受け取る側でJSON decodeすれば問題ないです。

Pythonで非同期に通知を受け取るコードは別記事に書きます。

完全に余談ですが、現状のPipelineDBではTRIGGERとLISTEN/NOTIFYをこのように組み合わせてCONTINUOUS VIEWの更新通知を受け取ることは出来ません。 CONTINUOUS TRIGGERの実現方法が原因だと思うのですが、多分そのうち何とかなると思います。

PostgreSQLちょっといい話

最近、PostgreSQL(というかPipelineDB)をいじる機会があったのでメモ。

RDBMSにあるとうれしい、というかPostgresにあって「これ良いな」と思った機能を適当に列挙するの回。

CREATE DOMAIN

要はtypedefが出来る。

CREATE DOMAIN company_code smallint;

これで型の別名を定義して、table定義でこの別名を使うことにより

  • 開発中に型の定義を「やっぱこっちで」と切り替えるのが簡単になる
  • 本来同じ型の値を使うべきtable間でうっかり定義がずれてしまうことを抑止できる

などなどのメリットが得られる。地味な機能だけど割とうれしい。

Lateral Join

Reuse Calculations in the Same Query with Lateral Joins

くやしくはリンク先を読んでほしいのだけど、要はカラムの値を元にした計算結果を使って更に何らかの値を計算したいような場合に、まあまあ簡便な記法でクエリが書けるということ。 集計とかでうれしい。

RETURNING

MySQLだとlast_insert_idとかを使うしかないが、RETURNINGを使うとINSERTやUPDATEした後に任意の値を返せる。よい。

思ったよりネタがなかった。

Pythonで async def / def 両対応のデコレータを書く

タイトルの通り。

単純に以下の様なdecoratorを書くと、async defをラップできない。

from functools import wraps
from datetime import datetime

def timetrack(func):
    @wraps(func)
    def inner(self, *args, **kwargs):
        start = datetime.now()
        return_value = func(self, *args, **kwargs)
        end = datetime.now()
        self.logger.info('%s takes %s sec', func.__name__, (end - start).total_seconds())
        return return_value
    return inner

かといって次のように書くと、今度は普通のdefが勝手にcoroutine functionになってしまって困る。

from functools import wraps
from datetime import datetime

def timetrack(func):
    @wraps(func)
    async def inner(self, *args, **kwargs):
        start = datetime.now()
        return_value = await func(self, *args, **kwargs)
        end = datetime.now()
        self.logger.info('%s takes %s sec', func.__name__, (end - start).total_seconds())
        return return_value
    return inner

なので、asyncio.iscoroutinefunctionを使って以下のように分岐するといける。

from functools import wraps
from datetime import datetime

def timetrack(func):
    if asyncio.iscoroutinefunction(func):
        @wraps(func)
        async def async_inner(self, *args, **kwargs):
            start = datetime.now()
            return_value = await func(self, *args, **kwargs)
            end = datetime.now()
            self.logger.info('%s takes %s sec', func.__name__, (end - start).total_seconds())
            return return_value
        return async_inner
    else:
        @wraps(func)
        def inner(self, *args, **kwargs):
            start = datetime.now()
            return_value = func(self, *args, **kwargs)
            end = datetime.now()
            self.logger.info('%s takes %s sec', func.__name__, (end - start).total_seconds())
            return return_value
        return inner

一般化したユーティリティみたいのを書けないこともない気がするけど、今回そこまでのモチベーションはなし。

Pythonのasyncioで(結果を待たない非同期実行|投げっぱなしジャーマン|fire and forget)する

Pythonは3.5から、C#みたいなasync/awaitを用いた非同期処理が可能になっている。

これ系のパターンでは「非同期処理を待つ」ためにawaitキーワードを使い、「(awaitを含む) 非同期処理が行われる関数をマークする」ためにasyncキーワードを使う。非同期処理の終了は行儀よくawaitで待ち受けるのが普通だが、時には単に非同期処理を投げっぱなしにしたいこともある。ログを書くとか、通知を飛ばすとか。

C#においてはasyncかつ返り値をvoidにすればawaitせずにコールできる非同期メソッドが書ける(逆に言うと、awaitする必要がある非同期メソッドはTaskかTaskを返すということになっている)のだが、pythonだとぱっと見どうすれば同じことが出来るのか分からない。その上、われわれ日本人にはどういう単語で検索すればいいかよくわからない。

知ってる人は「fire and forget」とかで検索すればいいのだが、これは一度どこかで見て覚えていなければどうしようもない類の知識なので、わざわざ日本語で検索しやすいようにとこんな記事を書いているのだ、という長い前置き。

次のようにすればオッケー。(以下のコードはそのままコピペして動くものではなく、あくまで「例」)

import asyncio

async def hogehoge():
     pass

asyncio.ensure_future(hogehoge())

使いようによってはensure_futureには落とし穴があったりするんだけど、それはまた別の機会に。

Fire OS 5.1.4でmicroSDカードにKindle本を保存できるようになってた

f:id:zentoo:20160609214328p:plain

分かる人にとってはタイトル見ただけで「アッー」という内容。

一応説明をすると、Kindle Fire系で安めの端末は内蔵ストレージの容量の少なさがネックになっていた。一応microSDカードで増設できたものの、「Kindle本は保存できない」「アプリも一部しか保存できない」という大きな制限があり、漫画など容量の大きな本をガンガンDLしたい、という向きは高めのお金を払ってハイエンドな端末を買うしかなかった。(ハイエンドなKindleって存在が微妙だけど、それはそれとして)

で、今回の制限突破によりお値段お安め系のKindleにmicroSDカード挿して漫画はそれで読む、という運用が一気にしやすくなった。

キャンペーン時に4000円くらいでFire買って、

Fire タブレット 8GB、ブラック

Fire タブレット 8GB、ブラック

64GBのmicroSD挿しておいた

ワイ大勝利。