巨人の足元でたじlog

そうして言葉を軽んじるから――― 君は私の言葉を聞き逃す

goのエラーハンドリングについて触りを学んだ所感

表題の件について整理してみました。

 

Goエラーハンドリング戦略

こちらの記事が結構参考になったかなと思います。ありがとうございます。

結構自分が知らなかったことが多かったので、列挙してしまうとほぼコピペ記事みたいになってしまうので詳細はリンク参照をしてもらうとして、一つまとめるとしたらGoは他の言語とはエラーハンドリングのやり方について結構違いがあるよーと言うことでした。

Goのエラーハンドリングと言うよりは、そもそものエラーハンドリングの設計等についての知見がなさすぎるなと言う所ですね。

そのあたりを深めるためには、

・自分でもっとコードを書く

・プロダクトコードを読む

・ライブラリを読む

あたりをやっていきたい所存です。

 

★★★

Gopher道場 昇段審査 - Gopher道場

このコンテンツすごく良さそうなので、明日以降やってみます。

サンプルコードとか理論だけ理解しても正味腹落ちはしていないので、やはり実際に自分で手を動かして四苦八苦してみるのが良いだろうと思います。

 

このスラックメンバーになったら見られるYouTube動画で、エラー周りの動画を見たけども、今の自分にはちょっと高度すぎて理解できませんでした。高度すぎると言うよりは具体的なコードが結構出てきたので、Goの書き方にまだ慣れきってないため何をやっているかの把握に時間がかかりそうだなと思い全てのコードをちゃんと(というかほとんど)読んでないというのもありますが。

ある程度自分で考えて行動書いてみて、ちょっとめんどくさいなぁとかこういう場合どうすればいいんだろうとかそういった疑問や課題が出てきたときに見てみるとなるほどねとなるような気がしています。

とりあえず現時点ではそのレベル感と言うことを今ここで記録しておきます。

 

ここのコンテンツの1つに課題というのがあり、4つの課題が与えられてそれを各自実装してみて提出してください、提出先は こちらのGitHubリポジトリにプルリクを出してくださいと言うものがありました。

自分で考えて描くと言うのをこの段階でようやっと着手しようかなと思います。おそらくこの4つの課題をやることで、Goエンジニアとして最低限必要な知識や経験がつくようなものを設定してくれているのかなと勝手に期待しています。

他の人が提出した課題も見られるので、自力で完成させた後に見比べてみて、なるほどそうやって書くのかとか、これはどういう意味なんだろうとかいろいろ比較して思うことが出てきそうなので、それもまた楽しみではあります。

自分がPythonを学びたての時に、Atcoderで他の人が書いたコードを見て、なるほどそうやってスマートに書くのかとか、なんだこの記法は?と言う学びがあったので、それに近いような感覚で良さそうです。


★★★

明日はGopher道場の課題をやってみることにします。

 

goのロギングについて、標準ライブラリのlogとzapをちょっと触ってみた所感

表題の内容について学習しました。

 

Goのロギングライブラリ 2021年冬

この記事を大いに参考にさせてもらいました。

goでは標準のログのライブラリがありますが、おそらくあんまりこれをそのまま使う事はしないのかなと思います。
というのも、標準ライブラリではログレベルの設定ができないみたいです。

現時点ではログ設計について正直あまり詳しくないですが、ログレベルを設定できないと言うのは自分レベルが見てもちょっと不便かなぁと思いました。
なので、ほんとに簡単にさくっとだけツールを作るときとか位しか使い所ないのかなと言う所感です。

★★★

て、どうやらzapと言うログのライブラリがデファクトになっているのかなあと言う感じがします。
いろんなソースを見たわけではないので全然確かな情報では無いですが、一旦そういうものとして理解してみます。

参照してた記事ではログライブラリはいろんなものが跋扈していたが、ザップの登場によってもうそれでいいじゃんというか最高じゃんみたいな感じになっているらしい。
高速に動作するのと、拡張性が高いというところがメリット。どうやら標準ライブラリよりも早いらしい。

