なんかI/Oが刺さってるぞ、という時に...

I/Oを確認する方法をメモっておく。

リアルタイム

sudo iotop

または

dstat --top-bio --top-io

あとから

sar -Bb -f /var/log/sa/sa28  -s '13:00:00' -e '16:00:00'

ATOMパッケージを管理する

複数のマシンを利用している時にインストールパッケージをどうやって同期しようかという話。

Star

atom.io でパッケージにスターをつけて管理するというやり方。

インストール済みパッケージにスター

apm login
apm star --installed

スターをつけたパッケージをインストール

apm login
apm stars --install

Git

インストール済みパッケージを記録

apm list --installed --bare > path_to_gitrepo/atom_packages

パッケージをインストール

apm install —package-files  path_to_gitrepo/atom_packages

config.cson

パッケージの話じゃないけど。

.atom/config.cson ->  path_to_gitrepo/config.cson

Mac OS X でpython3の環境構築

これまではpyenvを使ってました。

Mac OS X でのpyenvによるpython環境構築 - Tensorflow 編 - kotaroito's notes

Mac OS X でのpyenvによるpython環境構築 - kotaroito's notes

が、pyenvが必要かどうかフローチャート - QiitaPythonの仮想環境構築 2017.01版 - YAMAGUCHI::weblog を読んで、より標準的な方法を使うことにしました。エディタなどがサポートしてくれるので、エコシステムに乗っかる恩恵は大きい。

1. brew で python3 をインストール

brew install python3

2. venvで仮想環境をつくる

python3 -m venv path_to_my_dir

3. activateする

. path_to_my_dir/bin/activate

以上で終了。かんたん。

RubyではじめるgRPC

gRPCについては以前から興味があっていくつか記事を斜め読みしてましたが、結局自分で動かしてみないと分からないため、Rubyで動かしてみようと思います。

gRPCとは?

Googleの内部で使われていたRPCで、メッセージのシリアライズ方式としてProtocol Bufferを使う。HTTP/2ベース。 Client / Server の内部実装を問わないので、gRPC ServerをGoで書いて、Rubyから使うといったことが可能。

より詳しい説明は公式のWhat is gRPC?を見るべし。

Quick Start

gRPCがオフィシャルにRubyによるQuick Startを提供しているので、まずはここから始めました。 お約束のHello Worldです。

grpc.io

.proto ファイルにserviceを定義し、grpc_tools_ruby_protoc コマンドでRubyのコードをジェネレートするというgRPCのお作法が分かったので、次は自分で(しょうもない)マイクロサービスを作ってみることにしました。

マイクロサービスをつくる

ほぼHelloworldと大差ないですが、オウム返しをするParroterというサービスをつくってみることにしました。

.proto

リクエストとして文字列msgを受け取り、レスポンスはmsgとその回数countを返します。

syntax = "proto3";
package parroter;

service ParrotService {
      rpc say(ParrotRequest) returns (ParrotResponse) {}
}

message ParrotRequest {
      string msg = 1;
}

message ParrotResponse {
      string msg = 1;
      int32  count = 2;
}

.protoからRubyコードを出力します。

grpc_tools_ruby_protoc -Iproto --ruby_out=lib --grpc_out=lib proto/parroter.proto

とりあえず生成されたファイルをrequireすれば、gRPC Client / Server をつくれるのですが、できればインターフェースと実装の分離をしておきたい。

Building Microservices using gRPC on Ruby – Shiladitya Mandal – Software Developer に書かれているように、private な gem を生成するのがよさそうです。ディレクトリレイアウトは以下のようになりました。

$ tree .
.
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── bin
│   ├── console
│   └── setup
├── lib
│   ├── parroter
│   │   └── version.rb
│   ├── parroter.rb
│   ├── parroter_pb.rb
│   └── parroter_services_pb.rb
├── parroter.gemspec
└── proto
    └── parroter.proto

https://github.com/kotaroito/grpc-parroter-service

gRPC Server

作成した private な gem を使って、https://shiladitya-bits.github.io/Building-Microservices-from-scratch-using-gRPC-on-Ruby を参考にしながら、gRPC Serverを作成してみました。

Gemfile
source 'https://rubygems.org'

