dogwood008の開発メモ!

最近のマイブームは機械学習, Ruby on Rails。中でも機械学習を使った金融商品の自動取引に興味があります。

【Rails/ActiveRecord】特定のカラムの情報だけ欲しい場合は、pluckかselectを使う

例えば、「特定の条件を持つユーザIDだけ欲しい」といった場合に有効。この時、 pluck を使うと戻り値は Array で、 select を使うと ActiveRecord::Relation が返る。

この時、 map を使うのは無駄なメモリを食ったり動作が遅くなったりするので、やってはいけない。

# このようなUserモデルがあるとする
User.where('created_at >= ?', Time.zone.yesterday)
# => #<ActiveRecord::Relation [<#User id: xxx, ...>, <#User id: xxx, ...>, ...]>
# 昨日と今日に登録したユーザのユーザIDを取る場合を考える。

# select だと、ActiveRecord::Relation が返る
User.where('created_at >= ?', Time.zone.yesterday).select(:id)
# => #<ActiveRecord::Relation [#<User id: xxx>, #<User id: xxx>, ...]>

# pluck だと、Arrayが返る
User.where('created_at >= ?', Time.zone.yesterday).pluck(:id)
# => [xxx, xxx, ...]

# これはやってはダメ、無駄にメモリを使ってしまう
User.where('created_at >= ?', Time.zone.yesterday).map { |user| userid }
# => [1, 2, ..., 10]

【mac】ワンプッシュでスクリーンロックをかける

MacBook Proの場合、指紋センサと電源ボタンが一緒になっている。電源が入っているときにこのボタンを押すと、すぐにスクリーンロックがかかる。

 

復帰するにはパスワードの入力か指紋の照合が必要なので、少し離席するときにセキュリティ確保の目的で実施するのがおすすめ。

 

また、画面も消灯するので、バッテリーを少しでも長持ちさせたいときにも有効かもしれない?

【Bash】tee でstdoutとstderrをそれぞれ別々にする方法 (Process Substitution)

要旨

Bashで用意されているプロセス置換 (Process Substitution) という仕組みを使うと、下記のような書き方ができる。

$ ls /foo/bar 1> >(tee stdout.log) 2> >(tee stderr.log)
ls: /foo/bar: No such file or directory  # <--- 標準エラー
$ cat stderr.log
ls: /foo/bar: No such file or directory  # <--- `./stderr.log` にも書き込まれた

背景

Bashで標準出力 (stdout)と標準エラー (stderr)をそれぞれファイルに出力する場合、次のように記述することで可能である。

$ ls /foo/bar > stdout.log 2> stderr.log
$ cat stderr.log
ls: /foo/bar: No such file or directory

ところが、これを tee に適用しようとすると、なかなか難しい。下記はうまく動かない例。

$ ls /foo/bar 2> tee stderr.log | tee stdout.log
$ cat tee  # <--- teeコマンドを呼ぶのではなく、 `./tee` というファイルに書き込まれた
ls: /foo/bar: No such file or directory
ls: stderr.log: No such file or directory

要旨に記載したプロセス置換を使うと、下記のように記述できる。

$ ls /foo/bar 1> >(tee stdout.log) 2> >(tee stderr.log)

応用

私は主に、この仕組みをRSpecの実行結果を特定の位置に出力させるために用いている。各Project毎のRSpecの設定を確認しなくても、毎回定位置を見れば必ずログが出力されていることが保証されながら画面にもアウトプットされて便利である。

下記を実行すると、そのログが次の2つのパスへ出力される。その際、実行時の日時がファイル名として記録される。

  • rspec/yyyy-mm-dd-HH-MM-SS_some_spec.rb.log
  • rspec/yyyy-mm-dd-HH-MM-SS_some_spec.rb.err.log
FILE=spec/path/to/some_spec.rb; OUTPRFX=`echo _$(basename ${FILE}.log)`; time RAILS_ENV=test bundle exec spring rspec ${FILE} 1> >(tee rspec/`date "+%Y-%m-%d-%H-%M-%S"`${OUTPRFX}) 2> >(tee rspec/`date "+%Y-%m-%d-%H-%M-%S"`${OUTPRFX}.err.log)

参考

www.gnu.org

qiita.com

【RSpec】letは遅延評価、後から上書きできる

RSpecの世界では、 let は遅延評価される。これは宣言した値が本当に必要とされる時まで評価を遅延するということを指す。具体的な例を持って説明する。

例えば下記のようなテストを行うとする。ここでは、あるAPIのエンドポイントにPOSTする際のステータスコードをテストしている。この中では context でログイン時・非ログイン時の分岐がされている。

# ※実際にこのコードで動作を確認したわけではなく、あくまで解説用。
RSpec.describe User, type: :request do
  describe '/api/v1/users/me' do 
    let(:path_to_endpoint) { '/path/to/endpoint' }
    let(:params) { { key: 'value' }.to_json }
    let(:headers) do
      { 'content-type': 'text/javascript' }
    end

    before do
      allow_any_instance_of(ApplicationController).to(
        receive(:current_user).and_return(current_user))
    end
  
    subject do
      post(path_to_endpoint, headers: headers, params: params)
    end
    context 'when logged out' do
      let(:current_user) { User.guest }
      it { subject; expect(response).to have_http_status(401) }
    end
    context 'when logged in' do
      let(:current_user) { FactoryBot.create(:user) }
      it { subject; expect(response).to have_http_status(200) }
    end
  end