★★★

いつもだったらQiitaのやってみた記事を見てみて、サンプルコードコピペして動いたってやって満足してたところ、もうエンジニアも5年位やっているのでそろそろ公式ドキュメントを 参照できるようにならないと まずいなと思い、今回はやってみた記事を見ないで 公式ドキュメントを見てサンプルとか動かしてみた。
README的なドキュメントの量がそんなに多くなかったたので読んだ内容をまとめてみると

- とりあえず爆速で動くよ
- なんかメモリとかをあんまり使わない感じでいい感じに実装しといたよ、だから早いんや
- 標準ライブラリより早いよ
- 構造化されたログを出力できるよ
- ゆるい型でログを出力したいならSugarを使って、 厳密にやりたいならそうじゃないやつを使うこともできるよ
- これら2つを併用することもできるよ
- zapはzapcoreの薄いラッパーだよ
- 先進的なツールとか、突飛な設定とか独自の設定をやりたいときは、zapcoreを自分たちで拡張して使うことができるよ

 

こんなところでしょうか。

余談ですが、DeepLのChrome拡張機能が便利すぎますね。

Google翻訳だとちょっとが長い文章だと拡張機能では使えなくなってしまうので、だめです。DeepLこそ至高。

★★★

標準のログライブラリも触ってみました。簡単にしか触ってないんですが、やっぱりログレベルがないと言うだけで使いづらいのかなあと言う気がします。
といいつつちゃんとロガーを使ってログの出力とかしたのはPythonだけなんですが。

goの標準モジュールもまだ主要なものも把握しきれてない部分もあるので、ファイルioとかそこら辺も周辺知識として整理できました。

ここでもちゃんとしたエンジニアになるために、Qiitaとかで調べる事はあまりせず、基本的なVSコードの補完で マウスオーバーしたときに出てくる説明とかで内容把握したり、引数を確認したりと言うことをやってみました。

こういうことがいわゆる緊急ではないが重要なことに分類されることなのかなぁと。

★★★

zapの 話戻ると、そもそも構造化されたログがなぜ必要なのかと言うと、現代の開発において(主にビックデータ領域なのかな?)、 ログはそのままログとして見るのではなくて、パイプラインとかでつないでログ基盤に集めて、そこで分析やら確認やらをするというのが多い?

そのためにそういった中間のツール群が連携しやすいように構造化されたログを出力したいという 要望があるみたいです。

これは以前データ基盤を使ってた時にもそういった形で連携をしてたので、なるほどそういうことかと言う腹落ちはしました。

一方で、データ基盤のようにデータそのものが大事である場合はそういった連携が必要なので構造化された形が良いのだけれども、単なるアプリケーションのログ、エラーログやデバッグのためのログとしては特に構造化する必要は無いのかなあと言う気もしています。

zapのログ出力のための記述もちょっと冗長なのかなと言う気もしたので、 ケースバイケースで使っていけばいいのかなぁと。ログ設計はいろいろ奥深そうなので、いろんなユースケースをもっと見てみたいなとちょっと興味がわきました。

★★★

次回課題としては、エラー発生時の ハンドリングとか、いい感じのログの出し方とかを整理してみたいです。

後は、今盲目的にzapが良いと少ない情報の中で信じているので、もう少し他の情報も漁ってみて、そもそもどういう観点で良い悪いの判断をするのかなというところを整理して、ログを残す際に必要なことがそもそも何なのかというところも少し深掘りしてみたいなと思ってます。

 

今日は3h40minくらい。

 

 

追記: zapがデファクトってのはさすがに嘘ついたかもしれないです。そんなこともなさそう。用途によって使い分けたいところですね。

nginxのプロセスとかワーカとかスレッドとかについてよくわからないので整理する備忘録

今nginxとflaskのコンテナを使ったシステムを動かしているのだが、そのあたりでワーカーとかそこらへんがよく整理できていなかったので、この機会に勉強し直してみる。

