Location via proxy:   
[Report a bug]   [Manage cookies]                

わたねこコーリング

野良プログラマ発、日々のアウトプット

読みにくい JSON データは yq で YAML に変換!

JSON データとらめっこしたり値を拾ってコピペしたり、なんて作業をしてると(主に AWS-CLI の出力だったりしますがw)、以下の理由でちょっとつらみを感じたりします。

  • URI 文字列等はエスケープが多くて、コピペの際に加工が必要になる。
  • めっちゃ長いデータがブラケットや閉じ括弧で余計に見た目長くなる。

そんな時「YAML だったら少しは読みやすくなるのになぁ、あそうだ、jq があるんだから yq もきっとあるよぬ」ってなって発見したのがこれ。

github.com

jq に YAML/XML/TOML のプロセッシング機能を追加した、Python 製ラッパーだそうです。持ってると幸せになれそうなので、早速インストールしてみます。

pip 以外でも apt, dnf, brew 等のパッケージマネージャからもインストールできそうですが、ちょっと試すだけなので Docker コンテナにしておきます。LinuxServer.io からイメージが提供されてるので、こいつを採用。

インストールと言っても、docker pull linuxserver/yq してから ~/.bashrc や ~/.bash_profile 等に以下のエイリアス設定を追記するだけ。

alias yq='docker run -i --rm -v "$PWD:$PWD" -w="$PWD" --entrypoint yq linuxserver/yq'

さて、動作確認用のサンプルとしてこんな JSON ファイルを用意します。

{
  "name": "竈門炭治郎",
  "age": 15,
  "address": {
    "city": "東京",
    "country": "日本"
  },
  "styles":  [
    "水の呼吸",
    "日の呼吸"
  ],
  "friends": [
    {
      "name": "我妻善逸",
      "style":  "雷の呼吸"
    },
    {
      "name": "嘴平伊之助",
      "style":  "獣の呼吸"
    }
  ]
}

YAML に変換してみます。

yq -y . sample.json

はい、できました。

name: 竈門炭治郎
age: 15
address:
  city: 東京
  country: 日本
styles:
  - 水の呼吸
  - 日の呼吸
friends:
  - name: 我妻善逸
    style: 雷の呼吸
  - name: 嘴平伊之助
    style: 獣の呼吸

オプション -y の代わりに -Y にすると、値がフロースタイル表記になります↓。コンパクトにはなりますが、読みやすさや文字列エスケープの点で利点が損なわれるので、ケースバイケースですね。

name: "竈門炭治郎"
age: 15
address: {city: "東京", country: "日本"}
styles: ["水の呼吸", "日の呼吸"]
friends: [{name: "我妻善逸", style: "雷の呼吸"}, {name: "嘴平伊之助", style: "獣の呼吸"}]

という訳で、使用感はイイ感じでした。ドキュメントはこちら↓から。

kislyuk.github.io

ClickHouse で億単位レコードの集計をざっくりベンチマーキングしてみた


TL;DR

OLAP 向け DBMSClickHouse の、実際のパフォーマンスについての記事をあまり見かけなかったので、実際に1億超のレコードを持つテーブルで集計問い合わせしてみた、という話です。

背景

業務で携わっているサービス(AWS にて運営)にて提供しているアクセス集計機能が、年々のデータ増加によりじゅうぶんなレスポンスタイムを得られなくなって来ている問題に遭遇しています。アクセスデータは RDS for MySQL に集約してあり、集計が重くなってくる度に中間集計等で最適化を図って凌いできましたが、そろそろ限界みたいです。

そもそも、こういう用途に RDBMS を用いる事じたいが選定ミスというのは当初から判ってはいたので、他の OLAP 向きなソリューションを模索してはいました。AWS Redshift も試してみたのですが料金的にサービスのサイズと釣り合わないかもだし、統合的なデータウェアハウスをやりたい訳じゃなく、もうちょっとサクッとビッグデータ集計だけできちゃう OSS 製品とかない? と思ってたところ、ClickHouse の存在を知りました。少し調べたところ、

  • OLAP 用途を想定した、Redshift 等と同じく列指向の DBMS
  • RDBMS っぽい SQL が、ほぼそのまま使える。
  • 水平スケーリングに対応。
  • 公式 Docker イメージが提供されている。

