ユユユユユ

webエンジニアです

下降気味のモチベーション

 8月に読むテキストを書いた。本当であれば月末に振り返りの記事を書くはずだった。結局、8月のうちに終わらせることはできなかったし、9月ももうすぐ半分といういまの時点でもまだ半分と進められていない。

jnsato.hateblo.jp

 やっぱり、仕事をしながら勉強も進めるというのは容易でない。だからこそそれは理想であるのだと痛感している。

 案件のリリースがあった関係で、先月の稼働時間は大きく増えていた。楽しさはあれどおのずとストレスも溜まり、終業後や週末の空いた時間にまでコンピュータに向かうのは控えていた。代わりにジムで身体を動かしたり、料理をしたりしていた。まあまあ健康的な生活は守れたわけだが、なんだか人生が守りに入ってしまっているような気がしてどこか収まりの悪さを感じている。

 仕事に学びの機会を求めるのが筋違いであることは感じざるを得ない。もちろん学びはある。しかし密度は期待できない。日々の業務はどうしても、自分の知識を小分けにしてアウトプットしている感触であって、濃度のあるインプットは結局自分で求めるしかなさそうだ。

 そうわかっていても、終業時間間際に依頼が来ると対応しないわけには行かないと考えてついサービスしてしまうし、週末も何かまずいことが起きていないかと、つい slack を覗いてしまう。そうして少しずつ疲弊してしまい、自分だけの楽しみのためにプログラミングをする時間が減ってしまう傾向がある。

 仕事とはそういうものなのだろうし、結局のところは自分自身の問題に他ならないのだが、どう落としどころを見つけていくべきなのかはわからない。単純に、春以来あったモチベーションの波がピークを打って下降気味なだけで、しばらくすればまた気持ちよく向上心を持てるようになるのかもしれない。というか、そうあることを祈るくらいしかしていない。

 なにかとっかかりがあれば取り戻せる気はする。それがなにであるのか、意識的に探しあてないといけないと感じている。

Amazon RDS for PostgreSQL のタイムゾーンはほとんど常に UTC であることに要注意

 postgres の current_timestamp 関数を使ったクエリをアプリケーションに実装していたところ、本番データベースにタイムゾーンのずれによる時刻の不整合を招きいれてしまった。

TL;DR

  • RDSで非 UTC の時刻を扱うユースケースでは current_timestampcurrent_date といったタイムゾーン依存の関数は使うべきでなく、プログラム経由で動的に生成するのがよい。
  • 状況を再現させるコードをレポジトリに公開しているので、詳しくはこちらを参照いただけるといいかと思う

github.com

再現する条件

アプリケーションのタイムゾーンとして UTC を使っていないこと

 筆者の属するプロジェクトでは rails (activerecord) を使っており、タイムゾーンの設定は次のように JST を指定している。ここで UTC を指定しているような場合、特に問題はないだろうと思われる。

# config/application.rb

module SomeApplication
  class Application < Rails::Application
    ...
    config.time_zone = 'Tokyo'
    config.active_record.default_timezone = :local
    # config.active_record.default_timezone = :utc
    ...
  end
end

データベースに Amazon RDS を利用していること

 RDS のタイムゾーンについて、AWSの公式FAQに次のようなエントリがある。

aws.amazon.com

すべての Amazon RDS DB インスタンスは、デフォルトで UTC/GMT 時間を使用します。タイムゾーンの変更はオプションです。

データベースレイヤーで UTC タイムゾーンを使用するのがベストプラクティスです。UTC は夏時間 (DST) を持たないため、後でシフトするときに時間を調整する必要はありません。

ローカルタイムゾーンを使用する必要がある場合は、代わりにアプリケーションレイヤーでタイムゾーンを変換します。

より具体的には

UPDATE users SET updated_at = current_timestamp;

のように current_timestamp を直接埋め込んだクエリを発行すると、データベースのタイムゾーン、すなわち UTC が採用されて、アプリケーションのタイムゾーンと齟齬が生じる。

 アプリケーション層でローカル時間を生成してクエリに渡すには、次のようなアプローチをとる必要がある。以下は activerecord を前提に記述するが、やるべきことはどの言語/フレームワークを採用していても変わらないはず。