以前自分が書いた記事を参照してみる。 プロセスとスレッドとかタイムスライスとかスケジューリングがわからない - 巨人の足元でたじlog

こいつわかってねーなー。 この中にある有用な記事をもう一度読む。 イケてるエンジニアになろうシリーズ 〜メモリとプロセスとスレッド編〜 - もろず blog

とりあえずまとめ

プロセスは専用のメモリ領域を利用する
スレッドは共有のメモリ領域を利用する

スレッドのほうがいいじゃん。という結論?だった。

この辺はいいんだ。結局これが実際のアプリケーション、ミドルウェアになったときにどこに効いてくるのかを知りたいのが今日の目的。

nginxとflaskのアプリケーションを作ってみる。

検証したいことは、flaskのアプリケーションで重たい処理があって、1リクエストを処理中に次のリクエストが来たらどうなるかということ。 これをnginxとかがどううまいことやってくれるのかっていうことを確認したい。

Dockerfile

FROM ubuntu:latest
RUN apt-get update -y
RUN apt-get install -y python-pip python-dev build-essential
COPY . /app
WORKDIR /app
RUN pip install -r requirements.txt
ENTRYPOINT ["python"]
CMD ["app.py"]

requirements.txt

Flask==0.10.1
$  docker build . -t flask-thread:latest
$ docker images
REPOSITORY                                                                        TAG                 IMAGE ID            CREATED             SIZE
flask-thread                                                                      latest              5db7f165ba72        2 minutes ago       436MB

できた。

立ち上げる。

$ docker run -it -p 5000:5000 flask-thread

ブラウザからlocalhost:5000を叩く。 特に今は負荷もかけてないので、一瞬でレスポンスが帰ってくる

 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 926-602-021
172.17.0.1 - - [25/Sep/2019 16:56:22] "GET / HTTP/1.1" 200 -
172.17.0.1 - - [25/Sep/2019 16:56:22] "GET / HTTP/1.1" 200 -
172.17.0.1 - - [25/Sep/2019 16:56:23] "GET /favicon.ico HTTP/1.1" 404 -
172.17.0.1 - - [25/Sep/2019 16:56:35] "GET / HTTP/1.1" 200 -
172.17.0.1 - - [25/Sep/2019 16:57:01] "GET / HTTP/1.1" 200 -

sleep処理を作成

from flask import Flask
import time
from datetime import datetime
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Flask Dockerized.Ver.0.1.4'

@app.route('/sleep')
def sleep():
    time.sleep(3)
    dt = datetime.now
    return dt


if __name__ == '__main__':
    app.run(debug=True,host='0.0.0.0')

これに連続して6回リクエストを送ってみると、

172.17.0.1 - - [25/Sep/2019 17:06:24] "GET /sleep HTTP/1.1" 200 -
172.17.0.1 - - [25/Sep/2019 17:06:27] "GET /sleep HTTP/1.1" 200 -
172.17.0.1 - - [25/Sep/2019 17:06:30] "GET /sleep HTTP/1.1" 200 -
172.17.0.1 - - [25/Sep/2019 17:06:33] "GET /sleep HTTP/1.1" 200 -
172.17.0.1 - - [25/Sep/2019 17:06:36] "GET /sleep HTTP/1.1" 200 -
172.17.0.1 - - [25/Sep/2019 17:06:39] "GET /sleep HTTP/1.1" 200 -

と、1つの処理が終わってから3秒待っている。 つまり、一つの処理のレスポンスを返さない限りは他のリクエストの処理が開始できないということ。

このあたりに関しては昔調べてた気がした。 Flaskのデフォルトでは同時アクセスを処理できない - Qiita これにコメントしといた。

ところでいま適当に動かしてるのはFlask==0.10.1だった。 最新版のFlask==1.1.1に変更してみる。

 * Serving Flask app "app" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 209-172-304