gem 'parroter',:git => "https://github.com/kotaroito/grpc-parroter-service",:branch => 'master'
gem 'grpc', '1.7.0.pre1' 
bin/start_server.rb
#!/usr/bin/env ruby

require 'grpc'
require 'parroter_services_pb'

class ParrotServer
  class << self
    def start
      start_grpc_server
    end

    private
    def start_grpc_server
      @server = GRPC::RpcServer.new
      @server.add_http2_port("0.0.0.0:50052", :this_port_is_insecure)
      @server.handle(ParrotService)
      @server.run_till_terminated
    end
  end
end

class ParrotService < Parroter::ParrotService::Service
  def initialize
    @count = {}
  end

  def say(parrot_req, _unused_call)
    p parrot_req
    Parroter::ParrotResponse.new(msg: parrot_req.msg, count: count_msg(parrot_req.msg))
  end

  private

  def count_msg(msg)
    @count[msg] = 0 unless @count[msg]
    @count[msg] += 1
  end
end

ParrotServer.start
bin/test_parrot_service
#!/usr/bin/env ruby
require 'grpc'
require 'parroter_services_pb'

def test_single_call
  stub = Parroter::ParrotService::Stub.new('0.0.0.0:50052', :this_channel_is_insecure)
  req = Parroter::ParrotRequest.new(msg: 'Hello gRPC.')
  resp_obj = stub.say(req)
  p resp_obj
end

test_single_call

gRPC Serverを起動後に、何度かClientを実行すると...

$ bundle exec bin/test_parrot_service
<Parroter::ParrotResponse: msg: "Hello gRPC.", count: 1>

$ bundle exec bin/test_parrot_service
<Parroter::ParrotResponse: msg: "Hello gRPC.", count: 2>

こうなります。

気になることをつらつらと

RubyでgRPC Serverを書けそうなことは分かったので、気になることをいくつか調べてみました。

2017年10月現在、(Rubyで書くなら)サーバーは"grpc" gemに同梱されているGRPC::RpcServer 一択だと思われます。 充実したドキュメントはなく、設定オプションを知りたければGithubのソースを追いかけるのがよさそう。

https://github.com/grpc/grpc/blob/master/src/ruby/lib/grpc/generic/rpc_server.rb#L205-L210 を読むと、スレッドのpool_size や poll_periodなどが設定できそうです。

Interceptor

Rack Middleware に相当するものは、gRPCだとInterceptorと呼ばれるようです。 grpc gemの1.6.7にはありませんでしたが、1.7.0.pre1 から実装されていました。 ただし、EXPERIMENTAL API とのこと。

https://github.com/grpc/grpc/blob/master/src/ruby/lib/grpc/generic/rpc_server.rb#L200-L203

すごく簡素ですが、Interceptorを使ってみました。

class ParrotServer
  class << self
    def start
      start_grpc_server
    end

    private
    def start_grpc_server
      @server = GRPC::RpcServer.new(interceptors:[HelloInterceptor.new])
      @server.add_http2_port("0.0.0.0:50052", :this_port_is_insecure)
      @server.handle(ParrotService)
      @server.run_till_terminated
    end
  end
end

class ParrotService < Parroter::ParrotService::Service
   ...snip...
end

class HelloInterceptor < ::GRPC::ServerInterceptor
  def request_response(request:, call:, method:)
    p "Received request/response call at method #{method}" \
      " with request #{request} for call #{call}"
    call.output_metadata[:interc] = 'from_request_response'
    p "[GRPC::Ok] (#{method.owner.name}.#{method.name})"
    yield
  end
end

ParrotServer.start

感想

gRPCはインターフェースを明確に宣言でき、しかもカンタンなのが良い。 JSON Schemaの経験があるだけに余計に。。。

現時点では詳しいドキュメントないので、ソースコードを読みさえすれば、Rubyでもプロダクションで使えるgRPC Serverを書けそうな気がしてる。

参考資料

Mac OSにPrestoをインストールして試してみる

BigQuery、Athenaに続いてこんどはPrestoを触ってみたので、記録を残しておきます。

Prestoとは?

tug.red

インストールと設定