ということが判り、比較的サクッと評価作業できそうです。そんな訳で、現状で問題が発生しているテーブルをそのまま移植して、同じクエリでどれだけのパフォーマンスが得られるかをゴールにして触ってみることにしました。

準備

では、実際に ClickHouse サーバを立ててみます。実験場所として、t3a.large クラスの EC2 インスタンスを1台起動して作業ディレクトリを用意↓。ストレージは16GB以上は用意したほうが良さげ。尚、EC2 インスタンス内で docker を使えるようにしたり、DB に接続するための設定手順は割愛します。

mkdir -p clickhouse/ch_data
cd clickhouse

ここで開発元から提供されている Docker イメージを使ってコンテナを起動します。Compose ファイルはこんな感じ。

services:
  ch:
    image: clickhouse/clickhouse-server
    container_name: some-clickhouse-server
    volumes:
      - type: bind
        source: "./ch_data"
        target: "/var/lib/clickhouse/"
    ports:
      - "8123:8123"
    ulimits:
      nofile:
        soft: 262144
        hard: 262144
    environment:
      - TZ="Asia/Tokyo"

docker compose up -d でコンテナが起動したら、下記のようにクライアントから接続してみて動作確認。MySQL と同様に quit で抜けられます。

docker exec -it some-clickhouse-server clickhouse-client

では、ベンチマーク用テーブルの用意を。このテーブルは、実際のネットサービスにてトラッキング収集したデータを一定条件で集計した結果を格納する為のものですが、話を分かりやすくする為に「ネット投稿記事閲覧の時間単位集計」としておきます。MySQL 側ではこんな DDL です。