end

この時、 before ブロックの中で ApplicationController#current_usercurrent_user を返すように定義しているが、実はこの時点では未だ current_user は評価されていない。実際に it ブロックが呼ばれた際、初めて before が評価され、その時になって初めて before ブロック内から参照されている current_user が評価される。そのため、 context で条件を分けて書いたような記法が可能になる。

context 'when logged out' のブロックでは current_userUser.guest と評価されるので、 ApplicationController#current_userUser.guest が返る。同様に、 context 'when logged in' のブロックも、 FactoryBot.create(:user) が返る。

このように書くと、 context 内の記述量を減らすことができ、各 context 間での違いを比較しやすくできる。

なお、 let によく似た構文として let! がある。これは let とは異なり、定義したその時点で評価されるので、必要な場合を除いて、できるだけ let で宣言するのが望ましい。

【Kubernetes】 ネットワーク不通・応答無しになったNodeは、K8sがTaintsを付けてくれる

Kubernetesを使っていると、Nodeが応答不能になった時にTaintsを付けてくれる。

例えば、Amazon EKS を使っていて、Nodeが応答できなくなった時(例えば、 Kubelet が止まってしまった時)には、下記のTaintsを付けてくれる。

  • node.kubernetes.io/unreachable:NoExecute
  • node.kubernetes.io/unreachable:NoSchedule

これはKubernets コントロールプレーンにあるKubernetes Nodeコントローラーが付けてくれるので、「Nodeが応答できなくなって、Nodeが自分自身にTaintsを付けられない」という心配はない(と思う、誤っていたらコメントで教えてほしい)。

これで何が嬉しいかというと、Taintsの付いているNodeへは、スケジューリングを行わず、他のNodeを優先的に使えるということができる点である。

また、他にも、メモリが少ないことを意味する node.kubernetes.io/memory-pressure や ディスク容量低下を示す node.kubernetes.io/disk-pressure 等がある。これらのTaintsを参考にすると、ヤバそうなNodeからさっさと退避させ、安全なNodeへ再配備させるということが可能になる。

Kubernetes に命令を出す kubectl には、 drain というコマンドが用意されており、これを使用することで「指定したNodeからPodを退避させる」「指定したNodeに『こいつはヤバイから再配備するな』というTaintsを貼れる1」を一度に行える。

参考

kubernetes.io

踏み台サーバ (Bastion Server)を経由したSSHを、コマンド一発で

要旨

予め下記のように ~/.ssh/config に記入しておくと、 ssh work_server を実行するとそのまま一発で踏み台サーバを経由した先のサーバへSSHで入ることができる。

# ~/.ssh/config

Host work_server
  User ec2-user
  HostName ip-xxx-xxx-xxx-xxx.ec2.internal    # <- Private IP Addr
  Port 22
  ProxyCommand ssh -W %h:%p bastion_server
  IdentityFile /path/to/key.pem

Host bastion_server
  User ec2-user
  HostName ec2-xxx-xxx-xxx-xxx.compute-1.amazonaws.com   # <- Public IP Addr
  Port 22
  IdentityFile /path/to/key.pem

何故今になって急にブログ執筆を再開し始めたのか

前回が2020年8月22日なので、実に1年と9ヶ月、日数にして638日ぶりである。前回は Google が発売している Andoid搭載スマートフォンPixel 4a でeSIMを使う記事だったのに、今は Pixel 6 Proを使っている。

blog.dogwood008.com

最近他人にプログラムやWeb技術のことを教えることが多くなってきて、知識の伝達の難しさを自分事として感じることが増えたことが、様々な理由のうちの一つである。

他にも理由がある。何かを伝える際、口頭なら一番アジャスタブルにその人に合った内容、濃度で伝えられる一方、別の人に説明する際には1からやり直しになってしまう。これを避けるため、自分が持っている知識をできるだけ細かい単位で吐き出していって、「詳しくはここを読んで」にしてしまえるようにしたいな、と思った事が挙げられる。最初に書籍に知識を残すことを発明した人は、本当に偉大である。

また、全く違う観点の理由もある。完全にIfの話しだが、もし今急に全ての記憶が吹っ飛んだら、どうやって食べて行こうか、ということを考えたことがある。その時に、記事として書き貯めたものがあれば、それを繋げて何かビジネスに繋げられるかもしれない。日々を惰性で過ごすより、少しずつ積み上げて、複利で未来に実を結べば良いな、という考えもある。

正直言って、今回紹介した踏み台サーバは、今となってはあまり使われなくなってきている技術ではないかと思う。イメージからコンテナ化して、Kubernetesに管理させたり、ECS や Heroku のような IaaS に乗っけてサーバレスにWebサービスを運営するのが、よりモダンな開発&運用のフローだと考えている。そういう構成なら、SSHでのサーバの操作はあまり推奨されないか、そもそもサーバが存在せず技術的に不可能である。それでも書いたのは、「頭の中にある情報技術的な知識を、可能な限り明文化して文章に吐き出していく」ことをこのブログの目的に本日決めたからである。

最後になるが、先日とある記事を目にし非常に感銘を受け、この記事を書くに至った。紹介して終わりにする。

blog.hiroppy.me