Firestoreの新しいライブラリを思いついたんだけど
思いつきのメモなんですが
FirebaseのFirestoreを読み書きするTypeScriptのコードってちょっと書くとすぐごちゃごちゃし始めて脳負荷が高いコードになりがちじゃないですか?
テーブル?の定義とセーブロードのロジックが混じり合ってカオスになるのがとっても汚らしいと思うんですよ。
なので、アノテーションつかってこういうふうにかけるライブラリあったらよくないかなとおもいました。
class Game extends FSModel { // このアノテーションで/usr/games/ 以下に保存される @definePath("/user/games") // これでFireStoreのエントリを定義する // storeEntityアノテーションがつけられたメンバー変数は // toFirestore(), fromFirestore()で自動的に保存される @storeEntity public gameTitle: string; @storeEntity public gameID: GameID; @storeEntity private published: boolean = false; public setPublish() { this.published = true; } } const e = new Game(); e.gameID = "Foo"; e.gameTitle = "Bar"; e.setPublish(); // これでFireStoreに保存 e.toFirestore(); // FireStoreから取ってくる const ref = Game.fromFirestore(); ref.gameTitle = "Hoge"; ref.gameID = "Fuga"; // もっかい保存しなおす ref.toFirestore();
こんな感じで書けると嬉しい。
ロシアの軍事ドローンのオルラン10(Orlan-10)ってまともに使えないんじゃない?
ウクライナの軍事ドローンオルラン10(Orlan-10)。カメラが日本製(Canon EOS Kiss)とのこと。ここで気になったのは。一体どうやってKissから動画とか画像とか取り出してるの?っていうのがカメコ+技術屋的に気になった
— 🔞田中高橋🔞 (@2020tatarou) 2022年4月24日
Twitterにも書いたんだけど、これの補足。
ロシアの軍事ドローンオルラン10(Orlan-10)。偵察用に使うものなのかな?こいつの中身が結構な割合で日本製の部品を使用しているとのこと。カメラ、エンジン周りは全部日本製。民生品を徹底利用してる。
この動画をソフト屋さんの観点からみて「このドローン。画像はさておき動画を見るのは辛いんじゃないか?」という疑惑がふつふつ湧いてきたのでそれについて考えを書きなぐる。
まず、気になったのは民生品のカメラからどうやって画像とか動画とか取り出してるの?っていうところ。カメラはCanonのEOS Kiss X9iが使われてるらしい。私もCanonユーザやって長い(ずっと5dユーザでmark II, mark IIIと持ってる)ですが、Canonのカメラからリアルタイムで動画を取り出すのは結構しんどい。
選択肢はかなり限られて私が思いつく限り以下の2つ。
ここで、まず(1)はないかなと。Webカメラモードは画質がかなり制限される。これを使うのであればEOS Kissみたいないいカメラ+レンズを使う必要は全く無いので違うのでは?と。
次に(2)は有り得そうかなと。HDMIパススルーモードはかなり良い画質の動画が取れます。しかし、このHDMIパススルーモードを備えたラインナップは限定的で、少なくともEOS Kiss X9iにはその機能はなかったはず。もっとハイエンドの(5d系統以上)カメラじゃないとだめ。非公式の改造ファーム入れるとできるけど…それを使ってるのかしら?
などとぼんやり考えてたら、友達からUnboxing動画があると教えてもらったので見てみた。結構詳しくバラしてて内部基盤とかもちゃんと見れる
それで、6:40秒あたりのこれ。多分これが答えかなーと。
多分これが答え。この端子をCanonのカメラに指してSDカード経由で。つまりFATレイヤー経由で取ってきてるんだと。。。おおーなるほど賢い!日曜電子工作勢みたいなことやってるんですね。
ここから考察です。このドローン画像はさておき動画を見るのはかなり辛いんじゃないの?と思いました。見れてもブツブツ途切れてまともに見れなさそう…
そう考える理由ですが、まずFATレイヤーをHookして取り出すとなるとSDカードにデータが降りてくるタイミング、つまりカメラ側がfsync発行するタイミングが完全にカメラ依存になり、そこは制御することはできない。これが結構問題。
すると、再エンコーディングとかで必要なbufferingのタイミングの自由度がかなり下がりそうです。動画を電波で飛ばすための再エンコーディングは必須だと思いますが、その時、通信帯域やCPUパワーが空いてるにも関わらずカメラ側からデータが降りてこず完全に待ちぼうけをくらい、次の瞬間はCPUパワーや通信帯域が一度にさばききれない量のデータがカメラ側から(fsyncされて)どかっと降りてくるみたいなことが普通に起こりそうです。
こういった状況下で安定したストリーミングを行うのは大変そうです。安定配信には輻輳制御はかなり重要で、各々のコンポーネントが互いに調整しあわないとすぐに不安定になると思います。しかし、肝心のカメラ側の書き出しタイミングがいじれない以上選択肢が大幅にさがり、安定した動画再生は難しいのでは?と感じました。
割り切って動画は見れたら嬉しい!基本的に画像だけでなんとかする!みたいな運用をしてるんでしょうかね?
現物を隅から隅までみたわけではないので、動画用のカメラは別にあるとかも考えられると思いますが、以上考察でした。
追記。教えてもらったんですが、このOrlan-10から撮ったであろう動画見るとやっぱりかなりガクガクしてますよね。カメラを安定させるジンバルが不安定である可能性もありますが。。。さて。。。
The Russian MoD posted a video showing that Ukraine was storing MLRS at the mall in Kyiv that was struck last night. Also notable that Russian Orlan-10 UAVs can operate over Kyiv. https://t.co/c0hLS31sLy pic.twitter.com/z7lfUs8fW3
— Rob Lee (@RALee85) 2022年3月21日
WSL2でCentOS9Streamを動かしたあと一般ユーザでログインしたい
ここに書かれてるように、アンオフィシャルなCentOS9StreamとかをWSL2にimportしたとしよう。
importしたままだと、WSLを起動したときrootユーザになってしまう。これを自分で作った一般ユーザでログインできるようにしたい。
例えばCentOS9Streamという名前でimportしたWSL環境にtakahashi
という一般ユーザでログインしたい場合。Power Shellで以下のようにすればいい
wsl --distribution CentOS9Stream -u takahashi
次回。Windows Terminalから起動したときはtakahashiでログインするようになる。
zfsはcloseとwriteが遅いです
Linuxのzfsの話です。zfsのI/O特性を知っておきましょう。
結論から言います。zfsは「書く量を増やせば増やすほど、それにつられてclose(2)とwrite(2)が顕著に遅くなる」という性質があります。ちなみにLVMではこれは見られません(zfsのほうが色々付加価値があるので、フェアな比較ではないことは重々承知しています)
これを検証するためにちょっとしたマイクロベンチマークを書いてみました。以下のGistにありますので見てください。
このプログラムは、指定されたファイルに対して、特定の大きさのブロック(bs
)を、特定の大きさまで(total_size
)for文でひたすらseek
してwrite
するという単純なプログラムです。
20GBのzvolを/dev/pool/20gb
に作って、書き込み量のtotal_size
を変えながらclose
とwrite
のレイテンシの結果を見てみましょう。ちなみに各システムコールのレイテンシはstrace -c -f
で集計した値を使っています。
total_size (GB) | write (usec) | close (usec) |
---|---|---|
1 | 37 | 454748 (454.748 ms) |
5 | 47 | 1277080 (1277.08 ms) |
10 | 47 | 1693095 (1693.095 ms) |
15 | 50 | 1730856 (1730.856 ms) |
19 | 53 | 2232082 (2232.082 ms) |
total_size
(書き込み量)を増やせば増やすほど。write, closeともにレイテンシが上昇していくのが見られます。特にcloseのレイテンシの上昇はとても大きいです。
一方、LVMではこの特性は見られません。書き込み量を増やしても、大体同じぐらいのレイテンシに収まります。
total_size (GB) | write (usec) | close (usec) |
---|---|---|
1 | 93 | 81 |
5 | 91 | 3 |
10 | 98 | 90 |
15 | 96 | 79 |
19 | 92 | 91 |
若干遅くはなってますが、zfsほどではありませんね。
今回close, writeの測定しかしていませんが、fioで色々実験した限りですと、非同期I/Oのpwriteでもio_submitでも同様の傾向が見られます。
というわけで、LVMと同じI/O特性を期待してzfsを使うと痛い目を見ます。特にcloseのレイテンシの増加率はLVMに比べるととても大きいです。書き込み量を増やすときはなるべくcloseは発行しないようにしましょう。
p.s. 書き込んだ量ではなくて、汚したブロックの量(つまり同じ場所に書き込みまくったらレイテンシは変わらないのではないか?)によって変わるのではないかという疑惑ありますが、それの検証はまた今度。
WSL2でDockerが動かない
WSL2最高ですね。WSL2上のLinuxにjavaとかnodeとかrubyとかいれれば、IntelliJとかWebstormとかRubyMineからふつープロジェクトインポートして開発に使えます。もうx86_64ではないMacを使う必要はなくなるわけです。バイバイMac。ARMの本番環境がまだ普及していない以上開発機としてはもう使えません。
WSL2のUbuntu 20.04.4 LTS上でDockerを動かしたいと思うわけですが、公式ドキュメントの通りインストールはスムーズに行くんですけど、どうもDockerが起動しない!
$ docker ps Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
デーモンが起動してないのかしら?どうやらWSL2のUbuntu上だといちいち手動でDockerデーモンをあげてやらないと使えないっぽいですね。
$ sudo service docker start * Starting Docker: docker
$ sudo docker run hello-world Unable to find image 'hello-world:latest' locally latest: Pulling from library/hello-world 2db29710123e: Pull complete Digest: sha256:bfea6278a0a267fad2634554f4f0c6f31981eea41c553fdf5a83e95a41d40c38 Status: Downloaded newer image for hello-world:latest Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://hub.docker.com/ For more examples and ideas, visit: https://docs.docker.com/get-started/
O_DIRECTでopenするときはmemalign使わないとだめだよ
O_DIRECT
でopen
してwrite
したいときがたまにあると思います。
通常のopenしてwriteだとカーネル内部でページキャッシュが使われて、カーネル内部でのメモリコピーが何回か行われちゃいます。が、O_DIRECT
だとページキャッシュをバイパスできて、その結果カーネル内部でのメモリコピー回数が減ります。
通常O_DIRECT
は使わないんですけども、自前でバッファリングとかしてるアプリケーションとかだと(データベースとか)、カーネルのページキャッシュ層と競合してわけわかんないことになったりするのでOFFにしたりします。
あとは、ベンチマークプログラムでファイルシステムの生の性能を知りたいときなどもコレを使います。
んで、僕知らなかったんですがO_DIRECT
でopenしたfdにread/writeするときってalignされたメモリ空間じゃないと受け付けないんですね。つまり、通常のmallocだとだめなんです。これで30分ぐらいハマりました。
普通のmallocで確保したバッファをwrite(2)
に渡そうとすると、perror()
でInvalid argument
エラーが出ちゃいます。
write : Invalid argument
なんでmallocじゃなくてposix_memalign
とか使いましょうって話です。こんな感じ。
if (posix_memalign((void **)&buf, 512, bs)) { perror("posix_memalign"); exit(EXIT_FAILURE); }
ページ空間をまるごと直接アロケーションしちゃってるんですかね?
まあ、manにはちゃんと書いてあります。ちゃんと読めって話ですね。
O_DIRECT The O_DIRECT flag may impose alignment restrictions on the length and address of user-space buffers and the file offset of I/Os.
すべてのコードは以下の通り:
O_DIRECT tipsでした。
docker-composeで作ったコンテナ同士をネットワークでつなげる
ファイルはここに。
図のようにdockerコンテナを2つ(図中のmy_clientとmy_server)つくって、それらコンテナ間で通信したい。
単一のdocker-compose.yml
の中に2つ以上のコンテナが定義されてる場合は別に問題ない。同一ymlファイル内部でコンテナの名前がホスト名として使えるので、それを指定して通信できる。単一のymlファイルに定義されたDockerコンテナはすべて同一ネットワーク(Bridge)に所属できるのでコンテナ間通信ができるわけ。
問題はこれら2つのdockerコンテナが別々のdocker-compose.ymlファイルで定義されてるとき。この場合は、自分で独自のネットワーク(図中のmy_network)を構築した上で、接続したいコンテナをその独自ネットワーク(my_network)に所属させる必要があります。それで、コンテナ同士が接続がコンテナ名をホスト名として相互に通信できます。
(ちなみにBridgeとは、コンテナ内部のみで通信できるNATみたいなものが生成されて、そのNATネットワーク内部でコンテナが動く。BridgeなのにNATとはこれ如何に?)
ネットワークはdocker-compose upする前に自分で生成しときましょう。以下のように:
#!/bin/sh # make a network docker network create --driver bridge my_network cd ./client && docker-compose -f docker-compose.yml up -d cd - cd ./server && docker-compose -f docker-compose.yml up -d
こんな感じで。docker-compose.yml
ファイルは以下の2つ。
version: '3.8' services: my_client: build: context: ./ dockerfile: Dockerfile networks: - my_network networks: my_network: external: true
version: '3.8' services: my_server: build: context: ./ dockerfile: Dockerfile networks: - my_network networks: my_network: external: true
これでmy_client
コンテナに入って通信してみましょう。
kazushi@dev2:~$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 62af57565a5a server_my_server "/bin/sh -c 'while :…" 35 minutes ago Up 35 minutes server-my_server-1 7adf566c7103 client_my_client "/bin/sh -c 'while :…" 35 minutes ago Up 35 minutes client-my_client-1 kazushi@dev2:~$ docker exec -it 7adf566c7103 /bin/bash root@7adf566c7103:/client# root@7adf566c7103:/client# ping my_server PING my_server (172.25.0.3) 56(84) bytes of data. 64 bytes from server-my_server-1.my_network (172.25.0.3): icmp_seq=1 ttl=64 time=0.043 ms 64 bytes from server-my_server-1.my_network (172.25.0.3): icmp_seq=2 ttl=64 time=0.048 ms 64 bytes from server-my_server-1.my_network (172.25.0.3): icmp_seq=3 ttl=64 time=0.047 ms 64 bytes from server-my_server-1.my_network (172.25.0.3): icmp_seq=4 ttl=64 time=0.047 ms 64 bytes from server-my_server-1.my_network (172.25.0.3): icmp_seq=5 ttl=64 time=0.048 ms 64 bytes from server-my_server-1.my_network (172.25.0.3): icmp_seq=6 ttl=64 time=0.037 ms 64 bytes from server-my_server-1.my_network (172.25.0.3): icmp_seq=7 ttl=64 time=0.047 ms 64 bytes from server-my_server-1.my_network (172.25.0.3): icmp_seq=8 ttl=64 time=0.047 ms --- my_server ping statistics --- 8 packets transmitted, 8 received, 0% packet loss, time 7162ms rtt min/avg/max/mdev = 0.037/0.045/0.048/0.003 ms root@7adf566c7103:/client#
通信できてますねping my_server
で通信できます。
external: true
の意味
ちなみのymlファイル内に記述されてるexternal: true
の意味ですが、docker-composeですでに作成済みのnetworkを再利用するときはtrueを指定します。するとdocker-compose -f docker-compose.yml up
のときに通常生成される独自ネットワーク(Bridge)が生成されず、あり物のmy_network
を探して使用するようになります。逆言うと、my_network
が作られてない場合はエラーが出てコンテナが起動しません。上述したように事前にdocker network create --driver bridge my_network
で作っておきましょう。