ActiveRecord::Base.sanitize_sql_array(['UPDATE users SET udpated_at = ?', Time.current])

 もっとも、多くの場合はこのようなまわりくどい書き方をする必要がない可能性も高い。 activerecord であれば、単に user.touch と記述すれば上記のユースケースは満たせる。

 とはいえ筆者の場合、ある程度の大きさのバッチタスクの一部にて、大量のレコードをインスタンス化せずに一括更新する箇所でこの問題に遭遇した。似たような条件に出くわすことはないとはいえず、またそのような場合には十分な注意が必要となる。

AtCoder Problems の Training モードで中難度をコンプリートした

 4月半ばに AtCoder の問題を解くことを初めて、ほぼ4ヶ月となる。

 5月にかけて集中して過去問に取り組んで、その後すこし熱がおさまっていた。これはもっぱら Project Euler に心変わりしていたというのが大きい。直近ではむしろ Project Euler に取り組むのが後回しになってしまっているものの、代わりに AtCoder の過去問は一日一問取り組むように自分に課してきた。そのひとつの区切りがやってきたので、記録しておく。

 一日一問のペースで100問を解いたわけだから、ほとんど3ヶ月かけてやってきたことになる。言ってみれば同じ程度の難易度の問題に繰り返し挑んでいたわけだから、それほど劇的に腕がよくなったという感覚はない。

 ただそれでも、はじめの頃を思い返すと、そもそもの方針すら思い浮かばずに頭も手も止まってしまう、ということは減った気がする。困難な問題に出会うこともあるが、しっかり考えて向き合えばそれなりの速さで解法にたどり着けるようになっているのかもしれない。

 もっとも、解法は見つけられても、エッジケースを特定できずにいつまでも正解できないということは高い頻度で起こる。そのあたりが自分の課題とわかってはいても、どういう練習を積めばそれを手早く発見できるかはわからない。

 コンテストにはぼちぼち参加している。スケジュールが合わないことが多いのがほんの小さなフラストレーションである。それほど週末に予定を詰め込んでいるわけでもないのだが、コンテスト日に限って忙しい。土曜開催と日曜開催のランダムさに振り回されているようなところはある。とはいえ、あわててレーティングをあげる必要もないし、やったところでそんなに劇的には上がらないだろうから、引き続きのんびり取り組んでいくつもりでいる。

activerecord で一括更新を行うときの選択肢をベンチマークで比較してみた

 Rails を使ってある程度の大きさのレコード群を一括更新する処理の実装方針を考えていた。愚直にイテレートして一件ずつ update していくのは論ずるまでもなくバッドプラクティスであるとして、その他の選択肢の優劣は検討してみないとよくわからないというのが正直なところであったため、ベンチマークをとって調査してみた。この記事はその記録である。

TL;DR

  • activerecord-import を選択する積極的な理由はなさそう
  • update_all で実装できるのであればそうするのが一番

方針

 ほぼ裸のテーブルを作成し、n件のレコードを一括更新する。nは任意の自然数でいいが、今回は10,000件と100,000件でそれぞれ調査してみた。

 比較対象は以下の5つ。ただし素の .update については最初から期待しておらず、ほとんど参考記録としての扱いとなる。

作ったもの

github.com

結果

https://github.com/sato11/benchmark-bulk-update-rb/blob/master/README.md