2.1. Deploying Presto — Presto 0.185 Documentation には tar ball と書かれていますが、brewでインストールが可能です。java >= 1.8 が必須。

brew install presto

これで、version 0.185(2017年10月現在)がインストールされます。 設定はほぼデフォルトですが、jvm.configでオプションを1つ無効化した記憶。

/usr/local/Cellar/presto/0.185/libexec/etc/jvm.config
-server
-Xmx16G
-XX:+UseG1GC
-XX:G1HeapRegionSize=32M
-XX:+UseGCOverheadLimit
-XX:+ExplicitGCInvokesConcurrent
-XX:+HeapDumpOnOutOfMemoryError

起動は

presto-server run

で行います。

MySQLに接続してみる

Connectorさえあれば(つくれば)、どんなデータソースでも直接SQLでクエリを書けるというのがPrestoのウリの一つ。 (自分にとっては)最も手軽なMySQLを試してみます。

/usr/local/Cellar/presto/0.185/libexec/etc/catalog/mysql.properties
connector.name=mysql
connection-url=jdbc:mysql://localhost:3306
connection-user=root
connection-password=

これで準備はできたので、CLIをインストールして、prestoに接続します。

2.2. Command Line Interface — Presto 0.185 Documentation

./presto --server localhost:8080 --catalog mysql

以下、実際にworldデータベース に対して、クエリを打ってみた結果です。

presto:world> use world;
presto:world> SHOW TABLES;
      Table
-----------------
 city
 country
 countrylanguage
(3 rows)

Query 20171013_134538_00010_ra2dz, FINISHED, 1 node
Splits: 18 total, 18 done (100.00%)
0:00 [3 rows, 71B] [11 rows/s, 271B/s]

presto:world> SELECT * FROM city LIMIT 10;
 id |                name                 | countrycode |       district       | population
----+-------------------------------------+-------------+----------------------+------------
  1 | Kabul                               | AFG         | Kabol                |    1780000
  2 | Qandahar                            | AFG         | Qandahar             |     237500
  3 | Herat                               | AFG         | Herat                |     186800
  4 | Mazar-e-Sharif                      | AFG         | Balkh                |     127800
  5 | Amsterdam                           | NLD         | Noord-Holland        |     731200
  6 | Rotterdam                           | NLD         | Zuid-Holland         |     593321
  7 | Haag                                | NLD         | Zuid-Holland         |     440900
  8 | Utrecht                             | NLD         | Utrecht              |     234323
  9 | Eindhoven                           | NLD         | Noord-Brabant        |     201843
 10 | Tilburg                             | NLD         | Noord-Brabant        |     193238
(10 rows)

Query 20171013_134553_00011_ra2dz, FINISHED, 1 node
Splits: 18 total, 18 done (100.00%)
0:01 [4.08K rows, 0B] [5.3K rows/s, 0B/s]

思ったこ

PrestoでMySQLにクエリを投げるのは比較的カンタンだった。

5.4. Hive Connector — Presto 0.185 Documentation もあり、Amazon S3にあるログに対してクエリを実行できるらしい。(試そうと思ったが、HadoopやらHiveやら事前準備が多く断念)

実際にプロダクションで使う場合には、セットアップ・運用が大変そうなので、Amazon EMRなどのマネージドサービスを使うのがよさそうです。

Amazon Athenaについて

なんとなく触ってみたレベルなので、特徴やユースケースをかんたんに整理しておきたい。

特徴は?

  • Amazon S3のデータを標準SQLで分析できる
  • サーバーのセットアップ・管理は不要
  • スキーマ定義は必要だが、事前のデータロードは不要
  • SQLのエンジンは presto

よくある質問 - Amazon Athena | AWS にいろいろ書いてあります。

ユースケースは?

AWSソリューションアーキテクトの方のスライドがわかりやすいです。

f:id:kotaroito2002:20171006091349p:plain

出典: Presto ベースのマネージドサービス Amazon Athena

SQLエンジンとして採用しているprestoは多様なデータソースに対応していますが、AthenaはS3のみを対象にしている(だよね?)ので、参照頻度は低いがいつか使うかもしれないログをS3に保存しておき、そのタイミングが来たらAthenaでさくっと分析するっていう使い方になるのかなと。