172.17.0.1 - - [25/Sep/2019 17:14:07] "GET /sleep HTTP/1.1" 200 -
172.17.0.1 - - [25/Sep/2019 17:14:08] "GET /sleep HTTP/1.1" 200 -
172.17.0.1 - - [25/Sep/2019 17:14:10] "GET /sleep HTTP/1.1" 200 -
172.17.0.1 - - [25/Sep/2019 17:14:12] "GET /sleep HTTP/1.1" 200 -
172.17.0.1 - - [25/Sep/2019 17:14:13] "GET /sleep HTTP/1.1" 200 -
172.17.0.1 - - [25/Sep/2019 17:14:15] "GET /sleep HTTP/1.1" 200 -

3秒待たずして、次のリクエストが返ってきている。

これがマルチスレッドになっているということか。

ところで、会社のコードではworkerとかだった気がする。

flaskではなくて、gunicornの設定だった。
そもそも、flaskは裸で使うのはdev環境だけにしなさいって注意されてるしな。
gunicornの設定・挙動ををまた確認しなくては。

flask + gunicorn

そもそも今までなんの気なしに書いていたDockerfileの末尾

ENTRYPOINT ["python"]
CMD ["app.py"]

これの表現気になった。 このあたりは雰囲気でやっている。
[docker] CMD とENTRYPOINT の違いを試してみた - Qiita をみると、

docker image からdocker container を実行するときにCMD やENTRYPOINT の記述内容が実行されます。 1つのDockerfile にCMD、ENTRYPOINT は1度のみ。 ※ 複数記載されている場合、最後の1個が実行される。

とな。
話が違う。 一旦、以下で試してみる。

# ENTRYPOINT ["python"]
# CMD ["app.py"]
ENTRYPOINT ["python", "app.py"]

こちらでも挙動は特に問題なさそう。
このあたりはまたの機会に整理することにする。

docker-composeでgunicorn+nginx+flaskを動かしてみた話 - Qiita

gunicornの使い方なんとなく理解。

CMD ["gunicorn", "flask_sample:app"]

と書いてあるところの、flask_sampleがファイル名、appが最初に実行させたい関数か。

FROM ubuntu:latest
RUN apt-get update -y
RUN apt-get install -y python-pip python-dev build-essential
COPY . /app
WORKDIR /app
RUN pip install -r requirements.txt
ENTRYPOINT ["gunicorn", "app:app", "-b", "0.0.0.0:5000"]

これでbuildしてd run -it -p 5000:5000 flask-thread すると、複数リクエストを投げると、1リクエストずつしか処理されていない。 これは最初にまずgunicornがリクエストを受け付けているが、gunicornはデフォルトでマルチスレッドとかになっていないからなはず。

これをマルチスレッドにしたかったら、gunicornのconfigに設定する。
gunicorn + Flask + nginx + Systemdで動かしてみた - Qiita

guniconf.py

import multiprocessing

workers = 2

Dockerfile

ENTRYPOINT ["gunicorn", "app:app", "-b", "0.0.0.0:5000", "--config", "guniconf.py"]

これで2スレッドで実行できる。

AWSの多要素認証まわりを整える

今まではメールアドレスとパスワードだけでルートユーザーロイグ員していました(個人だし別にいいやろと思って) しかしこれはまずいなと思ってのでちゃんと段階認証をやっていきたいと思います

Googleの2段階認証をアプリを使わずMacのターミナルで行う方法 - Qiita これを非常に参考にして設定。

brew install oath-toolkit

エイリアスAWSのアカウント番号を入れておく。 .zshrcにエイリアスを登録。

alias mfa111122223333='oathtool --totp --base32 "AAAAAABBBBCCCCCDDDDDDDDEEEEEEEFFFFFFFFGGGGGGGGGGGGGGGGGGGGGGHH" | pbcopy'
alias mfa99998888777='oathtool --totp --base32 "AAAAAAAAAAAABBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDD" | pbcopy'

念の為、スマホの方でも同じキートークンで登録はしておく。

競技プログラミングのテストケース実行環境を用意しようとしてハマった

GitHub - kmyk/online-judge-tools: Tools for online judge services. Downloading sample cases, Testing/Submitting your code, and various utilities.

これを用意しようと思った。