解釈と所感

 まず目に付くのは、 .import.upsert_all はレコード数におおむね比例して処理時間と消費メモリが増加しているが、 .upsert_all の方が20%ほどパフォーマンスに優れている点である。

 .upsert_allRails 6 で鳴り物入りで実装された新機能であるわけで、優れた結果を出すのは当然といえば当然といえる。いずれにせよ、これから実装を検討するうえでは、 activerecord-import を選択する積極的な理由は個人的には見出せない結果となった。

 他方、生の UPDATE 文を .execute に渡すコードと、 .update_all で同等のクエリを生成するコードは、パフォーマンスのうえで有意な差はほとんどなかった。メモリ消費量と実行速度において一長一短あるように見えつつも、レコードの数に関わらず一定のパフォーマンスが担保されていることもあり、これはほとんど無視できる程度の差異にすぎない。

 一行で書けるだけ .update_all に軍配が上がるだろう。.update_all ではカバーできないような複雑なクエリを扱うというのでなければ、 .update_all を使っておくのが安心そうである。

マルチプレクサとデマルチプレクサ

 The Elements of Computing Systems に沿って基本の論理ゲートを実装している。

jnsato.hateblo.jp

 NOT, AND, OR, XOR のような耳慣れた論理に加えて、 Multiplexor と Demultiplexor という耳慣れない回路が、基本の論理ゲートとして扱われていた。そもそも論理ゲートというものを、「2つの入力を1つの出力に合成するもの」のように捉えていた僕にとっては、複数の入出力を持ちうるこれらのゲートは異色の存在に思われた。ノートの書写しにはなるが、TILとして書いておきたい。

multiplexor

 マルチプレクサは、3つの1ビットの入力を受け取り、1つの1ビットの出力を与える。入力のうち1つはセレクタと呼ばれ、このセレクタがフラグとして立っているかどうかで、残りの2つの入力のうちどちらが出力されるかが決定される。

 真理表を書くと次のようになる。

input1 input2 selector output
0 0 0 0 (=input1)
0 1 0 0 (=input1)
1 0 0 1 (=input1)
1 1 0 1 (=input1)
0 0 1 0 (=input2)
0 1 1 1 (=input2)
1 0 1 0 (=input2)
1 1 1 1 (=input2)

 高級言語であれば、IFを使って条件分岐するような処理になる。

bool input1, input2, selector;

// 入力処理

if (selector)
    printf("%d", input2);
else
    printf("%d", input1);

 論理ゲートで実装する上では、こうなる。

f:id:jnsato:20200803222313p:plain

demultiplexor

 デマルチプレクサは、2つの1ビットの入力を受け取り、2ビットの出力を行う。今度も2つの入力のうち一方がセレクタであり、セレクタの値によって、出力ビットのどちらに入力ビットを出力するかが決定される。

 真理表は次の通り。

input selector output
0 0 00
0 1 00
1 0 10
1 1 01

 高級言語であれば、さながらこうなるだろう。

bool input, selector;

// 入力処理

if (selector)
    printf("0%d", input);
else
    printf("%d0", input);

 実装はこうなる。

f:id:jnsato:20200803223424p:plain

8月に取り組むテキストを紹介する

 月初めのモチベーターとして、準備したテキストを紹介していく。

f:id:jnsato:20200802152524j:plain

The Elements of Computing Systems

 Nand2tetris という愛称が与えられていて、知名度も高い教科書である。O’Reily から『コンピューターシステムの理論と実装』という邦題で出ている日本語訳も評価は高いようす。

 Nand2tetris という呼称が端的に表現しているように、 NAND という単一の論理ゲートを出発点に、CPU、メモリ、機械語アセンブリコンパイラ、OS までのコンピューターシステムを作り上げて、最後にはそのマシンの上でテトリスを実装するという方針の教科書である。

 骨太の一冊のように思われるが、実際には学部1年生でも取り組めるように、前提知識をほとんど要求しないように書かれている。学部レベルの計算機科学を学び直すという意味ではうってつけの教科書になる。ただし、「マシンを作り上げる」という目標のために、パイプライン処理やメモリ階層といった重要概念への言及が犠牲になっているとは指摘されている。

In seeking simplicity and cohesiveness, Nand2Tetris trades off depth. In particular, two very important concepts in modern computer architectures are pipelining and memory hierarchy, but both are mostly absent from the text. 1

 基本知識へのハンズオンとしてこれを読み終えて、アーキテクチャの全体像を手に入れた上で、より古典的な計算機科学のテキストに進むのがいいのだろう。とっかかりとしては申し分のないテキストであるようだから、時間をかけても完走したいところ。

