Kindle Scribeがすごく良いという話
昨年、Kindle Scribeを買いました。
皆さんは、Kindle Scribeをご存知ですか?周りにあまりKindleを使っている方が少ないためかあまり認知度が高くない印象があり、満足度高く使ってるぞ!というのを伝えたく、ブログを書いてみました。
Kindle Scribeって何?
Kindle Scribeはその名の通り、Kindleシリーズのデバイスの一つです。なので、ベースはE-inkスクリーンのデバイスで、電子書籍が読めるツールです。E-inkスクリーンなので、読みやすいです。
でも、それだけでは無いです!最大の特徴は、手書き入力機能が搭載しているということです。専用のペンになってしまうのですが、スラスラと書けるKindle端末がKindle Scribeです。
何がそんなによいの?
僕は考え事をするときに手で色々と書きながら考えると物事や思考が整理されるなと常々感じます。同じことをPCでやれるかと思ってファイルに書くことに何度か挑戦してみましたが、整理される感じがあまりなく、続かなかった印象があります。また、論文を読んだりすることが多く、PDFに思考を書いたり、メモしたりしたいと思う場面がしばしばありました。一方で、和訳するのは、デジタルのほうが早かったりするので、もどかしいなーと感じことが多々あります。
タイピングのスピードや操作の慣れの問題もあるのかもしれませんが、手書きのいいところは思考と同じスピードで書けるところ、思いついたら余白に書ける、関連するところに飛んで追記する事ができる。こういう感触が個人的にはいいなと感じており、PCで再現できていないなと感じるものなのかもしれません。
一方で、アナログの一番の問題点は検索性が無いことですよね(そして、デジタルの最大のメリットといってもいいかもしれません)。メモしたはずのメモがどのノートに記述したのかわからない。そんなことがないのがデジタルの良さだと思います。
Kindle Scribeの場合、電子書籍への直接の書き込みはできないものの、付箋の形式でメモを残すことができます。なので、メモを章や節、用語などに紐づけておけば、検索することができます。(もちろん付箋を直接辿れるはずです。)なので、アナログの課題をデジタルの要素と組み合わせることで、操作性の良いメモになるのです。
電子書籍への書き込み以外にも、Kindle Scribeにはノートの機能やPDFに直接書き込める機能があります。ここで書いた内容の編集の自由度は高く、書いたものを移動させたりもできるので、アナログのメリットをさらに飛躍させてくれています。
この他にもメリットを上げると切りがないですが、つらつら上げていくと、
- E-ink端末なので電池持ちがものすごくよい
- 1ヶ月使ってても1回充電するかしないかのレベル
- ペンの充電が不要(のようにみえている)
- ノートと本と印刷したPDFを持ち歩かなくてよい(かさばらない)
- シンプルに軽い
- バックライトの調整ができるので、暗い中でも読み書きできる
- ノートはスマホのKindleアプリでも見られる
- 書き込んだPDFはEmailで転送することができる
- インターネット接続できるので、web検索、文単位の翻訳などが可能 -単語単位の翻訳はオフライン環境でも可能
- パームリジェクト機能がある
- 書き味が紙の様
- 画面がツルツルしていないので、ものすごく書きやすいです
などがあります。
個人的には、電池の持ちが良いのは、経済の面でも環境の面でも嬉しいです。
タブレットでよくない?
どうせ電子書籍が読めて、手書きのものを書くならタブレットでよくない?なんなら他のアプリも使えるし、カラーだし。
このような声が聞こえてきそうな気がします。笑
まあ、このあたりはその通り。だと思います。ただ、個人的には、タブレットは重く、多機能な故電池の持ちが悪い(一日もつので十分だとは思いますが、Kindleと比較するとどうしても短く感じてしまいます。)印象があります。また、専用のペンが必要だったり、電池交換が必要だったり、充電したりで気にかけることが多いと思います。
このあたりの、対象としている機能は少ないが、多くの細かな気になるポイントを一切気にしなくてよいのが、本当に最高だなと思っています。
所感
色々かきましたが、タブレット一つで仕事ができる方はタブレットを駆使するのがよいと思います。
ただ、PCは仕事をする上で必要不可欠で、何かしらの工程で手書きを必要としている方には強くおすすめできるプロダクトだとおもいました!
k8s のpod からネットワーク速度を覗く
k8s 上のpod でファイルを転送する場合にネットワークが詰まっているかどうか、プロセスがゾンビ化していないかを確認したかったので、やり方を調査したのでメモ。
どう解決したか
シンプルに netstat
を使うことで確認できた。
久しぶりに netstat
を使ったので存在を忘れていた。
コンテナのイメージサイズを減らすため、必要だったコマンドを実行するためのpackage が含まれていなかったため、pod に入ってからインストールする。
# pod にログイン $ kubectl exec -it pod-id -n namespace -c main /bin/bash # netstat とps コマンドを実行するために必要なpackage をinstall $ apt-get update && apt-get install -y procps net-tools
procps
は ps
を実行するために必要で、pid を調べるためにinstall した。
netstat
でプロセス毎の状態を見るには以下のコマンドを使用した。
$ netstat -alp
上記のコマンドを実行すると ESTABLISHED
という State
が確認でき、接続中であることがわかる。
また、 Recv-Q
, Send-Q
でパケットの送受信も確認できた。
watch
コマンドを組み合わせて、 watch netstat -alp
とすることで、パケットの変動とかも確認することができる。
以上
aws-glue-libs をCI/CD で使いたい
概要
AWS Glue の自動テストで aws-glue-libs コンテナイメージを使いたいが high-uid-error
になってしまった。それを回避した話。
前提
AWS Glue の開発を進めやすくするために、amazon から aws-glue-libs
というコンテナが公開されている。
https://hub.docker.com/r/amazon/aws-glue-libs
このコンテナを使って、
- スクリプトの開発
- Zeppelin, Jupyter などによるNotebook 開発
- テストの実行・開発
を行うことができる。
今回の事象
今回はglue とpySpark の組み合わせでETL タスクを実装しており、一部のtransform をpytest で書いていた。
local 環境ではdocker-compose でテストを走らせるようにしていて、base image に aws-glue-libs
を使っているという形。
local 環境ではうまくテストが実行されたので、CircleCI で自動テストを行おうとした際に、 high-uid-error
にぶつかった。
https://circleci.com/docs/2.0/high-uid-error/
問題
high-uid-error
は、コンテナ内のファイルやディレクトリのUID/GID の値が指定の値域以上の値が指定されているケースに起こる。
ドキュメントには
The error is caused by a userns remapping failure. CircleCI runs Docker containers with userns enabled in order to securely run customers’ containers. The host machine is configured with a valid UID/GID for remapping. This UID/GID must be in the range of 0 - 65535.
とあり、コンテナ内のUID/GID は 0 ~ 65535 の範囲内にある必要がある。
原因
UID/GID が原因のようなので、コンテナを起動して ls -la
などでファイルなどのオーナーを調べる。
# local 環境から amazon/aws-glue-libs:glue_libs_1.0.0_image_01 を起動する $ docker run -it amazon/aws-glue-libs:glue_libs_1.0.0_image_01 /bin/bash # amazon/aws-glue-libs:glue_libs_1.0.0_image_01 内の操作 root@b3219a5e7e66:/# ls -la /home/ total 32 drwxr-xr-x 1 root root 4096 Jul 21 2020 . drwxr-xr-x 1 root root 4096 Jul 5 05:13 .. drwxr-xr-x 3 root root 4096 Jul 15 2020 aws drwxr-xr-x 5 root root 4096 Jul 21 2020 aws-glue-libs drwxr-xr-x 3 root root 4096 Jul 21 2020 jupyter drwxr-xr-x 25 root root 4096 Jul 21 2020 livy drwxr-xr-x 11 2049080342 staff 4096 Sep 17 2019 spark-2.4.3-bin-spark-2.4.3-bin-hadoop2.8 drwxr-xr-x 13 root root 4096 Jul 21 2020 zeppelin
/home/spark-2.4.3-bin-spark-2.4.3-bin-hadoop2.8
のuid が 2049080342
になっていることが確認できた。
うまくいった対処
今回はCircleCI の代わりにGitHub Actions を使うことでクリアしました。 幸いにもテストを実行するdocker-compose file を用意していたので、そちらをGitHub Actions の中で呼ぶだけで済みました。
他に試みた対処
CircleCI の jobs
の中で amazon/aws-glue-libs:glue_libs_1.0.0_image_01
を image
として指定し、 command
で /home/spark-2.4.3-bin-spark-2.4.3-bin-hadoop2.8
のオーナーを root:root
に変更してみた。
これはうまくいかなかった。
そもそも、CircleCI で該当のコンテナをマッピングするときにエラーが起きるので、コマンドまで到達せず、UID/GID を変更するまでに至らなかった。
Docker image を公開しました
CircleCI でも使えるように amazon/aws-glue-libs
の Spark を root:root
に変更しただけのDocker image とGitHub repository を公開しました。
興味がある人は使ってみてください!
Docker Hub
GitHub repository
Redshift で日付区間を縦持ちにする
今となっては横持ちのデータを縦持ちに展開する場合、もしくは縦持ちのデータを横持ちに変換する場合、
Airflow なりDataflow なりデータパイプラインで実現すると思います。
今回は検証をしたいためだけのため、横持ちのデータを縦持ちに展開することをSQLだけで頑張らないといけない機会があったので、
記録として残しておきます。
いつもどおり、誰かの参考、助けになれば嬉しいです。
以前にもHive でデータを縦持ちに展開するとかの記事を書いたのが懐かしい…
対象のデータとどう展開したいか
さて、本題に入っていきます。
RDBのデータとして以下のような期間を表現しているデータがあったとします。
SELECT 'hoge' AS name, '2021-02-01'::DATE AS start_date, '2021-02-15'::DATE AS end_date
テーブルで表現するとこんな感じです。
name | start_date | end_date |
---|---|---|
hoge | 2021-02-01 | 2021-02-15 |
start_date
が開始の日時、 end_date
が終了の日時を表現してます。
以下のように name
カラムを start_date
から end_date
まで日を更新しながら一行ずつに展開していくことがゴールです。
name | expand_date |
---|---|
hoge | 2021-02-01 00:00:00 |
hoge | 2021-02-02 00:00:00 |
hoge | 2021-02-03 00:00:00 |
... | ... |
hoge | 2021-02-13 00:00:00 |
hoge | 2021-02-14 00:00:00 |
hoge | 2021-02-15 00:00:00 |
どうやったか
SQL の cross join と Redshift の関数の generate_series
, dateadd
関数を組み合わせて実現しました。
generate_series
generate_series 指定した区間とインターバルで連続的に数字を生成する関数です。
Redshift ではgenerate_series は使えないという記事をよくでてきますが、from 句で生成するケースでは使うことができます。
SELECT * FROM generate_series(1, 4, 1);
このようなSQL を実行すると
generate_series |
---|
1 |
2 |
3 |
4 |
という結果が返ってきます。 関数としては、
generate_serise( start, end, interval )
で指定できて、start からend までinterval の間隔で閉区間の数列を生成してくれます。
CROSS JOIN
cross join は簡単に言えば2つのテーブルの行を全通り組み合わせるものです。
cross join で調べると資料がたくさん出てくるが、以下のQiita の記事がわかりやすいと思う。
dateadd
dateadd は複数のSQL で追加されている関数です。
Redshift の関数の仕様は以下のドキュメントを一読してください。
大まかに関数を説明すると、
DATEADD( datepart, interval, {date|time|timetz|timestamp} )
という関数で各引数は
- datepart:
year
,month
,day
など加算したい部分を指定 - interval: 加算数
- {date|time|timetz|timestamp}: date や timestamp データ型のカラム
を表しています。
組み合わせていく
ではまず最初に最初の1行しかないテーブルとgenerate_serise 関数で生成したテーブルをcross join で組み合わせていきます。
WITH tmp_table_1 AS ( SELECT 'hoge' AS name, '2021-02-01'::DATE AS start_date, '2021-02-15'::DATE AS end_date ) SELECT * FROM tmp_table_1 CROSS JOIN ( SELECT * FROM generate_series(1, 30, 1) ) tmp_table_2;
これにより
name | start_date | end_date |
---|---|---|
hoge | 2021-02-01 | 2021-02-15 |
を30 行生成します。 生成したテーブルの部分抜粋は以下の通りです。
name | start_date | end_date | generate_series |
---|---|---|---|
hoge | 2021-02-01 | 2021-02-15 | 1 |
hoge | 2021-02-01 | 2021-02-15 | 2 |
hoge | 2021-02-01 | 2021-02-15 | 3 |
... | ... | ... | ... |
hoge | 2021-02-01 | 2021-02-15 | 28 |
hoge | 2021-02-01 | 2021-02-15 | 29 |
hoge | 2021-02-01 | 2021-02-15 | 30 |
次にこの生成した generate_series
の数字を使い、 dateadd
していきます。
SQL としては以下の通りです。
WITH tmp_table_1 AS ( SELECT 'hoge' AS name, '2021-02-01'::DATE AS start_date, '2021-02-15'::DATE AS end_date ) SELECT tmp_table_1.name, tmp_table_1.start_date, tmp_table_1.end_date, tmp_table_2.generate_series, dateadd(day,tmp_table_2.generate_series,tmp_table_1.start_date-1) AS expand_date FROM tmp_table_1 CROSS JOIN ( SELECT * FROM generate_series(1, 30, 1) ) tmp_table_2;
生成されたテーブルは
name | start_date | end_date | generate_series | expand_date |
---|---|---|---|---|
hoge | 2021-02-01 | 2021-02-15 | 1 | 2021-02-01 00:00:00 |
hoge | 2021-02-01 | 2021-02-15 | 2 | 2021-02-02 00:00:00 |
hoge | 2021-02-01 | 2021-02-15 | 3 | 2021-02-03 00:00:00 |
... | ... | ... | ... | |
hoge | 2021-02-01 | 2021-02-15 | 28 | 2021-02-28 00:00:00 |
hoge | 2021-02-01 | 2021-02-15 | 29 | 2021-03-01 00:00:00 |
hoge | 2021-02-01 | 2021-02-15 | 30 | 2021-03-02 00:00:00 |
のようになり、うまく日付が生成されていることがわかります。
しかし end_date
期間以上の日付が生成されています。
なので、最後に end_date
で期間を絞りつつ、カラムも絞って目的のテーブルに整形していきます。
今回は name
と expand_date
に制限します。
SQLとしては以下の通りです。
WITH tmp_table_1 AS ( SELECT 'hoge' AS name, '2021-02-01'::DATE AS start_date, '2021-02-15'::DATE AS end_date ) SELECT tmp_table_1.name, dateadd(day,tmp_table_2.generate_series,tmp_table_1.start_date-1) AS expand_date FROM tmp_table_1 CROSS JOIN ( SELECT * FROM generate_series(1, 30, 1) ) tmp_table_2 WHERE expand_date <= end_date;
そして生成されたテーブルは以下の通りです。
name | expand_date |
---|---|
hoge | 2021-02-01 00:00:00 |
hoge | 2021-02-02 00:00:00 |
hoge | 2021-02-03 00:00:00 |
... | ... |
hoge | 2021-02-13 00:00:00 |
hoge | 2021-02-14 00:00:00 |
hoge | 2021-02-15 00:00:00 |
目的の縦持ちに展開したテーブルを生成することができました。
expand_date
のフォーマットを変えたい場合は、 to_char
関数とかを使えばフォーマットできます。
ネックになっているところ
イケてないところとしては、生成する行数が start_date
と end_date
とで一致していないところだと思っています。
必要以上のレコードを生成してフィルターしているのをもう少しスマートにできればなという感触です。
もっとスマートなやり方をご存知の方がいればコメントをお願いします…!
所感
一応SQLだけで目的のテーブルを作成することができました。
序文に書いた通り、本来はデータパイプラインなどで前処理を実装するほうが確実に実装できると思います。
ですが、このようなやり方もあるということを発見できただけでも良かったかなと思います。
parquet の中身を見る @mac
久しぶりにparquet file を扱う機会があり、中身を見る必要があったので、やり方を残しておく。
$ brew install parquet-tools
以前のブログでツールはなるべくビルドしたいと書いたが、今回は brew
でインストールした。
Java の環境などはセットアップしていなかったので、brew install のときに出力されたパスも合わせて設定した。
ぼくはzsh を使っているので、今回は .zshrc
に出力した。
# jdk のpath をzshrc に設定 echo 'export PATH="/usr/local/opt/openjdk/bin:$PATH"' >> ~/.zshrc echo 'export CPPFLAGS="-I/usr/local/opt/openjdk/include"' >> ~/.zshrc
その後は、reload すると parquet-tools
が使えるようになった。
Airflow doc_md のすゝめ
Summary
Airflow のdag とtask には、doc_md
というものがあるよ。
これを使えばタスクの定義や目的をみんな大好きMarkdown形式で記述することができるよ。
今回は、Airflow==1.10.12を使ってるけど、Airflow<=1.10.1から使えるからGoogle Cloud Composer でも使えるよ。
What's Airflow
Airflow は元々はAirbnb が開発していたタスクスケジューラOSSでした。 今は、Apache コミュニティの一つのOSSとしての位置づけになっています。 また、マネージドサービスとしてはGCP のCloudComposerがあります。
仕組みなどは他の記事などを参照されたいが、簡単に要点を以下にまとめてみました。
- それぞれのタスクは
Operator
と呼ばれる処理を実行するもの、Sensor
と呼ばれるなにかのイベントを検知するものがある - 上記のインスタンスを利用して、処理をTaskとして実装
- そのTaskをつなぎ合わせてDAG(有向非巡回グラフ)として表現
- タスクの依存関係の管理やGUIでのリトライなどのメリットがある
タスクのドキュメントはどう管理するの?
これまでいくつかのプロジェクトに関わり、Airflowが採用されるケースを見てきましたし、 巷の事例紹介でもよく分析基盤のタスクスケジューラとしてAirflowを採用しているケースを聞くことが増えてきました。 その中で個人的にタスクの役割やダグ全体が処理する内容はどうやって管理するのがベストなのだろう?という疑問がありました。
Which is better? Python docstring, Other document tool and doc_md...
Pythonコードの場合、Pythonのdocstringとして管理するというのが一つの回答かもしれません。 しかし、これでは
- タスクがやっていることはコードを見に行かないといけない
- AirflowとしてGUIを提供しているのにその旨味を活かせない
このような点が挙げられるかと思います。
また、ドキュメントツールが別途あるからそれを使えばいいじゃん。という意見もあるかもしれません。
ドキュメントツールにまとめることは確かに素晴らしいことです。
しかし、開発途上にあるツールをドキュメントにまとめ更新し続けるのは難しく、だんだんと開発のスピードを言い訳にドキュメントの更新がおろそかになると思います。
そこでAirflowで提供されているdoc_md
を紹介したいと思います。
What's doc_md?
doc_md
はMarkdown形式でdagやtaskのノートを記述することができ、記述した内容をGraph View
やTask Instance
上に表示することができます。
上記のリンクに飛ぶと気づきますが、doc_md
以外にもplain textで記述できる方法やjson形式、yaml形式などお好きな方法で記述する方法があります。
そして、このdoc
attributeを使う最大のメリットは、
コードの更新と同時にドキュメントの更新が指摘・確認できる
ことにあると思います。
例えば、パイプラインを修正するPRにドキュメントを更新したコミットがない場合はPRで指摘すればよいので、特段開発する側としても、別ツールになっているドキュメントを更新するよりも億劫にならずに済むと思われます。
どうやって書くの?
今回は、GitHubにあげられているairflow-examples/dags/example_python_operator.pyをベースにdoc_md
を追加したいと思います。
追加した内容は以下のような内容になります。
from __future__ import print_function from builtins import range from airflow.operators import PythonOperator from airflow.models import DAG from airflow.utils.dates import days_ago from datetime import datetime, timedelta import time from pprint import pprint seven_days_ago = datetime.combine( datetime.today() - timedelta(7), datetime.min.time()) args = { 'owner': 'Airflow', 'start_date': days_ago(2), } dag = DAG( dag_id='example_docmd_python_operator', default_args=args, schedule_interval=None) dag.doc_md = """ # What this dag will do? This dag is for an example workflow using a PythonOperator. ## Do you want to share any details? First of all, it prints dags' context and ds.<br> And then, it will run 10 sleeping tasks in parallel.<br> Note that sleeping time is set randomly, from 0 to 9 seconds. """ def my_sleeping_function(random_base): '''This is a function that will run within the DAG execution''' time.sleep(random_base) def print_context(ds, **kwargs): pprint(kwargs) print(ds) return 'Whatever you return gets printed in the logs' run_this = PythonOperator( task_id='print_the_context', provide_context=True, python_callable=print_context, dag=dag) run_this.doc_md = """ # What this task will do? This task simply print the dags' context and ds. ## Do you want to share any details? Nothing for detail. """ for i in range(10): ''' Generating 10 sleeping task, sleeping from 0 to 9 seconds respectively ''' task = PythonOperator( task_id='sleep_for_'+str(i), python_callable=my_sleeping_function, op_kwargs={'random_base': float(i)/10}, dag=dag) task.doc_md = """ # What this task will do? This task will sleep after `print_the_context` task. ## Do you want to share any details? Sleep time are set randomly, from 0 to 9 seconds. """ task.set_upstream(run_this)
どんな感じに描画されるのか?
Dag の場合
Graph View
やTree View
に遷移すると描画されているのが確認できます。
dag.doc_md
に記述された内容が表示されています。
ここに説明を記述することで、task_id
だけでは表現できない、リトライのことやDAGの目的などを書くことができます。
dag の描画
Task の場合
dag のときとは異なり、Task の場合は、Task Instance Details
のページに遷移すると描画されているのが確認できます。
Attribute: doc_md
という枠が表示されているので、その領域にノートの内容が描画されています。
print_the_context task の描画
sleep_for_ task の描画
まとめ
今回はAirflow に付随するdoc
attribute を紹介しました。
パイプラインはどうしても煩雑になる傾向があるので、この機能を使って可読性とパイプラインの責務をスッキリまとめ簡潔な開発が維持できるようにしていきたいです。
Rust インストール
Rust をインストールして、色々合わせてインストールされたので、それぞれの役割を書いていきます。
Rust インストール
先日の記事でもRust のインストールを紹介したが、rust-lang.org にインストール方法を説明するページがあるので、そちらを参照されたい。
今回は以下のコマンドでインストールした。
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
そうするとインストールの方法に関して、以下のような出力がだされる。
1) Proceed with installation (default) 2) Customize installation 3) Cancel installation >
そのままインストールを進める場合は1
、インストールをカスタマイズする場合は2
、インストールをキャンセルしたい場合は3
といったところか?
今回は1
でそのままインストールを進める。
そうすると、基本的にインストールが完了される。
bash
なり zsh
なりのprofile ファイルにパスが自動的に設定されます。
source
コマンドでprofile をリロードします。
$ source ~/.zshrc
合わせてインストールされたツールたち
Rust をインストールすると2つのツールが合わせてインストールされます。
rustup
cargo
の2つです。
何が違うの??
rustup
と cargo
の役割の違いをメモして起きます。
Rustup: the Rust installer and version management tool
rustup
の役割は、Rust のインストールページにありますが、Rustそのものを管理するmanagement toolです。
Rust の魅力はいくつかあると思いますが、その中の一つに更新の感覚とその頻度があるのではないでしょうか?
Rust が更新され、update したい場合は
$ rustup update
で行うみたいです。(まだやったことないですが笑)
Cargo: the Rust build tool and package manager
一方、 cargo
の役割は、自分で書いたコードのbuildや実行、あるいは他者が書いたtool(Rust の世界では crates
と呼ぶみたいです。)のインストールに使うみたいです。
コマンドとしては、
$ cargo build # build the source code $ cargo run # run the source code $ cargo test # run the test code $ cargo install # install published crates
があるみたいです。
とりあえず、今日はインストールのときに気になったことをブログにしてみました。
どなたかの助けになれば幸いです。