BigQueryの何がすごいのか?

既に語り尽くされてそうな話ではありますが、自分なりにまとめないとアタマに入らないのでドキュメントの整理も兼ねて、ポイントをまとめてみます。

BigQueryとは何か?

Google Cloud Platformでは、"BigQuery is Google's fully managed, petabyte scale, low cost analytics data warehouse." と紹介されており、主要なユースケースとして、大規模データに対するアドホックで試行錯誤を要するインタラクティブなクエリが想定されています。

Technical White Paper: An Inside Look at Google BigQueryに書かれているケースがより具体的です。

Can you imagine how Google handles this kind of Big Data during daily operations? Just to give you an idea, consider the following scenarios:

  • What if a director suddenly asks, “Hey, can you give me yesterday’s number of impressions for AdWords display ads – but only in the Tokyo region?”.
  • Or, “Can you quickly draw a graph of AdWords traffic trends for this particular region and for this specific time interval in a day?”

普通のウェブサービスならいざ知らず、Googleの規模でこれを実現するのはなかなか大変です。例えば、Google Searchは世界中のウェブサイトを扱っているわけで。。

で、Googleの内部で開発されたのが、Dremel という技術で、これを外部に公開したのがBigQueryです。

BigQueryの何がすごいのか?

一言で言うなら、1000億行クラスのデータセットに対して、何の事前準備もなくフルスキャンしても数十秒のオーダーで結果が返ってくること。

この事実だけを聞いても最初はピンと来なかったんですが、 Anatomy of a BigQuery Query | Google Cloud Big Data and Machine Learning Blog  |  Google Cloud Platformを読むと次元の違うことをやっていることがよくわかります。

詳しくは記事参照ですが、100 billion rows のテーブルに対して正規表現によるマッチを行い、30秒以内に結果を返すということは少なくとも

  • About 330 100MB/sec dedicated hard-drives to read 1TB of data
  • A 330 Gigabit network to shuffle the 1.25 TB of data
  • 3,300 cores to uncompress 1TB of data and process 100 billion regular expressions at 1 μsec per

というリソースを同時稼働させてるということです。

この規模のリソースを事前準備なしにボタンを押すだけで扱えるとは。。。BigQuery恐るべし。。

BigQueryの中を覗く

じゃあ、一体どうやって実現しているんだろうかという疑問が湧きますが、

Technical White Paper: An Inside Look at Google BigQueryDremel: Interactive Analysis of Web-Scale Datasetsを読むと、BigQueryの内側を垣間見ることができます。White Paperによると、前例のないレベルのパフォーマンスは2つのコア技術により支えられているとのこと。

1. Columnar Storage

f:id:kotaroito2002:20171005083340p:plain 出典: Dremel: Interactive Analysis of Web-Scale Datasets

BigQueryはColumnar Storageを採用しており、アイデアそのものはデータウェアハウス向けのDBではよくある設計。 BigQuery(あるいはDremel)を特徴づけるのは、数千台のサーバーのコンピューティングパワーを活用し、クラウドサービスとして提供していること。

2. Tree Architecture

数千台のマシンでクエリを分散実行し、最終的に秒オーダーで結果を集約することが設計におけるチャレンジで、DremelではTree Architectureを採用しているらしいです。

f:id:kotaroito2002:20171005085043p:plain

出典: BigQuery under the hood | Google Cloud Big Data and Machine Learning Blog  |  Google Cloud Platform

Treeのリーフは「スロット」と呼ばれ、データの読み出しと計算を担当しており、Treeの枝は「ミキサー」と呼ばれ、集約を担当しています。

ネストされたデータ・繰り返しデータを扱える

Loading Data  |  BigQuery  |  Google Cloud PlatformQuerying Nested and Repeated Fields in Legacy SQL  |  BigQuery  |  Google Cloud Platform にて紹介されていますが、BigQueryはネストや繰り返しのあるJSONを扱うことができます。

f:id:kotaroito2002:20171005232751p:plain

まとめというか感想

ドキュメントを読んでまとめただけですが、BigQueryの中が少し分かった気になりました。 ともかく凄いの一言に尽きます。

References