『新装版 数学読本2』

新装版 数学読本2

新装版 数学読本2

 対数関数と三角関数について、ときおり参照する機会がありつつ、その度に検索エンジンに頼ってぼんやりと思い出すことを繰り返していた。受験勉強以来、数学の勉強から離れていたというブランクからくるコンプレックスを打破する上でもいい機会ではないかと、高校レベルの参考書を求めて、ここに行き着いた。

 著名なシリーズであるから、内容の品質には一分の心配もない。第一巻を飛ばしていきなり第二巻から着手するのはすこし腰が引けたが、遠慮していて勉強ができるかと開き直った。

 関数一般から取り組んでいて、二次関数の問題を解いたりしている。一日の終わりに、コンピュータを閉じて、裏紙に粛々と手計算をしていく時間には、日常から離れたリラクゼーションがあって、気持ちいい。

おわりに

 7月は C++ の参考書を中心に取り組んでいた。継続して C++ の勉強を膨らませていく、という方針もありえたが、結局のところ業務で使わないスキルであるから、モチベーションの維持に不安があった。

 計算機科学と数学については、そこに知識の欠落があるというコンプレックスは自覚してながら、「業務には必要ない」とか「いまの自分には必要ない」などと奇妙な正当化をして遠ざけてきた節がある。それでいて、「いつかは体系立てて学んでみたい」というようなことを飲み会の場で口走るようなこともあって、いくぶん迷走していた感じは否めない。

 短期的な投資対効果が見込める学習対象はほかにごまんとある。しかし今後長くコンピューターに携わっていく気持ちがすこしでもあるのであれば、できるだけ早くに計算機科学のイロハを学び直すのがいいだろう、とここのところ考えている。

 とはいえそれは、キャリアのための勉強とか、お金のための勉強とは思わず、好奇心を満たすための勉強と割り切って取り組むのが無難だろう。

 いま、計算機科学の観点からコンピューターアーキテクチャを作り上げるという課題を前にして、ここで学ぶひとつひとつの知識が、新たな視点をもたらしてくれるのだと胸をときめかせている。この感覚は、大学用の MacbookAir に初めて Ruby をインストールした日々の清らかな思い出に通じるものがある。

 モラトリアムの不安に押しつぶされていた時代に Ruby と出会い、のめり込むように学んできた結果としていまの自分があるけれど、その駆動力となったのは結局、好奇心であったはず。そして同じ好奇心を持って臨める対象であれば、なんでもある程度は習得できるはず。そう思って、一文無しの学生のように謙虚な気分で取り組んでいきたい。

NAND 演算があればあらゆる論理演算ができる

 NAND とは NOT AND のことで、真理値表は次のようになる。

x y x NAND y
0 0 1
0 1 1
1 0 1
1 1 0

 見ての通りの、 AND の否定形である。否定にすぎないから、論理学ではそう注目される演算でもないように思う。

 しかし電子回路の世界ではこの演算が中心の座を占めるといっても過言ではないらしい。というのも、 NAND 演算はそれ単独で、 AND, OR, NOT の論理を導出できるのである。AND, OR, NOT が使えればあらゆる論理が表現可能であるから、つまるところ NAND があればあらゆる論理が表現可能であるということにほかならない。

 例えば、 x NAND x を真理値表に表してみる。 x が同時に 0 と 1 であることはないから、二行で十分となる。するとこれが、 NOT x となっていることは一目瞭然だ。

x x NAND x
0 1
1 0

 これを利用して、 x NAND y を否定してあげると、 AND が導ける。

x y x NAND y (x NAND y) NAND (x NAND y)
0 0 1 0
0 1 1 0
1 0 1 0
1 1 0 1

 同じように、 OR はこう表せる。

x y x NAND x y NAND y (x NAND x) NAND (y NAND y)
0 0 1 1 0
0 1 1 0 1
1 0 0 1 1
1 1 0 0 1