pip3 install oneline-judge-tools

pip3 install online-judge-tools 
Collecting online-judge-tools
  Using cached https://files.pythonhosted.org/packages/61/35/e04d089b6eaf045b0b20b6105e53f86e65d393d64430185fc43672e8f922/online-judge-tools-6.2.1.tar.gz
    ERROR: Complete output from command python setup.py egg_info:
    ERROR: Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/private/var/folders/m8/y33xsjwj1ms48m5mpbsm9n400000gn/T/pip-install-ng_ziqtr/online-judge-tools/setup.py", line 10, in <module>
        raise RuntimeError('setuptools>=30.3.0 is required for "setup.cfg"')
    RuntimeError: setuptools>=30.3.0 is required for "setup.cfg"
    ----------------------------------------
ERROR: Command "python setup.py egg_info" failed with error code 1 in /private/var/folders/m8/y33xsjwj1ms48m5mpbsm9n400000gn/T/pip-install-ng_ziqtr/online-judge-tools/

「Command "python setup.py egg_info" failed」

で検索したところ、日本語で一番上に出てきたものを見てみる。

ja.stackoverflow.com

なるほど。

$ pip install --upgrade pip setuptools                                                                    ✘ 1 master ◼
Requirement already up-to-date: pip in /Users/yoshii/.anyenv/envs/pyenv/versions/3.6.0/lib/python3.6/site-packages (19.1.1)
Collecting setuptools
  Downloading https://files.pythonhosted.org/packages/ec/51/f45cea425fd5cb0b0380f5b0f048ebc1da5b417e48d304838c02d6288a1e/setuptools-41.0.1-py2.py3-none-any.whl (575kB)
     |████████████████████████████████| 583kB 3.9MB/s
Installing collected packages: setuptools
  Found existing installation: setuptools 28.8.0
    Uninstalling setuptools-28.8.0:
      Successfully uninstalled setuptools-28.8.0
Successfully installed setuptools-41.0.1

おお。
pip install online-judge-tools
→行けました!

 oj download https://atcoder.jp/contests/abc128/tasks/abc128_a

一番最近のコードを持ってくる。 実行ディレクトリと同じディレクトリにtestディレクトリができていた。

├── a.rb
└── test
    ├── sample-1.in
    ├── sample-1.out
    ├── sample-2.in
    ├── sample-2.out
    ├── sample-3.in
    └── sample-3.out

こんな感じだ。
これからどうすればいいのか。

ojのヘルプコマンドだけ見て雰囲気でやってたけどだめだったので、ここをちゃんと見たら書いてくれてた。

kimiyuki.net

$ oj test -c ./a.rb
[*] 3 cases found

[*] sample-1
/bin/sh: ./a.rb: Permission denied
[x] time: 0.005608 sec
[-] RE: return code 126
[-] WA
output:

expected:
3


[*] sample-2
/bin/sh: ./a.rb: Permission denied
[x] time: 0.005723 sec
[-] RE: return code 126
[-] WA
output:

expected:
0


[*] sample-3
/bin/sh: ./a.rb: Permission denied
[x] time: 0.006032 sec
[-] RE: return code 126
[-] WA
output:

expected:
58


[x] slowest: 0.006032 sec  (for sample-3)
[-] test failed: 0 AC / 3 cases

パーミッション問題。。。

実行ファイルに実行の権限をつければいいだけだった。

