valid,invalid

関心を持てる事柄について

ISUCON9 予選の過去問でNew Relicを使う

あとN+1回寝るとISUCONです。私はいま予選突破の夢を見つつNew Relicを素振っています。

本記事ではNew Relicの説明は特にしません。また、Rubyで実験しててちょっとハマったところを掘り下げたログが実はメインです。

(2020-07-22追記) 本記事を書いたときには気付いていなかったのですが同じことを公式のWebinarでも行うようでした。(7月30日開催|せっかく無料なのでISUCONでNew Relicをスッと使う方法RubyだけでなくGo, Pythonの設定の話やその他盛りだくさんなので期待!!

New Relic meets ISUCON

New Relicは業務で過去に使っていて大変便利だったのでISUCONもこれぐらい楽にメトリクス見てぇな〜と思っていたら、自腹契約して使うという道もあったのだということをISUCON9優勝の白金動物園mirakuiさんによる振り返りで学びました。

今回はたまたま別件で間違って Annual Subscription を自腹契約してしまった New Relic の個人ライセンスがあったので、それを使いました。分析できることは自前のスクリプトでやってるときとだいたい同じなんだけど、僕だけじゃなくてメンバーがベンチ結果分析に参加したり、同じ数字を見て語れるところが便利でした チーム白金動物園として ISUCON 9 予選を通過しました - 昼メシ物語

それはそれで良い話なのですが、なんと2020年のISUCON10ではNew Relic社がスポンサーについており、New Relic全機能を大会前後の期間中制限なく利用できるライセンスを参加者全員に提供するというすごいことになっています。自腹契約しなくても自作スクリプト頑張らなくても分析が始められます。(気に入ったらお勤め先でエンタープライズ契約しよう!)

New Relicアカウント取得

以下に従って進めるだけです。

ただし、過去に登録済のメールアドレスで登録するとメールが永遠に届かないことがある(自分がそうだった、昔いつか登録したのを忘れてた)のでその場合は別メールアドレスやエイリアスを使うなどして回避しましょう。

blog.newrelic.co.jp

isucon9-qualify環境構築

本題ではないのでさらっと。

やり方はなんでも良いのだけど僕は https://github.com/matsuu/vagrant-isucon を使い、Vagrantで環境構築してます。8以前の過去問に比べると言語バージョンとか諸々が比較的新しいのでVirtualBoxを起動しておいて、vagrant upすれば動きました。

$ git clone https://github.com/matsuu/vagrant-isucon
$ cd vagrant-isucon/isucon9-qualifier-standalone
$ vagrant up

ssh仮想マシンに繋いだら、最初はGolangのサービスが起動しているのでRubyに切り替える。

$ vagrant ssh

$ sudo systemctl stop    isucari.golang.service
$ sudo systemctl disable isucari.golang.service
$ sudo systemctl start   isucari.ruby.service
$ sudo systemctl enable  isucari.ruby.service

ベンチマークテストが完走すれば環境構築OK。

$ cd /home/isucon/isucari
$ bin/benchmarker
(ログ省略)
{"pass":true,"score":1410,"campaign":0,"language":"ruby","messages":[]}

New Relicの導入

まずは公式通り進めていきます。ログインするとapplicationの各言語ごとのsetup手順が出てきます。こんな感じのURLです (以下setup pageと呼ぶ)。 https://rpm.newrelic.com/accounts/xxx/applications/setup#ruby

さらにframework別の手順も Sinatra instrumentation | New Relic Documentation などに記されていますので一読すればOK。

Install gem

# /home/isucon/isucari/webapp/ruby/Gemfile
source 'https://rubygems.org'

+gem 'newrelic_rpm'
gem 'puma'
gem 'sinatra'
...
$ cd /home/isucon/isucari/webapp/ruby
$ /home/isucon/local/ruby/bin/bundle install

applicationからgemを読む

Railsと違って明示的にrequireする必要があります。Sinatra instrumentation | New Relic Documentation によるとSinatraの読み込み直後におく必要があるようです。

# /home/isucon/isucari/webapp/ruby/lib/isucari/web.rb
require 'json'
require 'securerandom'
require 'sinatra/base'
+require 'newrelic_rpm'
require 'mysql2'

設定ファイルを配置

newrelic.ymlを setup page からダウンロードし、application root (/home/isucon/isucari/webapp/ruby/)に置く。

その他

New Relic関連のlog

デフォルトだと /home/isucon/isucari/webapp/ruby/log/newrelic_agent.log にはログが吐かれます。設定がちゃんとできてるかな?と思ったらログに以下のような記述があるか見てみましょう。

[2020-07-22 11:58:45 +0000 vagrant (32128)] INFO : Reporting to: https://rpm.newrelic.com/accounts/:account_id/applications/:application_id
ライセンスキーの扱い

デフォルトではnewrelic.ymlに記載されているが、secret情報をgit historyに含めてしまうのは好ましくないです。 (git pushしてrepositoryをpublicにすると世界中の人がライセンスを利用できてしまう)

newrelic.ymlからlicense_key: xxxxxの行を消し、プロセス起動時に環境変数として与えるようにしておくと安心です。

NEW_RELIC_LICENSE_KEY=xxxxx

Ruby agent configuration | New Relic Documentation


ここまでは簡単ですね。ここからちょっと悩みました。

benchmarkerが失敗する

上述の手順でセットアップした直後にベンチマーカーを実行するとfailします。

$ bin/benchmarker
(ログ省略)
2020/07/21 08:58:07 main.go:129: cause error!
   {"pass":false,"score":0,"campaign":0,"language":"ruby","messages":["POST /login: got response status code 500; expected 401","GET /settings: got response status code 500; expected 200","GET /settings: got response status code 500; expected 200","GET /items/18697.json: got response status code 500; expected 200","POST /login: got response status code 500; expected 200","GET /items/50000.json: got response status code 500; expected 200","GET /upload/336bd3af5fbb247a55354184a3dfb35f.jpg: got response status code 500; expected 200","POST /items/edit: got response status code 500; expected 200 (item_id: 409)"]}

期待するレスポンスが返っていない。アプリケーションの挙動を変えてしまった。

省略したログからスタックトレースを抜粋して貼ったのが以下。

"status code: 500; body: RuntimeError: can't add a new key into hash during iteration
    /home/isucon/isucari/webapp/ruby/.bundle/ruby/2.6.0/gems/sinatra-2.0.7/lib/sinatra/base.rb:31:in `accept'
    /home/isucon/isucari/webapp/ruby/.bundle/ruby/2.6.0/gems/sinatra-2.0.7/lib/sinatra/base.rb:46:in `preferred_type'
    /home/isucon/isucari/webapp/ruby/.bundle/ruby/2.6.0/gems/sinatra-2.0.7/lib/sinatra/show_exceptions.rb:92:in `prefers_plain_text?'
    /home/isucon/isucari/webapp/ruby/.bundle/ruby/2.6.0/gems/sinatra-2.0.7/lib/sinatra/show_exceptions.rb:26:in `rescue in call'
    /home/isucon/isucari/webapp/ruby/.bundle/ruby/2.6.0/gems/sinatra-2.0.7/lib/sinatra/show_exceptions.rb:21:in `call'
    /home/isucon/isucari/webapp/ruby/.bundle/ruby/2.6.0/gems/newrelic_rpm-6.12.0.367/lib/new_relic/agent/instrumentation/middleware_tracing.rb:101:in `call'
    /home/isucon/isucari/webapp/ruby/.bundle/ruby/2.6.0/gems/sinatra-2.0.7/lib/sinatra/base.rb:194:in `call'
    /home/isucon/isucari/webapp/ruby/.bundle/ruby/2.6.0/gems/newrelic_rpm-6.12.0.367/lib/new_relic/agent/instrumentation/middleware_tracing.rb:101:in `call'
    /home/isucon/isucari/webapp/ruby/.bundle/ruby/2.6.0/gems/sinatra-2.0.7/lib/sinatra/base.rb:1950:in `call'
    /home/isucon/isucari/webapp/ruby/.bundle/ruby/2.6.0/gems/sinatra-2.0.7/lib/sinatra/base.rb:1502:in `block in call'
    /home/isucon/isucari/webapp/ruby/.bundle/ruby/2.6.0/gems/sinatra-2.0.7/lib/sinatra/base.rb:1729:in `synchronize'
    /home/isucon/isucari/webapp/ruby/.bundle/ruby/2.6.0/gems/sinatra-2.0.7/lib/sinatra/base.rb:1502:in `call'
    /home/isucon/isucari/webapp/ruby/.bundle/ruby/2.6.0/gems/newrelic_rpm-6.12.0.367/lib/new_relic/agent/instrumentation/middleware_tracing.rb:101:in `call'
    /home/isucon/isucari/webapp/ruby/.bundle/ruby/2.6.0/gems/rack-2.0.7/lib/rack/tempfile_reaper.rb:15:in `call'
    /home/isucon/isucari/webapp/ruby/.bundle/ruby/2.6.0/gems/rack-2.0.7/lib/rack/lint.rb:49:in `_call'
    /home/isucon/isucari/webapp/ruby/.bundle/ruby/2.6.0/gems/rack-2.0.7/lib/rack/lint.rb:37:in `call'
    /home/isucon/isucari/webapp/ruby/.bundle/ruby/2.6.0/gems/rack-2.0.7/lib/rack/show_exceptions.rb:23:in `call'
    /home/isucon/isucari/webapp/ruby/.bundle/ruby/2.6.0/gems/rack-2.0.7/lib/rack/common_logger.rb:33:in `call'
    /home/isucon/isucari/webapp/ruby/.bundle/ruby/2.6.0/gems/sinatra-2.0.7/lib/sinatra/base.rb:231:in `call'
    /home/isucon/isucari/webapp/ruby/.bundle/ruby/2.6.0/gems/rack-2.0.7/lib/rack/chunked.rb:54:in `call'
    /home/isucon/isucari/webapp/ruby/.bundle/ruby/2.6.0/gems/rack-2.0.7/lib/rack/content_length.rb:15:in `call'
    /home/isucon/isucari/webapp/ruby/.bundle/ruby/2.6.0/gems/puma-4.1.0/lib/puma/configuration.rb:228:in `call'
    /home/isucon/isucari/webapp/ruby/.bundle/ruby/2.6.0/gems/puma-4.1.0/lib/puma/server.rb:664:in `handle_request'
    /home/isucon/isucari/webapp/ruby/.bundle/ruby/2.6.0/gems/puma-4.1.0/lib/puma/server.rb:467:in `process_client'
    /home/isucon/isucari/webapp/ruby/.bundle/ruby/2.6.0/gems/puma-4.1.0/lib/puma/server.rb:328:in `block in run'
    /home/isucon/isucari/webapp/ruby/.bundle/ruby/2.6.0/gems/puma-4.1.0/lib/puma/thread_pool.rb:135:in `block in spawn_thread'"

RuntimeError: can't add a new key into hash during iteration

名前の通り、Hashをイテレーションで処理している破壊的な変更として新しいkeyを挿入しようとすると起きるRuntimeErrorとのこと。わかりやすいエラーメッセージだ!!

で、Sinatraの該当バージョンの該当コードを見るのですがイテレーションなんてしてないんですよねぇ…。

ググってみたところマルチスレッド環境下だと、別スレッドでイテレーションしてる最中に上記の破壊的なコードが実行されたりすると起きる雰囲気がある。

can't add a new key into hash during iterationが謎の状況で発生しているように見えるときは - 猫型の蓄音機は 1 分間に 45 回にゃあと鳴く

エラーが起きるエンドポイントに単発でリクエストを送ってもRuntimeErrorが再現しないのと、そもそもログを見ると色んなエンドポイントで起きているので単一のエンドポイントの問題でなくRack middlewareとかそのへんの問題な気がしてくる。

WEBrickに切り替える

マルチスレッドの問題なら非マルチスレッドのWeb server試すか〜と思って起動コマンドを変更してみます。

systemctlに登録してるserviceのコマンドを書き換えてWEBrickにしてみる。知らなかったけど、rackup -sオプションで何も指定しないとrack gem内の実装で書かれている優先度順に試行されるのだった(puma > falcon > webrick)。

# /etc/systemd/system/isucari.ruby.service
[Unit]
Description = isucon9 qualifier main application in ruby

[Service]
WorkingDirectory=/home/isucon/isucari/webapp/ruby
EnvironmentFile=/home/isucon/env.sh

-ExecStart = /home/isucon/local/ruby/bin/bundle exec rackup -p 8000
+ExecStart = /home/isucon/local/ruby/bin/bundle exec rackup -s WEBrick -p 8000

コマンドいじったのでdaemon-reloadしてからrestart、からのベンチマーカ実行すると…

$ sudo systemctl daemon-reload
$ sudo systemctl restart  isucari.ruby.service
$ bin/benchmarker
{"pass":true,"score":1410,"campaign":0,"language":"go","messages":[]}

pass!!

ヨッシャ、New Relicのwebで各種メトリクスや統計が見られるようになった!!

f:id:ohbarye:20200722020636p:plain
顧客が本当に欲しかったもの

余談: 初期化処理の#POST initializeまで記録されてて邪魔なので newrelic_ignore '/initialize' を書いておくと見やすくなるかも。

余談2: New Relicの力をフルに使うとTransaction traceなどの機能でクエリの内容やかかった時間が見られるようになりますが、 ISUCON9予選のRuby実装そのままではDatabaseへのクエリでかかっている時間は解析できません。クエリを実行するのにActiveRecordなどではなくMysql2::Clientを直接使っているためです。(New Relic agentがフックを仕込んでいない)

(追記2020-07-30) New RelicのセミナーでMySQL2::Clientを拡張してクエリを計測する方法の説明がありました。コードはこれ


とはいえpuma使えないって…そんなことあるか…?

ちなみにwebrickに切り替えたらベンチマークの結果も早速下がってます。

bundle exec rackup -> bundle exec puma

mtsmfmさんから「本当にマルチスレッドの問題なのか、threads数変えて試してみては?」との本質的な指摘をもらったので試してみる。

pumaのconfig file書くよりコマンドを書き換えたほうが早いのでまたisucari.ruby.serviceをいじる。

そういえばpumaコマンドじゃなくてrackupだったな…まずはpumaコマンドでちゃんと動くか見てみるか〜という感じでthreads数を変えずにコマンドだけ変更。

# /etc/systemd/system/isucari.ruby.service
-ExecStart = /home/isucon/local/ruby/bin/bundle exec rackup -s WEBrick -p 8000
+ExecStart = /home/isucon/local/ruby/bin/bundle exec puma -b tcp://0.0.0.0:8000

で、これで再起動したあとにベンチマーカーを試しに実行すると…なんとpassしました。

ええ… rackuppumaコマンド変えただけでSinatraの内部でRuntimeError起きるようになる、そんなことあるか…?

rackupとpumaの違い

挙動が違うからにはまぁ、何か違うのだろうということでrack/rack中心にコードリーディングしていく。

server (Puma::Serverインスタンス) をrunするところwrapped_appってなんや、何で包んどるんや。

server.run(wrapped_app, **options, &block)

その正体はこちらappmiddleware包み。

      def build_app(app)
        middleware[options[:environment]].reverse_each do |middleware|
          middleware = middleware.call(self) if middleware.respond_to?(:call)
          next unless middleware
          klass, *args = middleware
          app = klass.new(app, *args)
        end
        app
      end

      def wrapped_app
        @wrapped_app ||= build_app app
      end

結局素材はなんなのか、がわかるのがこちら

      def default_middleware_by_environment
        m = Hash.new {|h, k| h[k] = []}
        m["deployment"] = [
          [Rack::ContentLength],
          logging_middleware,
          [Rack::TempfileReaper]
        ]
        m["development"] = [
          [Rack::ContentLength],
          logging_middleware,
          [Rack::ShowExceptions],
          [Rack::Lint],
          [Rack::TempfileReaper]
        ]

        m
      end

      def middleware
        default_middleware_by_environment
      end

あ〜ここで完全に一部を理解しました(?)。

RACK_ENVもしくはrackup -E optionの値がdeploymentdevelopmentの場合 (デフォルトはdevelopment)、rackupコマンドで起動されるappにはいくつかのRack middlewareが足された状態になる、と。

上述のスタックトレースをよく見ると確かにこのmiddlewareを介して呼ばれてます。

rackup -E foo

じゃあこのmiddlewareが足されない状態なら大丈夫なのでは?ということで環境をfooにしてrackupします。deploymentdevelopment以外ならなんでも良いはず。

# /etc/systemd/system/isucari.ruby.service
-ExecStart = /home/isucon/local/ruby/bin/bundle exec puma -b tcp://0.0.0.0:8000
+ExecStart = /home/isucon/local/ruby/bin/bundle exec rackup -E foo -p 8000

これで再起動したあとにベンチマーカーを試しに実行すると…予想通りpassしました。

ウォーッ、rackupdevelopment環境で足してくるRack middlewaresと、マルチスレッドと、newrelic_rpmの組み合わせで再現する問題だということがわかった!!

わかったところで力尽きたのでこれをNew Relic forumかnewrelic_rpmにレポートして手を引きます。

余談: app_nameは環境に応じて変わる

newrelic.ymlの設定次第では環境 (= RACK_ENVまたは-E optionで指定する値) によってapp_nameが変わります。特定のapp_nameのpageだけをウォッチしていて「あれ〜データ来ないな〜」とならないよう注意。

デフォルトの設定だとこんな感じでした。

  • My Application (Development) => -E optionなし = デフォルトのdevelopment
  • My Application => development, staging 以外

2つのアプリケーションが一覧に出ているので見たいほうを見ましょう。

gyazo.com

まとめ

  • New Relicは便利
  • ISUCON9の過去問の構成のとき、Rubyの実装にNew Relicの設定を入れるだけだとベンチマークテストが失敗する
  • 以下のいずれかで、特定のRack middlewareとnewrelic_rpmの組み合わせを回避すれば成功する
    • WEBrickを使う (bundle exec rackup -s WEBrick)
    • environmentdevelopment以外にする (bundle exec rackup -E production etc.)
    • pumaコマンドで起動する (bundle exec puma)

まぁ、本番だったら時間も限られているのでこんな風にコードリーディングなんてせずに見つかったワークアラウンドのどれかで回避しているだろうとは思いますが、本番で予期しないことが起きると焦って一日が一瞬で終わるので利用したいツールの素振りを予めしておくのは大事ですね。

また、New Relicは特に設定せずとも突っ込むだけでアプリケーションのモニタリングがめちゃ捗りますが、デフォルトのアプリケーション実装だとスロークエリ分析までは届かなかったり、システム全体のボトルネック特定は難しいかもしれません(特定エンドポイントの外部APIリクエストが重い、とかはわかる)。topコマンドやMySQL slow query logなど古馴染みのツールとうまく併用して最大限役立てたいところです。

詳解 システム・パフォーマンス

詳解 システム・パフォーマンス

  • 作者:Brendan Gregg
  • 発売日: 2017/02/22
  • メディア: 単行本(ソフトカバー)

ブロックチェーンとDapp開発の基礎を学んだ

ブロックチェーンについては、2017年末の仮想通貨バブル時代にちょっと興味が湧いたものの投機に熱中していて技術的な理解は全然していなかった。

求職活動にて見知ったことなのだが、ここ数年でブロックチェーン技術を活用するスタートアップが台頭してきただけでなく、金融・不動産・保険業などの大手企業が実証実験に乗り出している。直近で業務として触るところまでいかなくても、将来的に利活用する製品やサービスに触れる機会があろう、ということで再訪してみた。

期間は2週間ほどで集中的に、おおよそ50時間ぐらいかけて基礎の基礎を学んだ。

学んだ成果

技術の根幹となる用語や概念や取り巻く環境を少しは理解することができた。

結果として、関連するニュースを読んで「わかる!」というポイントが見つかったり、「これはXXを理解しないとわからないのだろう」と、何がわからないかに気付ける機会ができてきた。

たとえば直近、Twitterアカウントの大規模な乗っ取りがあり、Bitcoinトランザクションを通じてメッセージを発したというニュースがあった。

Twitter不正投稿の首謀者、ビットコインブロックチェーンで「メッセージ」を発信

この記事を読み、

  • メッセージはどうやって埋め込んだのか?*1
  • この送金取引の結果はBitcoinブロックチェーン上にはどのように格納されているのか?*2
  • 不当に得たBitcoinを現金化するのは難しいと考えられるがそれはなぜか?*3

等々。こうした疑問に答えられたり、新たな疑問が湧いてくるようになったりした。


また、スマートコントラクトについて、ちょっとしたDappを書いてローカルのEthereumネットワークにデプロイしたり、Webブラウザで動作するフロントエンドのアプリケーションの実装をしてみた。

production useには程遠いまでも、「スマートコントラクトってよく聞くけど何?」というところから簡易な設計と実装を経て、スマートコントラクトとDappについて手触り感を伴うイメージを持つことができた。

やったこと

本当に何もわからなかったのでまずは体系的な理解から入ることにした。

本を読んで概観を掴む

まずは本2冊読んでみた。いずれも別記事にて感想を書いた。

おすすめはブロックチェーンのしくみと開発がこれ1冊でしっかりわかる教科書』感想

『スマートコントラクト本格入門―FinTechとブロックチェーンが作り出す近未来がわかる』感想

未読だが、評判を見たところ更に一歩先に理解を深めたければ『Mastering Bitcoin』や『Mastering Ethereum』あたりが良さそうだった。

ゲーム感覚でSolidityを学ぶ

実際にスマートコントラクトを自分で書いてみるため、専用のプログラミング言語であるSolidityを学ぶことにした。

CryptZombies*4なるチュートリアルがあるので"Beginner to Intermediate Smart Contracts"コースを全部やってみた。

cryptozombies.io

オンラインエディタでコードを編集しつつ、ステップバイステップで進められる。Solidity文法の基礎、スマートコントラクトにおける基礎概念がDappのコード上でどのように表現されるのか、WebブラウザからどのようにDappと連携するのかを学ぶことができた。

f:id:ohbarye:20200719000211p:plain
奇妙なゾンビ軍団を従えることができる

最終的にどういうものができるのかイメージがあまり湧かないまま進んでいく点と、(日本語版だと)Solidity versionがやや古いのが難点と感じたが、基礎をおさえる意味合いではさほど関係なさそうだった。

Solidityは静的型付け言語でJavaScript"風"のため書き始めるハードルはさほど高くなかった。とはいえ、作法やプラクティスみたいなところで悩むこともあったのだが公式ドキュメントEthereum Stack Exchangeである程度解消できた。

余談: 仮想マシン、EVM

Solidityで書かれたスマートコントラクトはEVMと呼ばれる仮想マシン上で実行される。EVMはEthereum上でスマートコントラクトを実行するためのEthereum独自の仮想マシンであり、Solidityのような高級言語コンパイル時にEVMバイトコードになる。

つまり、JVMや.NET等の先行するVMが知らしめたように、EVMバイトコードコンパイルさえできればどんな言語でスマートコントラクトを実装しても構わないということだ!

実際にSolidity以外にもEVMで動作するプログラミング言語はEthereum Wiki (https://eth.wiki/en/concepts/evm/ethereum-virtual-machine-(evm)-awesome-list) をチラッと見ただけでも7つあった。Solidityの次に有力なのはPython"風"の文法を持つVyperだろうか。

また、言語だけでなくEVM自体の実装も複数存在する。いくつかはすでに開発が停止しているようだが…。

この辺の動向を見守るのも面白そうだ。

Dappを書いてみる

ローカルでEthereumネットワークを立ち上げ、このネットワーク上にDaapをデプロイしてみた。

『スマートコントラクト本格入門―FinTechとブロックチェーンが作り出す近未来がわかる』にてgethを使ったネットワークの操作を学んだが、Ganacheを使うことでとても快適になった。

Web開発的にいえばテストデータ(seed data)が予め突っ込まれているデータベースがシュッと立ち上がり、実行されたUPDATE/INSERT(トランザクション)が全部記録として残り、さらにそれらをGUIで確認できる、みたいな感じ。

f:id:ohbarye:20200718235021p:plain
Ganache公式サイトより

CLIで操作できるものもあり、Docker imageも用意されている。慣れてきてGUI不要だったりDocker Composeでまとめて立ち上げたいならCLIを使えば良い。

また、Dapp開発のフレームワークとしてはTruffleを使ってみた。CLIでプロジェクトを立ち上げる(≒rails new)ことができ、デプロイやマイグレーション(≒rails migrate)、テストの実行を行うことができる。テストはSolidityでも書けるし、JavaScriptchaiやmocha風に書くこともできる!

Web開発との差分

静的型付け言語でプログラミングできてテストも書けてデプロイパイプラインを作れるCLIツールもあって…なんだかWeb開発とさほど変わらないのか?と思ったがそんなことはなかった。

Web開発と似てる部分もあるがだいぶ違うので「あ〜わかるわかる」と理解ったふりをせずに謙虚な姿勢で学ぶ必要がある

差分はたくさんあるので、その中でも特に印象深かった点を2つだけ書いてみる。

コスト意識

Dappではストレージに書き込んだり、コントラクトをインスタンス化したりするのにコストがかかる。ここでいうコストとはCPU・ストレージといった計算資源を使うことだけでなく、その操作の多寡に応じて実際にEthereumを消費するということ(Gasと呼ばれる)。Web appでいえばPOSTやPATCHリクエストを送るのにお金がかかる、というイメージ。

コントラクトを設計・実装する際には冪等性やメソッドの可視性といったWeb API的な観点のみならず、このメソッドはどれだけ高価なのかを意識することになる。そのうえ、ストレージに保存されるデータ量でコストが変わるため変数の型もしっかり考える必要がある。(1 byteですむようなデータにuint256など使ったらレビューで指摘されるのだろう)

デプロイしたらロールバックできない

スマートコントラクトは一度デプロイするとEthereum上で自律的に動き続ける。この実行環境は開発者が管理するサーバではなくP2Pネットワークのマシンたちなので誤ってデプロイしてしまったものを停止したりロールバックしたりはできない

修正後のコントラクトをデプロイする場合も別インスタンスで別アドレスを持つコントラクトアカウントが生まれることになるので旧バージョンは残り続ける。Web API的に言うと/v1/my_contractendpointにまずいところがあり直しても/v2/my_contractがデプロイされ、v1は生き続ける、みたいな。こわい。

さらに、コントラクトのコードはネットワークの参加者には公開されるため、機密情報・秘匿情報を含んではいけない。一度含んでしまったらこれも取り消せない。

モバイルアプリ開発でも同様の不可逆性はあるが、そちらには強制アップデートの仕組みもあるので、それ以上に厳しい。

脆弱性について学ぶ

上述の通りロールバックがきかないため、スマートコントラクトプログラミングでは慎重に脆弱性を潰していく必要がある。

セキュリティの指針やベストプラクティスをまとめたEthereum Smart Contract Best Practicesを読むのもためになった。

とりわけ面白かったのはReentrancyとして知られる脆弱性

Reentrancy

コントラクトAのコードを実行する際に外部のコントラクトBのメソッドを呼び出せる。その外部のコントラクトBが呼び出し元のコントラクトAを呼び出すこともできる。*5

たとえばBankAccountコントラクトが持つwithdrawメソッド内で残高の更新(ストレージへの書き込み)を完了する前に外部への送金を行うと、その送金を受け取った側へ処理フローが移る。そこからさらにwithdrawを呼ばれると、ストレージに記録されている残高が減ることなく資金が送金され続ける*6

実際に過去にこの脆弱性を突いた事件があり、"Ethereum Smart Contract Security Best Practices"にも記されているので、興味があれば掘り下げてみると面白いと思う。(少し探せば脆弱性があるコードを修正した実際のcommitまでたどりつける)

フロントエンドを書いてみる

Ethereumと連携するフロントエンドアプリケーションも書いてみた。Webブラウザで動作するやつ。

ローカルまたはリモートのEthereumネットワークと通信するためのweb3.jsというライブラリがあり、これを用いる。*7*8

web3.jsはあくまで外部とのインタフェース、言うなればaxiosなんかのようなAPI clientとしてのモジュールに過ぎないので、それ以外の部分の実装はReactでもVue.jsでもjQueryでもなんでも良い(CryptZombiesはjQueryを用いて説明している)。

Promiseベースのインタフェースなので馴染み深い。

かなりweb frontendぽい感じに見えてきたので、ここからちょっと違う話をする。

MetaMask

web3.jsから直接任意のEthereumに繋ぐこともできるが、Chrome ExtensionであるMetaMaskを介してネットワークに接続したり、アカウント情報を取得するのが乙なようだ。

MetaMaskはweb3.jsとEthereumの間にいるプロキシのようなやつで、接続先のネットワークやアカウントを管理できる便利Extension。web3.jsで送金処理を実行する場合にも一度MetaMaskが立ち上がって取引の内容を確認される。

www.youtube.com

同期/非同期

ブロックチェーン上のデータを参照するだけのcall(GETリクエストに相当)によるコントラクトのメソッド呼び出しでは同期的に値を返すことに何の問題もないが、トランザクションを生成することになるリクエストでは注意が必要。

ブロックチェーンで保存されるデータの変更や書き込みリクエストは即時に処理されるわけではない。ネットワークの承認によって初めてブロックチェーンにデータが格納されるためだ。なのでコントラクト側で発行するイベントをクライアント側でsubscribeして結果を受け取ったあとのコールバックを書いたりできる。

Web APIでも要求を受け付けたことだけを一旦レスポンスとして返し、裏側で非同期に処理が行われ、結果を取得するにはポーリングなりWebsocketなりで対処したりすることはある。その対応がわりと頻繁に必要になりそうだが、もっとうまいやり方はあるかもしれない。

その他

上記の他にも色んなリソースも見ていたが、とりわけ読み応えがあるのはこの辺。

あとはVitalik ButerinによるEthereumのWhite Paper!読んでも正直全てを理解できていないのだけど、人間って19歳でこんなこと考えられるんだ…と感嘆せずにはいられない。*9

おわりに

ブロックチェーン技術とその周辺は日々新しい情報が生まれる領域であり、これらの学習だけでは実務レベルにはまったく不十分ということがわかってきた。Web開発でいえば「Rails tutorialやりました」以前のところにいる。

しかしながらこういった基礎的な部分を押さえておくことで関連ニュースの情報処理効率は少なからず上昇するし、また、将来なんらかの形でブロックチェーンを活用した製品に利害関係者・消費者として接する機会が来るのはだいぶ確定的に思えてきた。その時に必要なリテラシーを備える意味でも、こういった学習を時たま行っていくのは上々の投資効果があると考える。

*1:メッセージはBitcoinアドレスの一部として埋め込まれている。通常、ビットコインアドレスは公開鍵から生成されるが、秘密鍵なしでもvalidな値("1"と混同しないように"l"を除外するとか)であればアドレスとして利用することはできる(ただしビットコインアドレスから公開鍵と秘密鍵を知ることはできないので誰もそのアドレスから送金できない)。

*2:データベースのようにレコードのmutableな値をupdateするのではなく、https://en.bitcoin.it/wiki/Transaction に従って取引ごとのOutputが記録されるのみ。残高を見る時はUTXOという仕組みで、Outputから未使用のものを集計している

*3:Bitcoin Tumblerと呼ばれる、不当に得たBitcoinとcleanなBitcoinを混ぜ合わせて洗浄するマネーロンダリングの手法があるが、全ての取引記録が改ざん不能な形で記録されている以上は追跡から逃れるのは難しい。過去に同様の手口で資金洗浄を試みたが失敗した例がある

*4:Ethereum上で稼働するゲームに、仮想子猫を売買できるCryptKittiesというものがあり、それをパロったやつ。

*5:あるAPIから外部のAPIを呼び出すというのはWeb appでもよくあることだが、Dappでは送金処理の際に外部のコントラクトのメソッドがコールバックとして呼び出されるのでけっこうカジュアルかつ頻繁に発生するようだ。

*6:コールスタックに上限があるので循環して無限ループが起きることはない

*7:ether.jsというのもあるが触っていない

*8:iOSAndroid用のintegrationもあるようだ

*9:年齢で人を評価するとかそういう次元でなく19歳の自分と比べて絶望する感覚

『スマートコントラクト本格入門―FinTechとブロックチェーンが作り出す近未来がわかる』読んだ

タイトル通りスマートコントラクトがメインなのだが1~2章はFintech業界やBitcoinの話題で、これは『ブロックチェーンのしくみと開発がこれ1冊でしっかりわかる教科書』と重複する部分もあったのでさらっと読み飛ばした。

個人的に面白かったのは3~5章で紹介される社会実装の実例。一般にブロックチェーンというとFintechばかりが想起される気がするが、生活・産業分野、Legaltech領域においても適用可能性が大いにあることと、そこでもたらされるのが既存業務の効率化だけでなくバリューチェーンの変革(中間業者を廃するなど)でもあることが示されていて、とても心が踊った。

数年後はどうかわからないが10年後にはこの辺の製品やサービスに触れる機会がありそう、そんな近未来感があった。


6~7章のスマートコントラクト開発の部分はやや古いのかも、と感じた。

geth (go-ethereum) を使ってローカルにEthereumネットワークを立てたりインタラクティブに操作するのは初手としては面白かったものの、truffleganacheを利用した場合に比べると開発体験がかなり劣るのでちょっとつらかった。

truffleは最後に少しだけ紹介されているが、 公式のチュートリアルのほうが手厚いうえに情報も新しいのでこちらを頼ったほうが良さそうだ。

www.trufflesuite.com

『ブロックチェーンのしくみと開発がこれ1冊でしっかりわかる教科書』読んだ

各トピックが数ページ単位でまとまっており、かつ、言葉だけではイメージしづらい技術的な解説についての図解がかなり多いのでとても読みやすい。

技術的な解説がどれだけ深いのかは読んでいる時は正直わからなかったが、前段で説明した内容に対する補足や解説が次節で語られるような順序になっており、読み進めるごとに理解が深まる実感を持てる構成であった。

また、公開鍵暗号や分散システムにおけるビザンチン将軍問題などWebシステムでも取り上げられる話題には馴染みがあった。(当然ながら)全く異界の技術でなくコンピュータとネットワークの上に構築される技術だというのもわかり、読書を通じてブロックチェーンへより強い興味が持てるようになった。

「AWSによるクラウド入門」をやった

1週間ぐらい前にバズっていた東京大学計数工学科の講義資料が大変面白そうだったので全てのハンズオンをやってみた。

tomomano.gitlab.io

Webアプリケーション開発の実務経験があれば多少読み飛ばせる部分はあるのでだいたい半日程度、実行するコードを読み込んだとしても1日で完了できるボリューム。ハンズオンはやや腰が重いかもしれないが、細かく分けて実践することも可能なので興味を持った部分だけでも挑戦することをおすすめする。

実践的な内容

「1.1. 本講義の目的・内容」より。

本講義(計3回)の目的は,クラウドの初心者を対象とし,クラウドの基礎的な知識・概念を解説する. また, Amazon Web Service (AWS) の提供するクラウド環境を実例として,具体的なクラウドの利用方法をハンズオンを通して学ぶ.

特に,科学・エンジニアリングの学生を対象として,研究などの目的でクラウドを利用するための実践的な手順を紹介する. 知識・理論の説明は最小限に留め,実践を行う中で必要な概念の解説を行う予定である. 受講生が今後,研究などでクラウドを利用する際の,足がかりとなることができればこの講義の目的は十分達成されたことになる.

ざっくり言うと3回の講義で以下の実践が詰める。また、実践に必要となる前提知識も簡潔かつ必要十分でわかりやすい解説で学ぶことができる。

  1. CDKでVPN内にEC2インスタンスを起動してSSH
  2. GPUを搭載したEC2で機械学習、ECS+Fargateで分散並列処理
  3. Lambda, DynamoDBに代表されるサーバーレス技術を用いてスケーラブルなWebサービスを構築

業務でいろいろ触ったことがある人からすると「まぁ、そういう風に使うわな」といった感じだと思うが、「クラウドコンピューティング未経験者にその利便性・可能性を感じさせる」という目的ではこの上ない内容だと思う。

また、基本的にはコマンドラインでポチポチやっていけば動くものが見えるハンズオンなので理解が及ばないところ(自分にとっては機械学習まわり)はスキップできるし、深堀りしたくなったらコードを読んで理解を深めることができるのも良い。

学んだことなど

AWSに業務で触れたことはあるがゼロから構築した経験がないWebアプリケーションエンジニアとしては、これまで意識してこなかった具体が透けて見えるようになる良いハンズオンだった。比較的新しい技術スタックで構成されているため、特に手詰まりなく全トピックを終えることができた。

ここ数年はWebアプリケーションのフロントエンドやバックエンドといってもアプリケーションのコード中心に開発してきて、クラウドインフラを自分にとってのブラックボックスのまま放置してきてしまった(優秀な同僚頼みであった)負い目があり、それに対して少しはツケを払う土台ができた…と思いたい。少なくとも会話ができるレベルにこのまま持っていけると思う。

ふわっとした理解の至らなさと講義のコンテンツの結びつきは具体的にはこんな感じ。


「AMIとかVPCとかSecurity Groupとか、役割は知っているけど具体的な設定をどうやって管理してるのかよくわかってないんだよな…」

=> AWS Cloud Development Kit (AWS CDK) で構成管理Infrastructure as Codeの恩恵を享受してみる


「EC2インスタンス、業務ではSREが良い感じに選んでくれているけど普段使っていないのにはどんなのがあるんだろう…」

=> GPUモリモリでディープラーニングの畳み込み演算が高速化できるAMIを試してみる


「Lambda function便利そうだが他サービスとのインテグレーションがよくわからん…」

=> API Gateway + Lambda + DynamoDB で一連の流れを書いてみる


「FargateがEC2とLambdaの中間に見えるが使いどころがよくわからん…」

=> ECRに登録したイメージをECS + Fargateで動かし、コンテナで動くアプリケーションのスケーラビリティを実感する


また、直接業務で関わったことはないが機械学習領域でのクラウド活用も知識として見聞きはしていたが具体的にどのように活用しているのかのイメージも湧いていなかったので、そのあたりも体感できた。

GPUとCPUの速度差、知っているつもりだけど体感したことはあまりない…」

=> GPU搭載のEC2で起動したJupyter Notebookにローカルからポートフォワーディングして接続して、CPUとGPUでの処理速度の差をベンチマークとって比較してみる


その他にも名前は聞いたことあるが実態がよくわかっていない3文字のアルファベットがだいぶ解き明かされてきた。

感想

クラウドコンピューティングの実用性と革新性は知識では知っているが実際に手を動かしてみないと得られない手触り感はあるなぁ…。そのへんを大事にしなくなったらエンジニアとしては厳しいものがあるので引き続きやっていきたい。

最後に、講義の作成・公開をしてくださった方に多大な感謝。そして、大学時代にこんな実践的な講義が受けられるなんて羨ましい!

最近買って良かった漫画(2020年4月〜6月)

2020年4月~6月あたりで買ってよかった漫画。 元々集めていて続刊が出ただけのやつは除いて、新しく見知った漫画に限定。

スペクトラルウィザード

全2巻。7月はこの漫画をとにかく推していました。

絵柄と退廃的な雰囲気から道満晴明的なナンセンス感を出してくるのかと思ったけど良い意味でけっこう違った。

スパイダーマンよろしく、"大いなる力には大いなる責任が伴う"を体現したような魔術師もいれば、主人公のスペクトラのように最強クラスの魔法を使えるのに楽しかった過去にしがみつき毎日を無気力に過ごす魔術師もいる。彼女らの生き様と葛藤、魔術師ギルドを滅ぼさんとする人間とわかりあえそうで至らないディスコミュニケーションがグッと来る。

単純な能力バトルでも面白いぐらいの設定なのに各々の魔術がメンタリティに紐付いていてストーリーにしっかり絡んでくるあたり、とても漫画が巧み。

スペクトラルウィザード

スペクトラルウィザード

同じ作者の別作品もあたってみたいところ。

忍者と極道

切られた瞬間の生首が喋りながら飛んでいくタイプの、カラッとした残虐さと台詞回しの最大瞬間風速がものすごい漫画。『衛府の七忍』や『特攻の拓』など様々な先達の影響を感じさせる。現在2巻、連載継続中。

許斐剛先生のような計算しないで飛んでる感じではなく、山口貴由先生のようにポップなラインを狙ってきているがちょっとズレているタイプであることが言葉でなく心でわかる。

エンブリヲ

90年代の虫系ホラー。全3巻。

少女漫画のように可愛らしく描かれた表紙とは対象的に、虫の造形や異質な存在を迫害する人間の陰湿さ・陰惨さがやたらと細部までリアルに描き込まれておりパニックホラーとしてかなりすごいところまで来ている。

伊藤潤二らのホラーより救いはあるほうだが生理的嫌悪感はこちらのほうが上かもしれない。

この漫画のおかげで「生きたまま虫に食われて死ぬ」が"こんな死に方は嫌だ"ランキングの2位まで浮上してきた。 (1位は未だにグリーン・インフェルノのように「生きたまま食人族に解体される」)

エンブリヲ 1

エンブリヲ 1

砲神エグザクソン

岡田斗司夫YouTubeでおすすめしていた園田健一のSF作品。1997~2004年の作品。全7巻。

SFとして良作であると同時に知性体による"侵略"というのがいかなるものなのかを非常に巧く捉えている。また、ロボットに搭乗する主人公といえば読者としてはかんたんに予見も期待もできる熱い殴り合いバトルの筆致が優れているだけでなく、侵略に対し心の折れた地球人に反逆の意思を植え付けるための世論形成など、繰り広げられている高度な政治戦・情報戦・心理戦にもかなり見応えがある。(リアルタイムでない動画は編集・改ざんされている可能性が疑われるからタイミングが大事などなど、凝った思考で戦略を描いている)

サイエンスフィクションを成立させるためのリアリティにはこだわりがあるが、豪放なジジイが美人にモテまくっていたりするあたりのご都合主義はご愛嬌と言うか、懐かしさすら覚える。

ブレット・ザ・ウィザード

連続して、岡田斗司夫YouTubeでおすすめしていた園田健一のファンタジー作品。2010~2013年の作品。全4巻。

魔法使いの戦いではなく「魔法が刻まれた魔法銃を持つ者同士の戦い」という一歩新しい感覚のファンタジーであり、戦略性の高いバトルが楽しめる。随所に見える園田健一の銃器や車へのこだわりとロリータ趣味も好きな人はプラスアルファで楽しめるだろう。

だいぶ打ち切り感のあるエンドなのだが全4巻の中で見どころは十分あるのでおすすめできる。

『みんなのコンピュータサイエンス』読んだ

コンピュータに対するコンパクトな知識地図」と銘打たれていたので、何か自分の脳内地図に抜け漏れがないかを知りたくて読んでみた。

離散数学、データ構造、アルゴリズム、データベース、コンピュータアーキテクチャプログラミング言語…等々の広大な分野について、駆け足で鍵となる概念を紹介する本。9章にて述べられている「優れたプログラマであれば知っているべきコンピュータサイエンスに関する最小限の知識」というのがこの本の本質であった。

個々を詳解しているわけではないので理解のためには個別の領域について掘り下げる必要がある。何より手を動かして実践していく必要がある。

良かった点

個人的には『CPUの創りかた』『コンピュータシステムの理論と実装』あたりで学んだコンピュータアーキテクチャが簡素で最適化手法を一切省いたものだったので、L1キャッシュなど現代で一般的な最適化をいくつか例示していた点は学びがあった。

また、紹介する概念が現実世界の応用でどのように顕れてくるかにフォーカスしている点も興味を削がない工夫として良かった。

物足りなかった点

逆に言うとそれ以外は自分にとって知識のおさらいという感じだった。

データ構造についていえば『みんなのデータ構造』、アルゴリズムについては競技プログラミングCourseraのアルゴリズム講座、コンピュータアーキテクチャについては『CPUの創りかた』『コンピュータシステムの理論と実装』、データベースやプログラミング言語については実務で学んできた内容でだいたいカバーできていた。

明らかにヤバい抜け穴がないと追認された心持ちではある…が、大きい抜けがあるとしたら離散数学か…。

どういう人向きか

今の自分にとって既知の内容が多かったとはいえ、コンピュータサイエンスを学び始める前に読んでおけば自分の現在地や無知を知るのに有用なのは間違いなかった。


読書メモ: みんなのコンピュータサイエンス - ohbarye