N+1問題
Noahです。
ペアプロ等で指摘されたことで、重要だと思ったら1つずつメモを書こうと思い、書く。
続…かないかもしれない
今日はN+1問題について(ここのコードは深夜に書いたメモ程度でなんと言うか、補助的役割である👀)
N+1の問題
まず、1つのクエリで複数のidを取得し、その後繰り返しのクエリで、idのスライスのそれぞれをDBから抜いてくること。 idの数をNとすると、合計N+1のクエリが発生する。
// func Query() (Data, error){ // 内部では、1つの構造体Dataのものを返すようになっている // } // func Query2ByID(id string) (User, error){ // 内部では、idから紐付けられる1つの構造体Userを返すようになっている(Userモデル内でid はUNIQUEとする) // } func f() { // あるデータをDBから抜いてくる // 中に複数のidがあるような構造体 // (e.g.) // type Data struct { // Subs []Sub `db:"-"` // } // type Sub struct { // ID string `db:"user_id"` // } // type User struct { // ID string `db:"id"` // Name string `db:"name"` // } //構造体Dataを一件DBから取得するクエリ ......(1) data, err := Query() if err != nil { log.Fatal(err) } // idsをもとにDBからユーザ情報を抜く for i, sub := range data.Subs { //構造体Userを、Sub.IDから紐付けてDBから抜いてくるクエリ ......(2) user, err := Query2ByID(sub.ID) if err != nil { log.Fatal(err) } // なんか処理する } }
愚直にこんな感じで書いてしまうと、(1), (2) で、合計N+1のクエリを実行してしまうため、パフォーマンスが非常に悪い。
良い方法として、mapを利用した解決策がある
解決策
// func Query() (Data, error){ // 内部では、1つの構造体Dataのものを返すようになっている // } // func Query2ByIDs(id []string) ([]User, error){ // 内部では、idから紐付けられる構造体Userを返すようになっている(Userモデル内でid はUNIQUEとする) // 先程との変更点は、複数のidを受け取り、複数の結果を返すようになっている // } func g() { // あるデータをDBから抜いてくる // 中に複数のidがあるような構造体 // (e.g.) // type Data struct { // Subs []Sub `db:"-"` // } // type Sub struct { // ID string `db:"user_id"` // } // type User struct { // ID string `db:"id"` // Name string `db:"name"` // } //構造体Dataを一件DBから取得するクエリ ......(1) data, err := Query() if err != nil { log.Fatal(err) } // idをKeyとして、Userモデルを引っ張れるmapを作成 userMap := map[string]User{} for i, sub := range data.Subs { userMap[sub.ID] = User{} // keyとvalueを初期化しておく } // idのスライスを作る ids := make([]string, len(data.Subs)) for i, sub := range data.Subs { ids[i] = sub.ID } // idのスライスからそれらに紐付けられている複数のUserを引っ張ってくる ......(2) users, err := Query2ByIDs(ids) if err != nil { log.Fatal(err) } // usersをidをkeyにmapに入れていく for _, u := range users { userMap[u.ID] = u } // 以降、userMapからkeyをIDにするだけでモデルを取得できる }
このようにすると、(1), (2) のクエリが2個で済むようになり、パフォーマンスの向上が図れる。
書いてるときは、パフォーマンス等を全く考慮しないで書いていたので、ちゃんと考慮したいと思った次第…
RPN
Noahです
久々にHaskell触ってました
構文解析とかやってみたいなと思い、気づいたら深夜のノリでRPNのパーサと諸々を書いていました
半分寝落ちしつつ書いていたけどお愛嬌。
Functionally Solving Problems - Learn You a Haskell for Great Good!
H本は畳み込みでやってるけど、どうしても一旦データ構造に落としたかった…。
CockroachDB備忘録【導入編】
Noahです。
CockroachDBを触る機会があったのですが、いかんせん日本語の情報量が少なすぎるので導入する際に躓いたところを備忘録として残しておきます。
CockroachDBとは
調べた限り、GoogleのSpannerのオープンソースクローンらしい
いわゆるNewSQLであり、RDBとNoSQLのいいとこどりをしている分散型DB
ちなみに最近(2017年5月頃) にVersion1.0がリリースされた模様
インストール
まだ新しいため、日本語の情報が少ない。
brewでも行けるっぽいが、バイナリを直で落としてPATH通すことに
https://www.cockroachlabs.com/docs/stable/install-cockroachdb.html
このページを参考に、インストールする
リンク中にあるtgzファイルをダウンロード後に、
# 解凍 tar xfz cockroach-v1.0.4.darwin-10.9-amd64.tgz # PATHが通してあるディレクトリに移動 cp -i cockroach-v1.0.4.darwin-10.9-amd64/cockroach /usr/local/bin # 確認 cockroach version
これでバージョン情報が出力されればOK
起動
正直、ここが辛かった。
どこを見ても、
$ cockroach start
と書いてあるが、これでは起動しない
CAの証明書がないので、insecureで起動させる
$ cockroach start --insecure
しかし、これでもうまく起動しない。
正確には起動はするが、 localhost:8080
での管理画面で
Connection to CockroachDB node lost.
と怒られるのだ。
実際、起動時のターミナルの画面を見てみると、
ん、admin(管理用ページ)が http://Reo-no-MBP:8080
(Reo-no-MBP
とは自分のマシンの名前である)になっている。
アクセスすると
\(^o^)/
結局
以下で起動するとホストをlocalhost
にして起動できます
$ cockroach start --insecure --host=localhost
結局デフォでホストをいい感じに設定できていなかっただけか…?
ちなみに2台のPC(両方共バイナリから導入した)で両方共同じところで躓いていたため、バイナリで落としたのが良くなかったのかも??
GolangでのJSONパース備忘録
Noahです
GolangでGistにPOSTするプログラムを書いていたのですが、JSONのキーの値を変更せねばならないところで躓いたので備忘録します
GolangにおけるJSONの取り扱い方
基本的に構造体を使います
type Person struct { name string `json"name"` age int `json:"age"` }
このように json
タグを構造体の各要素に書いて、 encoding/json
パッケージ内の Marshal
関数でパースすることで、JSONを生成できます
noah := Person{ name: "NoahOrberg", age: 20, } data, err := json.Marshal(noah) if err != nil { // エラーハンドリング }
という感じで、 data
に []byte
でJSONにパースされたデータが入ります
本題
構造体を扱う場合は、構造体のタグの値は 不変 です
ミュータブルである変数のようにあとから後付できません
タグ情報を取得する関数は reflect
パッケージにありますが、再びセットし直すことはできないのです
そのため、
{ "description" : "説明", "public" : false, "files" : { "hoge.txt" : { <- ココ! "content" : "ファイルの中身" } } }
上のようなGistのAPIのPayloadのようにキーにファイル名を持たせる、などには扱えません。
どうしようかと、取り敢えずIssueだけ立てて解決策が出るまで保留にしようと思ったところ、Issueを立てて数分後に @upamune さんから助言をいただきました (サンプルコードまで用意していただきありがとうございます🙇 )
なるほど、 map
で持たせればJSONのキーの値はmapのキーの値となって変更可能になるのか。
1.のほうで map[string]File
としてmapで持たせて、2.の方で詰め込んでます
このようにすることで、パースする際にキーの値を変更させることができました🎉
gRPCでのUserAgent備忘録
Noahです
Golang で gRPC, Protocol Buffers を触る機会があり、User Agent の取得で少し戸惑ったのでメモします
gRPCとは
おなじみGoogleが2015年2月に発表したRPCフレームワークです
対応言語は、C++, Java, Python などはもちろん、Go や Ruby, Android Java, Node.js, Objective-C, PHP も対応しています
シリアライズはデフォルトでProtocol Buffersを利用していますが、好きなものに取り替え可能なようです
かなり速いと謳われるFlat Buffersなんていうものもあるそうですが、今回はProtocol Buffersで行きます
Protobuf定義ファイルの作成
メソッドとメッセージをProtocol Buffersで定義します
今回、リクエストを投げると User Agentを返すようにするので以下のようにします
syntax = "proto3"; package proto; service UserAgent{ rpc GetUA(UserAgentReq) returns (UserAgentRes); } message UserAgentRes { string user_agent = 1; } message UserAgentReq { }
message
で、リクエスト、レスポンスの構造を指定します
message UserAgentRes { string user_agent = 1; }
内部の string user_agent = 1
で、 = 1
としているのは、代入というより値をユニークに識別するために行っています
service
でメソッドの定義をしています
service UserAgent{ rpc GetUA(UserAgentReq) returns (UserAgentRes); }
この辺は普通の言語と変わりなく、メソッド名、引数の message
と 返り値の message
を指定します
あとは、各言語用にこれをトランスパイルします
protoc はインストール済みとして、Golang用にコンパイルするので以下のプラグインを導入します
$ go get github.com/golang/protobuf/protoc-gen-go
その後、
$ protoc --go_out=plugins=grpc:. api.proto
実行後に api.pb.go
が同じディレクトリにあることを確認してください
サーバーの記述
サーバー側を記述します
package main import ( "log" "net" "golang.org/x/net/context" "github.com/NoahOrberg/grpc-useragent/protobuf" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" ) type UserAgentServer struct { } func NewUserAgentServer() *UserAgentServer { return &UserAgentServer{} } func (s UserAgentServer) GetUA(ctx context.Context, req *proto.UserAgentReq) (*proto.UserAgentRes, error) { log.Print(ctx) md, ok := metadata.FromIncomingContext(ctx) if !ok { return &proto.UserAgentRes{}, grpc.Errorf(codes.Internal, "Cannot get user-agent") } return &proto.UserAgentRes{ UserAgent: md["user-agent"][0], }, nil } func main() { lis, err := net.Listen("tcp", ":50052") if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() proto.RegisterUserAgentServer(s, NewUserAgentServer()) s.Serve(lis) }
google.golang.org/grpc/metadata
を使い、FromIncomingContext
メソッドにコンテキストを渡して呼ぶことでメタデータを取得します。
md, ok := metadata.FromIncomingContext(ctx)
メタデータを取得したら、今度はそれからUser Agentを取得します。
これはメタデータ上では配列になっていますが、暫定的に配列の最初のみを取り出して返しています。(配列として返しても、ログとしてこの配列を出力しても確認はできる)
クライアントの実装
サーバー側で認識して処理するので、正直 User Agentを返さなくてもいいのですが、一応。
package main import ( "flag" "log" "github.com/NoahOrberg/grpc-useragent/protobuf" "golang.org/x/net/context" "google.golang.org/grpc" ) var ( addrFlag = flag.String("addr", "localhost:50052", "server address host:post") ) func main() { conn, err := grpc.Dial(*addrFlag, grpc.WithInsecure()) if err != nil { log.Fatalf("Connection error: %v", err) } defer conn.Close() c := proto.NewUserAgentClient(conn) resp, err := c.GetUA(context.Background(), &proto.UserAgentReq{}) if err != nil { log.Fatalf("RPC error: %v", err) } log.Printf("\nUserAgent: `%s`", resp.UserAgent) }
ただ単に呼び出して、結果を吐いてるだけですね。
まとめ
メタデータから取得する際に、そもそも「メタデータとは?」となっていたからもうダメ
もっと学習が必要だと思った
vimプラグイン備忘録
Noahです。
使用するエディタがもっぱらVim(vimの使用歴3年程度)なんですが、.vimrcへの設定やプラグインを使うことはあれど、作ったことはまったくなかったので作ってみました。
最初なので純粋にvimscriptだけで書いていきます
何を書こうか迷ったのですが、vimを3年使ってても方向キー使ってしまうときが恥ずかしながらあるためhjklキー矯正のvimプラグインを書こうと思います
…というより、以前このような方向キー矯正プラグインを利用して挫折した経緯があったため、(vimプラグイン書いてみたかったし)せっかくならと自分で実装して、それを忘れないための備忘録。
方向性
先述の通り、3年程度使っておきながら、移動は方向キーという初心者だったので、それの矯正を行うプラグインをテーマにします。
さらに、
「方向キーを使うことは悪魔的行為(デビルズアクション)」
と、天啓を得た(ただのアホ)ので、
「方向キー強制無効化(hjklを使わざるを得ない)、そしてもしも方向キー使用時はサターニャにvim下部のコンソールでツッこまれる」
こんな方向性で行きます
ところでサターニャとは
の、こんな子
かわいい。
いざ作る
- PLUGIN-NAME.vim/ - autoload/ - PLUGIN-NAME.vim - plugin/ - PLUGIN-NAME.vim
autoload/
内に関数定義を書き、plugin/
内でキーマッピングなどをするのが習慣のようです。
なぜ分けるかというと、 autoload/
内で 起動時に読み込みではなく必要時に読み込むスクリプト を書いておくことで、起動速度が改善されるからです。
ちなみに、偉大な先人の方がプラグインを書いており、
このプラグインから、雛形を簡単に作ることが出来ます
Neobundleの場合は ~/.vimrc
に
NeoBundleLazy 'mopp/layoutplugin.vim', { 'autoload' : { 'commands' : 'LayoutPlugin'} }
dein.vimの場合は $XDG_CONFIG/nvim/init.vim
に
call dein#add('mopp/layoutplugin.vim')
と記述し、:call dein#update()
などでインストール後に
:LayoutPlugin castOfArrow.vim
とすることで、プラグインのディレクトリ構成を一発で作れます。
雛形も作れたところで、autoload/
内に関数を定義していきます
"============================================================================= " File: castOfArrow.vim " Author: noahorberg " Created: 2017-03-05 "============================================================================= scriptencoding utf-8 if !exists('g:loaded_castOfArrow') finish endif let g:loaded_castOfArrow = 1 let s:save_cpo = &cpo set cpo&vim function! castOfArrow#Cast() let l:dist = ["hjklキー使えってこと!?師匠なのに!","こんな方向キー使う奴野放しにしてるなんて天界どうかしてるっ!!","矢印キーで移動するようとするなんて・・・悪っ!!", "どう!?矢印キーなんて最高に悪魔的な行為でしょっ!!", "矢印キーの形態はすべて掌握しているわ それもすべて・・・","上矢印とかkってなに!?"] let l:match_end = matchend(reltimestr(reltime()), '\d\+\.') + 1 let l:rand = reltimestr(reltime())[l:match_end : ] % (len(dist)) echo dist[rand] endfunction let &cpo = s:save_cpo unlet s:save_cpo
シンプルですね。乱数で文字列の配列からランダムに要素を選び、echoするだけです
直で配列に書くのは気が引けましたが、jsonファイルにまとめるにも、JSONパースが面倒そうだったので直書き。
では、plugin/
内
"============================================================================= " File: castOfArrow.vim " Author: noahorberg " Created: 2017-03-05 "============================================================================= scriptencoding utf-8 if exists('g:loaded_castOfArrow') finish endif let g:loaded_castOfArrow = 1 let s:save_cpo = &cpo set cpo&vim nnoremap <Right> :call castOfArrow#Cast()<CR> nnoremap <LEFT> :call castOfArrow#Cast()<CR> nnoremap <Up> :call castOfArrow#Cast()<CR> nnoremap <Down> :call castOfArrow#Cast()<CR> " inoremap <Right> <ESC>:call castOfArrow#Cast()<CR>i " inoremap <LEFT> <ESC>:call castOfArrow#Cast()<CR>i " inoremap <Up> <ESC>:call castOfArrow#Cast()<CR>i " inoremap <Down> <ESC>:call castOfArrow#Cast()<CR>i let &cpo = s:save_cpo unlet s:save_cpo
こっちには、キーマッピング系のものを書きます。ひとまずノーマルモード時は方向キーを強制的に使えないようにマッピングします。(インサートモード時は挙動が変だったため保留にしてます。)
完成品
という感じでひとまず完成させたのが 3ヶ月前(この記事ドラフトのままにしててすっかり忘れていた。)
サターニャかわいい。
Golang覚書
Noahです
Golangを触る機会があったので備忘録も兼ねて残しておきます
というかぶっちゃけ、
- 作者: 松木雅幸,mattn,藤原俊一郎,中島大一,牧大輔,鈴木健太,稲葉貴洋
- 出版社/メーカー: 技術評論社
- 発売日: 2016/09/09
- メディア: 大型本
- この商品を含むブログ (4件) を見る
この本の覚書が多いです
$GOPATH と $GOROOT
そもそもここからわかっていなかった。
$GOPATHとは、Golangのソースコードなどを置くワークスペース。
基本どこでもいいらしいが、自分は$HOME/go/
ディレクトリにしておいた。
その下にsrc/
を配置、その中に各プロジェクトを置いたりする。
githubなんかからgo get
した際には$GOPATH/src/github.com/<USER NAME>/<REPOSITORY>
のように置かれる。
$GOROOTはGolangのインストールパス。昔は指定する必要があったみたいだが、今は不要のようだ。
vendor
RubyでいうところのBundlerみたいなものがGolangにもある。
それがglide
brew install glide
で入手可能。
使い方は簡単で、対象のプロジェクトディレクトリで
$ glide init
とすればglide.yml
ができて、自動で依存関係共々そこに記述される。
その後に
$ glide update
とすれば、glide.lock
, vendor/
ディレクトリが生成され、その中に依存するパッケージなどが配置される。
ディレクトリ構成
以下のようにしている。
project/ ├ main.go -- package main ├ lib/ │ └ pkg1/ │ └ pkg1.go -- package pkg1 ├ glide.yml ├ glide.lock └ vendor/ -- 依存パッケージなど
ちなみに、$GOPATH/github.com/<MY USER ID>/<PROJECT NAME>/
で管理することで、実際にGithubなどにあげたときと同じ環境で出来る。
main.go内でlib/
内のパッケージをimportする際にも、このディレクトリ構成なら、import "github.com/<MY USER ID>/<PROJECT NAME>/lib/<PKG NAME>"
でいけちゃうので楽。
最後に
Golangを、「Gopher君、超かわいい!!」くらいにしか思ってなかったが、今回触ってみてやはり他言語と比べて割りとシンプルだという印象を受けた。
ろくに書けるわけでは無い(A Tour of Go を一通りやっただけ)のに「みんなのGo言語」を読んで少し駆け足気味だったのでもう少し基本的なところから復習しがてら何か成果物を作っていけたらな、と思います。