chmod 755 ./*

これで実行できましたー

念のため一応補足をすると、rubyのコードで書いたので、コードの先頭に

#!/usr/bin/ruby

を書き足しました。(これは各人の実行環境に合わせて臨機応変に)

ちなみに自分が普段使っているruby

which ruby
/Users/yoshii/.rbenv/shims/ruby

で、もはやどうやってrubyの環境構築をしたかは覚えていないのですが、たしか普通にrbenvかanyenvとかを使っていた気がする。

で、こういうパッケージ管理システムを使っているときのシバンの書き方についての正しい書き方は、

qiita.com

によると、

#!/usr/bin/env ruby

みたいな感じらしいので、それにならってやってみたが、うまくかけました!!

これにてテスト環境構築完了!!!

pythonではじめるソフトウェアアーキテクチャ読書メモ③

3章テスト容易性

テストは書いたほうがいい。それだけはわかっていながらも、雰囲気で理解していてちゃんとしたテストを書くことを先延ばしにしてきた。(というよりはテストを書く必要性に迫られなかった。)

ちゃんとテストの意義や方法論を理解しておきたい。

テストの種類

実際に自分が一番やってきたことといえば、パフォーマンステストくらいだ。そうか、あれも一種のテストだったんだな。
テストコードを書く機能テストだけがテストなのかと思いこんでいたが、様々な観点からテストをする必要があるんだな。ふむ。

テストの戦略

複雑さの削減

  • 結合度を下げる
  • 凝集度を上げる
  • 明確なインターフェースを用意する
  • クラスの複雑さを減らす

なるほど、ここらへんはコードを読みやすくすることだったり、修正を容易にすることに加えてテストを容易にすることもモチベーションになるんだな。

予測可能性の改善

予測可能性を上げるポイント - 正しい例外処理 - 無限ループやデッドロック - 時間に依存する処理 - 並列処理 - メモリ管理

確かに今までコードを書いていて、厄介なポイントだったりするのはこのあたりだった気もする。
そのあたりをこうして体系的に整理できるのはありがたいな。
この中でも特に正しい例外処理についてはちゃんとした設計方法をわかっていない。 このあたりも後々体系的に整理する必要がありそうだ。

外部依存の制御と分離

データソースに関するテクニック

  • データを記述したローカルファイルの使用
  • インメモリデータベースの使用
  • テストデータベースの使用

SQLiteがよく使われると言っていたが、職場の人いわく、SQLiteはjoin?だかのクエリを投げたときに微妙に挙動がおかしくなる的なことを言っていた気がするので、複雑なテストを書くときにはあまり使えないかもしれない。

リソースの仮想化に関するテクニック

  • スタブ
  • モック
  • フェイク

モックとスタブの違いについて、スタブはデータを用意するパーツで、モックはデータを投げる受け先という認識だったのだが、これは合っているのか?
定義だけをあれこれこねくり回してもしょうがない。
実際に書くことになってから改めて名前は確認することにしよう。

今日はここまで。テストについてちょっとだけ理論入りました。

pythonではじめるソフトウェアアーキテクチャ読書メモ②

双方向結合の削減

  • 単一の方向に依存するように関数を定義する場所を考える。

抽象共通サービス・継承の活用

  • 同じような処理があるモジュール、あるいは今後同じような処理が増えていくと予想されるクラスは、共通する部分を親クラスとして抜き出す。

遅延バインディング

  • これの概念がいまいちよくわからなかった。調べてみても、VBの情報がたくさん出てきている。あまり有名な概念ではないのかな?例も実装イメージやメリットが良く理解できなかった。そういう概念があるくらいに留めておこう。

静的解析ツールとメトリクス測定

クラスでのコードの匂い

  • ゴッドオブジェクト
  • 定数クラス
  • 拒否された遺産
  • フリーローダー
  • 属性・操作の横恋慕

関数やメソッドレベルのコードの匂い

  • ロングメソッド
  • パラメータクリープ
  • 循環的複雑度
  • 長すぎる、または短すぎる識別子

自分として思い当たるフシは、ゴッドオブジェクト作りがち、長すぎる変数名つけがち。ってところを気をつけていきたい。 パラメータクリープ(関数やmwソッドのパラメータが多すぎるために関数呼び出しやテストが困難になっている状態)にもなりがち。しかし、テストをちゃんと書くことをしていないのでこれのダメさにも正味あまり気づけていない。やはりテストを書いていくことから始めていきたい。

循環複雑度

$ python -m mccabe --min 1 hoge.py

これで循環度を調べることができるらしい。

この他にも、コードの臭いを確かめるために、flake8などのツールは積極的に使っていく。