CREATE TABLE `post_view_total` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `author_id` int unsigned NOT NULL COMMENT '著者ID',
  `post_id` int unsigned NOT NULL COMMENT '投稿ID',
  `category` varchar(20) NOT NULL COMMENT '記事カテゴリー',
  `platform` varchar(20) NOT NULL COMMENT 'プラットフォーム種別',
  `ymd` date NOT NULL COMMENT '閲覧日付',
  `hour` int NOT NULL COMMENT '閲覧時間帯',
  `views` int unsigned NOT NULL COMMENT '閲覧回数',
  PRIMARY KEY (`id`),
  KEY `author_id` (`author_id`),
  KEY `post_id` (`post_id`),
  KEY `category` (`category`),
  KEY `platform` (`platform`),
  KEY `ymd` (`ymd`),
  KEY `hour` (`hour`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='ネット投稿記事閲覧の時間単位集計';

これと同じ構成のテーブルを ClickHouse にて生成。

CREATE TABLE `post_view_total` (
  `id` bigint unsigned NOT NULL COMMENT 'ID',
  `author_id` int unsigned NOT NULL COMMENT '著者ID',
  `post_id` int unsigned NOT NULL COMMENT '投稿ID',
  `category` varchar(20) NOT NULL COMMENT '記事カテゴリー',
  `platform` varchar(20) NOT NULL COMMENT 'プラットフォーム種別',
  `ymd` date NOT NULL COMMENT '閲覧日付',
  `hour` int NOT NULL COMMENT '閲覧時間帯',
  `views` int unsigned NOT NULL COMMENT '閲覧回数'
)
ENGINE=MergeTree
PRIMARY KEY (`id`)
COMMENT 'ネット投稿記事閲覧の時間単位集計';

これら2つのテーブル間でレコードをコピーする訳ですが、ClickHouse には MySQL 連携機能が用意されているので、いちいちダンプ→レストアのようなことをせずともサーバ間で直接データコピーが可能です。ClickHouse クライアントにて、こんなふうにします。

INSERT INTO post_view_total 
SELECT * FROM mysql('<MySQL サーバの FQDN>:3306', '<MySQL データベース名>', 'post_view_total', '<ユーザ名>', '<パスワード>');

プログレスがリアルタイム表示されてコピーが進行し、終わるとこんな表示が。

0 rows in set. Elapsed: 968.037 sec. Processed 111.96 million rows, 7.67 GB (115.65 thousand rows/s., 7.92 MB/s.)

1.1億強レコード、7.67 GB のコピーに16分程かかったという訳です。

ベンチマークと結果

ようやく準備が出来たので、このテーブルに対して以下のような問い合わせをしてみます。

SELECT post_id, SUM(views) AS view_count FROM post_view_total 
WHERE author_id = 1234 AND category = '身辺雑記' AND platform IN ('アプリ', 'ブラウザ') 
GROUP BY post_id;

RDS の MySQL (インスタンスクラスは m5.large)ではこの問い合わせに初回は6分程、キャッシュが効いた2回目以降でも2分半程かかっていました。それが ClickHouse の場合は、こういう結果に↓。

148 rows in set. Elapsed: 4.484 sec. Processed 111.96 million rows, 4.72 GB (24.97 million rows/s., 1.05 GB/s.)

こちらは初回も2回目以降もレスポンスタイムに大きな差はありませんでした。平均してだいたい4.5秒程ですから、30〜80倍程のパフォーマンスアップです。尚、色々な EC2 インスタンスで試してみましたが、レスポンス速度は vCPU 数に比例するようです。t3a.large は 2vCPU なので、xlarge (4vCPU)にすれば更に倍の性能が期待できます。

評価

そんな訳で、シンプルながらも業務で発生している問題とほぼ同等なベンチマーキングが実施できました。期待以上の好結果だったので、問題解決のソリューションたり得る感触を得ました。とはいえまだまだ運用ノウハウが足りないので、さらに深堀りしてみようと思います。

S3 バケットを読み書きする SFTP サービスをサクッと用意する

前回記事「複数エンドユーザ用の Jail な SFTP 接続環境を Docker コンテナでサクッと用意する」の流れで、対象ストレージをローカルファイルシステムではなく、S3 バケットにしてみようという話です。

実は AWS には Transfer Family という、S3 バケットまたは EFS ボリュームに対して SFTP / AS2 / FTPS / FTP 経由で読み書きできる完全マネージド型サービスがあるのですが、ちょうど使える EC2 インスタンスがあればこの代替案で少しは料金を浮かせることもできるかもしれません。

以下のようなディレクトリ構造を持った S3 バケットの foo/ 以下を SFTP ユーザ client-foo に、bar/ 以下を SFTP ユーザ client-bar に開放する、というストーリーで進めます。

s3://my-bucket
├── foo
│   ├── aaa.txt
│   ├── bbb.txt
│   └── ccc.txt
└── bar
     ├── xxx.txt
     └── yyy.txt

まず、S3 バケットを EC2 インスタンスファイルシステムにリモートマウントする必要があるので、AWS 提供の Mountpoint for Amazon S3 を使います。インストール等については下記を参照して下さい。

docs.aws.amazon.com

当然、s3://my-bucket への読み書き権限が必要になるので、IAM まわりの設定もお忘れなく(その辺の具体的な手順は割愛します)。

では、mount-s3 でバケット/mnt/my-bucket/ にマウントします。SFTP 公開に際しては削除や上書きも出来ないと困るので、それらを許可するオプションが必要になります。また、ボリュームへのアクセスは dockerd 経由で行われるので --allow-root も追加します。

mount-s3 --allow-delete --allow-overwrite --allow-root my-bucket /mnt/my-bucket/

次に、適当な場所に Docker Compose 用のディレクトリを下記構成で用意。それぞれに、各ユーザ用の公開鍵と環境変数ファイルを置きます。

some-dir
├── foo
│   ├── docker-compose.yml
│   ├── id_rsa.pub
│   └── .env
└── bar
     ├── docker-compose.yml
     ├── id_rsa.pub
     └── .env

docker-compose.yml の内容はこんな感じ。ssh_host_ed25519_key と ssh_host_ed25519_key.pub はコンテナに適用するホストキーです(前回記事を参照)。

version: '3'
services:
  sftp:
    image: atmoz/sftp
    container_name: "${SFTP_USER}-sftp"
    env_file:
      - .env
    volumes:
      - ./ssh_host_ed25519_key:/etc/ssh/ssh_host_ed25519_key
      - ./ssh_host_ed25519_key.pub:/etc/ssh/ssh_host_ed25519_key.pub
      - "./${PUBKEY}:/home/${SFTP_USER}/.ssh/keys/id_rsa.pub:ro"
      - "/mnt/my-bucket/${S3_FOLDER}:/home/${SFTP_USER}/${S3_FOLDER}"
    ports:
      - "${SFTP_PORT}:22"
    command: "${SFTP_USER}::1001:1001"

環境変数ファイルはそれぞれこうなります。

$ cat foo/.env
SFTP_USER=client-foo
PUBKEY=id_rsa.pub
S3_FOLDER=foo
SFTP_PORT=10022

$ cat bar/.env
SFTP_USER=client-bar
PUBKEY=id_rsa.pub
S3_FOLDER=bar
SFTP_PORT=10122

以上、foo と bar それぞれで docker compose up -d すれば、sftp -P 10022 client-foo@>EC2インスタンスのグローバルIP< および sftp -P 10122 client-bar@>EC2インスタンスのグローバルIP< のように接続して S3 バケット内の foo/ および bar/ に対して読み書きできるようになります。

複数エンドユーザ用の Jail な SFTP 接続環境を Docker コンテナでサクッと用意する

インターネットアプリケーションの受注開発をしていると、「このディレクトリ以下だけ、自分たちで読み書きできるように FTP アクセスさせてくんない?」的な要求によく遭遇します。サーバがそのクライアント専用ならまだ良いのですが、他のお客様が同居している共有型運用だと、設定も面倒ですし何より各ユーザを Jail に隔離する為のセキュリティ保持に気を使います。

そんな用途にうってつけな SFTP サーバの Docker イメージを見つけたので、これを使ってファイル転送用リモートアクセス環境を作ってみます。

尚、本例ではサーバを AWS にて立てた EC2 インスタンス(OS は Amazon Linux 2023)とします。EC2 サーバ上で docker および docker compose を使えるようにする、および当該インスタンスにインターネット経由でポート 10022 に接続可能にする VPC やセキュリティグループの設定手順は割愛します。

また、提供するリモートアクセスのサービスは、ざっくり以下の諸元とします。

  • FTP はいくら何でもアレなので、SFTP にしてもらう(SCP は不許可)。
  • 読み書き対象のディレクトリは /var/www/client-foo/html 以下とする。
  • SFTP 接続時のユーザ名は client-foo、ポート番号は 10022 とする。また、認証は鍵交換のみとし、公開鍵をお客様から支給してもらう。
  • Docker ホストでのファイルおよびディレクトリのオーナは、uid:1001, gid:1001 とする。

以上を簡単に図式化するとこんな感じ↓ですかね。

では設定を。まず、/var/www/client-foo/ に、docker-compose.yml とお客様から頂いた公開鍵 id_rsa.pub を保存します。前者の内容はこんな感じ。

version: '3'
services:
  sftp:
    image: atmoz/sftp
    container_name: client-foo-sftp
    volumes:
      - ./id_rsa.pub:/home/client-foo/.ssh/keys/id_rsa.pub:ro
      - /var/www/client-foo/html:/home/client-foo/html
    ports:
      - "10022:22"
    # user:pass[:e][:uid[:gid[:dir1[,dir2]...]]]
    command: client-foo::1001:1001

設定は以上です。/var/www/client-foo/html/ 以下は uid:1001, gid:1001 で読み書きできるようパーミション設定を忘れずに。ここで docker compose up -d すれば、お客様のローカル環境(キーペア設定済)から sftp -P 10022 client-foo@<EC2インスタンスのグローバルIP> として接続および読み書きができるようになります。お客様のスキルにより鍵交換が難しい場合はパスワード認証も可能(イメージ供給元のドキュメントを読んでください)ですが、そのぶんセキュリティ品質は落ちるので推奨はしません。

また、さらに別のお客様用に SFTP 環境を追加したいのであれば、上記までの client-foo を client-bar に、ポート 10022 を 10122 とかに読み替えた上で新しく環境一式を用意すれば OK です。

留意事項として、このコンテナを起動しなおすとその度にコンテナ内の /etc/ssh/ に異なるホスト鍵が生成しなおされてしまい、SFTP クライアント側も known_hosts の修正が必要になったりするので、ちょっと面倒です。これを防ぐには、最初にコンテナを起動した際に docker cp でコンテナ内のホスト鍵を Docker ホスト側にコピーしておいて、2回目以降の起動からはそれらをバインドしてやれば良いかと。この辺はイメージ供給元ドキュメントに「Providing your own SSH host key (recommended)」として延べられているのでご一読を。

2024年第3四半期 プライムビデオで観た音楽系コンテンツ

四半期恒例、ネタバレ御免なプライムビデオの音楽系コンテンツ寸評まとめです。

ガールズバンドクライ [全13話]

親友の裏切り・いじめ退学・両親との不和で熊本から単身上京した JK が憧れだったバンドの元メンと出会ってバンドを組み、それまでの半生にリベンジするかのうようにドロップアウトしていくお話。CG の動きに最後まで馴染めなかったけど、感情の昂ぶりをパーティクルで表現してるのはゲームの手法なのか可能性を感じました。ルサンチマン故に自己表現に向かうのはロックの定番だけど、それにしてはトントン拍子で上手く行きすぎてるような気も。

エルヴィス(字幕版)

パーカー大佐を語り部にしたプレスリーの伝記映画。もみあげを蓄えてヒラヒラ・キラキラ衣装でのベガス公演の日々は嘲笑されがちだったけど、その辺の認識が少し変わったのは観た甲斐があったかも。ショウビズからいったん離れて生き延びていれば、数十年後に再評価で華々しく復活できたかもなぁ、などと思ってしまいます。

エルヴィス(字幕版)

エルヴィス(字幕版)

  • オースティン・バトラー
Amazon

ファイナル・デッド・ツアー(字幕版)

売れない3ピースパンクバンド(♀2,♂1)が起死回生のドサ回りツアーを目論むが車を無くし、夜に人食いゾンビ化しちゃうおっさんに出会ってドライバーを依頼するハメに… というおバカロードムービー。バンド演奏シーンが3曲程あるけど、どれも程よくエモくてイイ感じ。フツーにゾンビ映画として観はじめたけど、エンドロール眺めながら「これ、音楽映画に入れていいかもw」ってなっちゃいましたw 字幕版をウォッチリストに入れたのに、何故か吹替版が再生されてどうしようもなかったのがちょっと残念。

ブライアン・ウィルソン ソングライター ザ・ビーチ・ボーイズの光と影(字幕版)

3時間を超える、1960年代のブライアン・ウィルソン・ドキュメンタリー。正直、先日観た映画「約束の旅路」より良かったです。フォー・フレッシュメンに影響を受けたキャリアスタート、フィル・スペクターへの傾倒、ビートルズへの対抗意識、ペット・サウンズと後続アルバムでの挫折など、見応えたっぷり。BB5 メンバーに加えて、キャロル・ケイなどのレッキング・クルーメンバーのインタビューは貴重かも。'70年代以降は PART2 に続くのだけど、どんどん暗くなっていくので辛そう…

ブライアン・ウィルソン ソングライターPART2 孤独な男の話をしよう

上記続編で、「スマイル」挫折から1982年までへのフォーカス。自分語りになりますが、ロックを聴き始めた'70年代中盤の自分にとって BB5 はベンチャーズのような懐メロロック的存在だったのが、後追いで「ペット・サウンズ」を中心に追体験して彼らにハマったクチです。それでも、本作で語られているように'70年以降の迷走ぶりを見ていると、最初の印象は無理もなかったのかなーと。とまれ、とにかくブライアンが気の毒の一言。世界の誰もが彼の才能を愛し、それ故に皆で彼を引き裂いていたのだという地獄絵図に心が痛みます。

Prophet 予測で日経平均を日次売買したらどうなるかやってみた

「やってみた」と言ってもプログラムで、という話です(当たり前w)。

ご存知のとおり、Prophet は Meta が開発した時系列データ予測ツールです。トレンド・季節変化・イベント等を組み合わせた予測モデルとなっている為か、ファイナンス分野への適用でも「今後1年間の株価推移」のような長期予測の例はよく見かけても、「ぶっちゃけ明日上がるの?下がるの?」的な単純な使い方はあまりしないようです。Prophet では一度遊んでみたかったので、そんな単純な短期売買をシミュレーションしてみました。

売買ルールはこんな感じです。

  • 売買対象は日経平均。もし実際に取引するとしたら日経平均連動型の ETF になりますかね。
  • 毎日大引け直前に終値近辺で買入(または空売り)して、翌営業日の大引け直前に終値近辺で売却(または買い戻し)するという短期売買。
  • 大引け直前に前日までのデータを元に Prophet で翌日の終値を予想し、本日終値より上昇予想なら買入、下降なら空売りと判断する。
  • 売買金額は関心の対象外とし、利益等は金額ではなくパーセンテージとして計算する。

これを、2005〜2015年についてバックテストしてみました。この期間を選んだのは上昇(アベノミクス相場等)・下降(リーマンショック等)の両トレンドを程よく均等に含んでいそうだったから。グラフ化するとこんな感じ↓です。

さて、バックテストの結果を、実際と予測の終値推移・売買勝率推移・利益率累計推移でグラフ化したのがこれ。

ご覧のように勝率こそ49%と五分五分ですが、売買成績はマイナス70%程の惨憺たるもの。例えば毎日1万円で10年間(2450日)売買を繰り返していたら、7000円あまりの損失ということになりますが、実際は手数料もありますから骨折り損+大損のダブルパンチですな。

これは考えてみたら当たり前で、高度な予測モデルをもつ Prophet と言えども所詮は過去のデータからの類推を弾き出している訳で、グラフのように実際の不規則な値動きに少し遅れてついてくる感じの予測値になります。こういう傾向は移動平均MACD のようなポピュラーな指数でも同じで、ちょっとトレーディングのプログラムをした方なら「あるある」だと思います。

だからと言って、Prophet が役立たずだと言っている訳ではなく、このような使い方が適切ではないということでしょう。例えば、予測値そのものではなく、未来のトレンドや周期性等に明確なサインが見いだせるならそこに着目し、利益を得るというよりはリスクを回避する方向で活用したりする、そんな使い方が正しいのかなぁなどと思ったりします。

本記事の為に書いたコードは、 Google Colaboratory でシェアしてあります↓。

colab.research.google.com

【EventBridge スケジューラ】ラムダ関数のペイロードに呼び出し日時を含める


AWS EventBridge スケジューラからラムダ関数を実行する際に、ペイロード(ラムダ関数へのパラメータ)に呼び出し時の日時を混ぜるにはどうしたら良いか、という小ネタでつ。

EventBridge スケジューラ設定で、ペイロードを設定しない場合はラムダ関数の event パラメータに以下のような内容が渡されるようです。

{
    "version": "0",
    "id": "d7668f47-e0a0-4cc5-bc56-45942a2a12bc",
    "detail-type": "Scheduled Event",
    "source": "aws.scheduler",
    "account": "999999999999",
    "time": "2024-07-11T02:48:00Z",
    "region": "ap-northeast-1",
    "resources": [
        "arn:aws:scheduler:ap-northeast-1:999999999999:schedule/default/my-schedule"
    ],
    "detail": "{}"
}

これが、自分で

{
    "my-parameter": "foo"
}

のようにペイロードを指定するとキレイさっぱり無くなってしまうのですな。ラムダ関数で現在日時に応じた処理をしたい場合に不便です。プログラム内でシステム時計から取得するのはテストや運用で不便なのでイヤだし。EventBridge ルールのほうでは入力変換という動的パラメータを生成する機能があるのですが、スケジューラではこれが使えない模様。

さてどうしたら良いのかと悩んでたらここに答えがありました。

docs.aws.amazon.com

つまり、スケジューラ ARN・呼び出し日時・呼び出し ID・試行回数に限ってはペイロードに動的に含めることができると。呼び出し日時が欲しければ、

{
    "my-parameter": "foo",
    "time": "<aws.scheduler.scheduled-time>"
}

のようにすれば良いと。何だか統一性の無いソリューションかなーという印象ですが、とりあえず問題は解決しますた。