PostgreSQL は MySQL などと同様に BEFORE/AFTER トリガーをサポートしており SQL や PL/Python がインストールされていれば Python を実行できる。また、Redisのような Pub/Sub機能(NOTIFY/LISTEN)があり、トリガーと組み合わせることでデータ変更をリアルタイムで通知できる。
CREATE FUNCTION notify_change() RETURNS TRIGGER AS $$
BEGIN
PERFORM pg_notify('data_changes', json_build_object('table', TG_TABLE_NAME, 'id', NEW.id)::text);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER users_notify
AFTER INSERT ON users
FOR EACH ROW
EXECUTE FUNCTION notify_change();
LISTEN data_changes; -- from another session
INSERT INTO users (name) VALUES ('Alice');
-- Notified: {"table": "users", "id": 1}
これにより Aurora のような PL/Python をインストールできない DB でもプログラマティックにトリガーをハンドリングできる。
$ PGPASSWORD='*****' psql -h *****.cluster-*****.ap-northeast-1.rds.amazonaws.com -U postgres -d testdb -c "SELECT * FROM pg_extension;"
oid | extname | extowner | extnamespace | extrelocatable | extversion | extconfig | extcondition
-------+---------+----------+--------------+----------------+------------+-----------+--------------
14501 | plpgsql | 10 | 11 | f | 1.0 | |
(1 row)
$ cat listen.py
import psycopg
conn = psycopg.connect(
host="*****.cluster-*****.ap-northeast-1.rds.amazonaws.com",
dbname="testdb",
user="postgres",
password="*****",
autocommit=True
)
conn.execute("LISTEN data_changes;")
try:
for notify in conn.notifies():
print(f"Received: {notify.payload}")
except KeyboardInterrupt:
print("\nStopped listening")
finally:
conn.close()
ただこの方法で変更を受け取ると常に通知を待ち構えている必要があり接続が切れると取り漏らすことになる。
Aurora には aws_lambda extension があって、これをインストールすると Lambda を呼び出すことができる。
CREATE IF NOT EXISTS EXTENSION aws_lambda CASCADE;
invoke するための権限を付与する。
const auroraLambdaRole = new iam.Role(this, 'AuroraLambdaRole', {
assumedBy: new iam.ServicePrincipal('rds.amazonaws.com'),
});
testFunction.grantInvoke(auroraLambdaRole);
const cfnDbCluster = dbCluster.node.defaultChild as rds.CfnDBCluster;
cfnDbCluster.associatedRoles = [{
roleArn: auroraLambdaRole.roleArn,
featureName: 'Lambda',
}];
あとはトリガーで aws_lambda.invoke() を呼べば常に待ち構えて接続を張りっぱなしにする必要がなくなる。
\x
SELECT * FROM aws_lambda.invoke(
aws_commons.create_lambda_function_arn('(function_name)', 'ap-northeast-1'),
'{"message": "Hello from Aurora"}'::json
-- , 'Event' /* async */
);
-[ RECORD 1 ]----+----------------------------------------------------------------------------------------------------------------------
status_code | 200
payload | {"statusCode": 200, "body": "{\"message\": \"Hello from Lambda!\", \"event\": {\"message\": \"Hello from Aurora\"}}"}
executed_version | $LATEST
log_result |