Go1.8からhttp/httpgtraceが追加され、 HTTP通信にフックを差し込めるようになりました。 以前触った時はパフォーマンス測定に利用しましたが、 他に面白い活用法はないかとWebNTPというのを作ってみました。
webntp HTTP/HTTPS/Websocket上でNTP(Network Time Protocol)っぽいことをするプログラムです。
HTTP/HTTPSで時刻同期 日本標準時はNICTの管理する原子時計が基準になっており、 NICTでは原子時計に直結したNTPサーバー(ntp.nict.jp)の提供を行っています。 それに加えて、https/httpサービスも提供しており、 ブラウザを使って現在時刻を取得できます。
APIは簡単でミリ秒単位のtimestampを返してくれるだけです。 その情報からサーバーとクライアント間の時間のズレを算出するわけですが、 HTTP通信では、DNSの名前解決、TCPハンドシェイク、TLSハンドシェイク等々の時間が入ってしまうため、 正確なズレを求めることは困難です。
そこでhttp/httpgtraceを使って、ハンドシェイクを除いたリクエストの送信時刻、レスポンスを最初に受信した時刻から、 より正確なズレを知ることができるのではと作ったのがWebNTPです。 NICTのJSON形式のAPIに対応しており、 以下のように時刻を取得できます。
$ go get github.com/shogo82148/go-webntp/cmd/webntp $ webntp https://ntp-a1.nict.go.jp/cgi-bin/json server https://ntp-a1.nict.go.jp/cgi-bin/json, offset -0.006376, delay 0.024411 2017-03-11 16:08:06.150393313 +0900 JST, server https://ntp-a1.nict.go.jp/cgi-bin/json, offset -0.006376 WebNTPはNICTのAPIと同様の内容を返すサーバーにもなれます。 本家のフォーマットにしたがい、しっかりとうるう秒の情報も返すようになっているので、 ntpdのSLEWモードを切った状態でお試しください。
$ webntp -serve :8080 $ curl -s http://localhost:8080/ | jq . { "id": "localhost:8080", "it": 0, "st": 1489217288.328757, "time": 1489217288.328757, "leap": 36, "next": 1483228800, // 今年の1/1にあったうるう秒の情報 "step": 1 } ところで、URLにcgi-binが入っているのは、過去との互換性を保つためにそうなっているのか、 今もCGIで動いているのか、気になる・・・ (少なくとも初期実装はPerlのCGIだったみたいですね)。
以前mattnさんが紹介していたschollz/jsonstore。 時間が経ってしまいましたが「ここは高速化できそうだなー」といじってみたので、 やってみたことをメモ。
本来は上流にフィードバックしたほうがよいのですが、 本家のほうも修正が入ってコンフリクトして面倒になったので、 フォーク版をそのまま置いておきます。
shogo82148/jsonstore 高速化 まだまだ高速化できそうなところがあったので、いじってみた部分です。
ロックの範囲を最小にする ロックの範囲を小さくすることで、並列処理時の性能が上がります。 例えば、jsonstoreに値を入れるSetメソッドは、 以下のようにSet全体がロックの対象になっていました。
func (s *JSONStore) Set(key string, value interface{}) error { // Set の中全体がロックの対象になっている s.Lock() defer s.Unlock() b, err := json.Marshal(value) if err != nil { return err } if s.data == nil { s.data = make(map[string]*json.RawMessage) } s.data[key] = (*json.RawMessage)(&b) return nil } jsonのエンコード処理はjsonstoreの中身を触らないので並列実行可能です。 次のように s.data だけをロックの対象にすれば十分です。
func (s *JSONStore) Set(key string, value interface{}) error { // json.Marshal は並列実行可能 b, err := json.
スケーラブルにIDを生成する方法として Twitterのsnowflakeが有名です。 1024台までスケールすることが出来ますが、各snowflakeのサーバにユニークなWoker IDを割り振る必要があります。 IDを振るためのサーバにIDを振るのが問題になるとは難しいですね。
各snowflakeサーバにIDを振る親玉Worker ID配布サーバを作るというアイデアはあったのですが、 Worker IDサーバの可用性を考えるのが大変で手を付けていませんでした。 最近になってWorker IDサーバとしてRedisを使い、ソート済みセット型で管理すれば楽できるのでは? と思いついたので、やってみたというお話です。
概要 レポジトリはこちらです。
shogo82148/yaraus 他のsnowflake-likeなID発番サーバの実装として katsubushiや sonyflakeなんていうのもあります。 これらのID発番サーバにRedisを使ってWorker IDを割り振るコマンドです。 Redis3.2以上推奨です。
使い方 Go製なのでgo getでインストールできます。
go get github.com/shogo82148/yaraus/cmd/yaraus # 1から1023までのIDが使えるようにRedisを初期化 $ yaraus init -min 1 -max 1023 # ユニークなIDが必要な処理を実行する $ yaraus run -- echo {} 2017/02/25 17:19:16 getting new id... 2017/02/25 17:19:16 client id: YourHostName-1488010756.738-1, id: 1 2017/02/25 17:19:16 sleep 2s for making sure that other generates which has same id expire.
「Golang Rust」とググると、関連項目は「Rust vs Go」のように GolangとRustが対立しているような項目ばかりです。 まあまあ、もっと仲良くやろうじゃないですか、ということで、 どうしたら仲良くなれるかを考えました。 Polyglotにして同じソースコードの中に閉じ込めてやれば、 そのうち仲良くなるのではないかと考え、 RustとGoのPloyglotを作ってみました。
結果 /*/*/ package main import "fmt" func main() { fmt.Print("Hello Go!!") _ = `*/*/ fn main() { println!("Hello Rust!!"); //` } /*/*/ package main import "fmt" func main() { fmt.Print("Hello Go!!") _ = `*/*/ fn main() { println!("Hello Rust!!"); //` } 仕組み 一番のポイントは最初の行の /*/*/ です。 RustもGoも/* */形式の複数行コメントに対応していますが、 Rustはネストに対応しており、Goはネストはできないという違いがあります。 この違いにより、Rustは/*/*/を/* /* /のように「二重にネストしたコメントの開始部分」として扱いますが、 Goは/* / */のように「/をコメントアウトしたもの」と見なします。 これにより2行目package main以降はGoには普通のコードに見えますが、 Rustからは単なるコメントとして認識されます。
次はGoからRustへの切り替えです。 Goではバッククオートで複数行文字列を定義できるので、その中にRustのコードを書きます。 この中ではバッククオートさえ使わなければ自由にRustのコードを書くことが出来るので、 あとはGoのコードだけ上手くコメントアウトされるよう調整すれば完成です。
昨年末にSongmuさんからお話を頂き、 WEB+DB PRESS Vol.97内の連載「第43回Perl Hackers Hub」に 「PerlでのRedis活用法」というタイトルで寄稿しました。 発売日は2月24日です。
内容 簡単に内容を紹介しておきます。 Perl使いではじめてRedisを使う人向けに書いたつもりです。
Redisの簡単な説明 Redisのインストール方と、Perlからの接続方法、そしてRedisの型の説明です。 記事の中でも紹介していますが、Redisはその豊富な型が特長です。 読者はきっとPerl使いだろうということで、Perlの型(Perlにも型はあるんだよ!!)と 比較しながら簡単に紹介しています。
Redisの応用例とCPANモジュールの紹介 Redisを使うとこんなことができるよ、という紹介です。 CPANで公開されているRedis関連のモジュールも合わせて紹介しています。
Redis自体の注意点 以前Redisを使ったサービスの運用に携わっていたのですが、 そのなかで実際に起きたことを元に、Redisの注意点について書きました。 さいわいサービスが停止するような事故にはありませんでしたが、 メトリックスを眺めながらエンジニア勢でヤバイヤバイ騒いでましたね・・・。 みなさんも気をつけて下さい。
執筆してみての感想 昔から文章を書くのにはだいぶ苦手意識があり、 今回の執筆も非常に苦労しました。 一文の前半を書いた時点で 「今から書こうとしている情報は本当に必要なのか」 「自分の記憶違いで間違った情報なのでは」と不安になり、 色々考えているうちに、何書こうとしてたのかわからなくなるんですよね。 まずは適当に書き上げて、後からちゃんと推敲しよう、 とは思いつつもなかなか進められず・・・。 スループットを上げたい。
細かい表現とかも気になってなかなか進まないので、 こういうの入れて頑張ろうと思います!
VS Codeでtextlintを使って文章をチェックする gitbookで技術書を書く環境の構築手順 (執筆が進まないと、こういう環境構築に時間をかけてしまうのもよくないと思うんだ・・・)
余談 ところで、Vol.97と第43回ってどっちも素数ですね! 雑なプログラムを書いて調べてみたところ、 両方素数になるのはVol.83, 第29回以来、7回目(これも素数だ!)。 次はVol.101, 第47回です。 そのときのPerl Hackerは誰になるのでしょうか。楽しみですね!
use warnings; use strict; sub is_prime { my $n = shift; return 0 if $n < 2; my $i = 2; while($i*$i<=$n) { return 0 if $n % $i == 0; $i++; } return 1; } my $i = 1; for my $n(1.
整数の公式でフィボナッチ数列を求めるという記事を読んで、 「これコンパイル時ならGoでも簡単に計算できるのでは?」と思いやってみたメモ。
背景 みんな大好きフィボナッチ数列(要出典)。 漸化式で定義されているため、再帰やループを使って書くことが多いと思いますが、 閉じた式で書くことが知られています。 ただし、この一般式には無理数の演算が入るので、コンピュータで厳密に扱うことはできません。 ところが、さきほど紹介した記事で紹介された方法を使うと、整数の演算のみで実現できるそうです。
原理などはネタ元の記事を参照してもらうとして、 Python3では以下のように書けるらしいです。
def fib(n): return (4 << n*(3+n)) // ((4 << 2*n) - (2 << n) - 1) & ((2 << n) - 1) ある程度大きなフィボナッチ数を求める場合、 計算途中の値が非常に大きくなるため、多倍長整数が必要となります。 Python3は多倍長整数に組み込みで対応していますが、 Goではmath/bigパッケージを利用する必要があります。
なんか面倒だなGolangと思っていたのですが、 Better C - Go言語と整数 #golangを読んで、 「Goの定数には型がない(場合がある)」「任意の精度で計算してくれる」ということを知り、 「つまりコンパイル時に定数として計算すれば楽にいけるのでは!!」と考えたわけです。
結果 ちょっと複雑な式ですが、個々の演算自体はPython3もGoも変わらないので、 翻訳は簡単ですね。
package main import "fmt" const Fib0 = 1 // 0だけはうまくいかない const ( _ = (4 << (iota * (3 + iota))) / ((4 << (2 * iota)) - (2 << iota) - 1) & ((2 << iota) - 1) Fib1 Fib2 Fib3 Fib4 Fib5 Fib6 Fib7 Fib8 Fib9 Fib10 Fib11 Fib12 Fib13 Fib14 Fib15 Fib16 Fib17 Fib18 Fib19 Fib20 Fib21 ) func main() { fibs := []int{ Fib0, Fib1, Fib2, Fib3, Fib4, Fib5, Fib6, Fib7, Fib8, Fib9, Fib10, Fib11, Fib12, Fib13, Fib14, Fib15, Fib16, Fib17, Fib18, Fib19, Fib20, Fib21, } for i, fib := range fibs { fmt.
Go1.8ではdatabase/sqlのcontextサポートが入ります。 (きっと今日のGo 1.8 Release Partyで詳しく説明があるはず、たぶん) それにともないGo言語でSQLのトレースをするで紹介した shogo82148/go-sql-proxyでもcontextを扱えるようにしました。
Go1.8新機能のサポート Golang 1.8 でやってくる database/sql の変更点で mattnさんが紹介しているように、Go1.8ではdatabase/sqlにいくつか新機能が追加されます。 (mattnさんの対応が早すぎて、メソッド名とか微妙に変更が入っているので注意)
特に大きなのがcontextのサポートでしょう。以下のようなコードでクエリのキャンセルが可能になります。
ctx, cancel := context.WithCancel(context.Background()) go func() { // 1秒待ってからキャンセル time.Sleep(1 * time.Second) cancel() }() rows, err := db.QueryContext(ctx, "SELECT name FROM test where id = ?", id) if err != nil { log.Fatal(err) } go-sql-proxyでもcontext対応を行ったので、 proxyを経由した場合でも、キャンセルが可能になります。 (もちろん、originとなるドライバの対応も必要です)
Go1.8ではcontextサポート以外にもいくつか新機能が追加される予定です。 これらについても、originとなるドライバが対応していれば、go-sql-proxy経由でも全く同じように扱えます。
contextとHookの関連付け contextにHookを関連付けて、一部のクエリにだけHookを付けることができるようになりました。 例えば以下のようなコードでctxに関連したクエリだけログを出力できます。
package main import ( "context" "database/sql" "github.com/shogo82148/go-sql-proxy" ) var tracer = proxy.NewTraceHooks(proxy.TracerOptions{}) func main() { // 何もしないproxyをインストール proxy.
Go1.8beta1が出た時に、Go1.8で追加される予定のGraceful Shutdownについて書く! とTwitterに書き込んで早1ヶ月。 この前の金曜日にGo1.8rc2がリリースされ、正式リリースも間近になってきて、 さすがに書かねばという気持ちになって来たので、がんばって検証してみます。
公式サポートで増える予定の機能 以前Go言語でGraceful Restartをするときに取りこぼしを少なくするで 紹介したようにshogo82148/go-gracedownというものを書きました。 あれから時は経ち、ついにGo1.8からはGraceful Shudownがbuild-inの機能として提供される予定です。 公式サポートが入ることによって、以下のような機能を使えるようになります。
HTTP/2のGraceful Shutdownができる HTTP/2ではGOAWAYフレームという接続を切ることを通知する機能があります。 Go1.8からはシャットダウン時にこのGOAWAYフレームを送ってくれるようになります。 GOAWAYフレームはサーバ側から任意のタイミングで送ることができ、 どこまで正常に処理できたかをクライアントに伝えられるという利点があります。
余談ですが、この機能はx/net/http2を利用している場合は動かないらしいです。 importしたときには動かないけどbundleしたときにだけ動く黒魔術が使われているためです。 覚えておいても今後絶対使うことはなさそう。というか使いたくない・・・。
contextが使える go-gracedownを作った頃は、contextはまだ標準パッケージに取り込まれていなかったので対応していませんでした。 (1.7のリリース時に対応を怠っていただけとも言える) net/httpのシャットダウンはもちろんcontextに対応しています。 これにより、Graceful Shutdownを中断して強制終了する、 ということが簡単にできるようになります。
公式サポートで変更になる予定の挙動 Keep-Aliveでのリクエストの挙動が少し変わります。 1.7以前のgo-gracedownでは、クライアントにKeep-Aliveが無効になったのを伝え、 クライアント側から接続を切るのを待つように実装してしました。 多少接続時間が延びたとしてもクライアント側でよくわからないエラーになるよりはマシだろ、との考えからです。
1.8からはシャットダウン時にIdle状態(TCP接続は有効だけど、リクエストは処理していない状態)な接続は切断されます。 内部で使っているServer.SetKeepAlivesEnabledの 挙動が変更になったためです。
Goの中の人的には「この挙動が原因で万が一トラブルになっても、クライアントがリトライしてくれるから大丈夫でしょ」とのことのようです。 サーバシャットダウン以外にもネットワークトラブル等でも接続は切れるので、 クライアント側で頑張ってというのは正論ですが、 どの程度エラーが増えるのかは気になるところです。
go-gracedownの対応 go-gracedownはGo1.8でコンパイルされたときはbuild-inの機能を直接使うようになります。 中身はほとんどがインターフェースの互換性を保つためのコードなので、 機能的なメリットは完全になくなってしまいました・・・。 HTTP/2サポートも問題なく動くはずです。 逆にパッケージの依存が増えること以外はデメリットはないともいえます。
Go1.7以下では今までの方法にフォールバックしてくれます。 というわけで、以下のような人には有用です。
深淵な理由でGo1.7以下しか使えない人 Go1.8とGo1.7以下のサポートがどうしても必要な人 Go1.8にアップグレードしたけど、graceful shutdownの処理を書き換えるのがめんどくさい人 ところで、環境が悪いときに性能を落としたり機能を制限することをフォールバック(fall back)というわけですが、 逆に環境が良いときに性能を上げたり機能を拡張することはなんていうんですかね? モデムでは通信環境が良いときに高速な通信方式に切り変えることを「フォールフォワード(fall forward)」というらしいです。 「Go1.8ではbild-inのGraceful Shutdownにフォールフォワードする」で使い方あってます?
使い方 Server.Shutdownを使う Go(その3) Advent Calendarの 最終日の記事でも扱ってますが改めて。
package main import ( "context" "fmt" "log" "net" "net/http" "os" "os/signal" "syscall" "github.
先日mattnさんの記事を読みました。
golang の http.Client を速くする nettというパッケージを使って 名前解決の結果をキャッシュすることで、http.Clientを早くするというものです。 この記事に関して、ちょっと疑問に思ったことがあったので、検証してみました。
疑問 疑問に思ったのは以下の点です。
名前解決遅すぎでは? ベンチマークの結果を見ると5億ns(=500ms)ほど速度が改善しています。 3つのURLに対してリクエストを投げているので、初回を除く2回DNSのキャッシュがヒットし、 名前解決2回分の速度改善になるはずです。 と、いうことは、名前解決1回あたり250msかかっている計算になります。 googleのsearchは302でリダイレクトがかかるので、Client.Getの呼び出し1回あたり2回リクエストが飛ぶ、 ということを計算に入れても100msほどかかる計算です。
Google先生の謎テクノロジーによってかなりの最適化がされているはずですし、 ネットワークプロバイダのDNSキャッシュにヒットする可能性も高いでしょう。 名前解決程度にこんなに時間がかかっていたらスプラトゥーンが出来ない! (mattnさんがスプラトゥーンをプレイしているかは知らない)
2017/01/16追記: mattnさんはスプラトゥーンをプレイしていないそうです。残念。
あとスプラトゥーンしてません。。。
— mattn (@mattn_jp) 2017年1月14日 もちろん、ネットワークが混雑していたり、 モバイルネットワークを利用していたり、という可能性もありますが、 ちょっと不自然な印象を受けました。
Keep-Aliveされてるのでは? スキーマがhttpsになっているので、Google先生相手ならHTTP2で通信していてもおかしくありません。 HTTP2は基本的にドメイン毎にコネクションを1つだけ張って、それを使いまわします。 もし仮にHTTP1.1で通信していたとしても、http.ClientはデフォルトでKeep-Aliveが有効になっているので、 普通に使うとコネクションを再利用してくれます。
そういうわけで、名前解決以前にそもそもTCPのコネクション確立もスキップされている可能性が高いのでは? と思ったわけです。 この予想が正しければ、名前解決は初回リクエストでしか行われないので、ベンチマークに差はでないはずです。
HTTPリクエストの様子をトレースしてみる これらの疑問を解消するために、HTTPリクエストの様子をさらに詳細に解析してみることにしました。
DNSキャッシュなし版をトレースする Go1.7からnet/http/httptraceというパッケージが追加され、 名前解決やコネクション確立etcのタイミングにフックを仕込めるようになりました。 これを利用すれば各段階でどの程度時間がかかっているかが具体的に分かるはずです。
頑張って自前でフックを差し込んでもよいのですが、 deeeetさんのgo-httpstatという便利パッケージがあるので、 これをありがたく利用させていただきます。 go-httpstatを使うと時間計測を行うコードを簡単に差し込むことができます。
package main import ( "io" "io/ioutil" "log" "net/http" "time" "github.com/tcnksm/go-httpstat" ) var ( urls = []string{ "https://shogo82148.github.io/blog/2016/12/20/redis-fast-0-dot-19-released/", "https://shogo82148.github.io/blog/2016/12/15/leap-second-in-datetime-dot-pm/", "https://shogo82148.github.io/blog/2016/11/23/qr-code/", } ) func main() { client := &http.
Redis::Fast 0.19 をリリースしました。 主な変更点は以下の通りです。
reconnect_on_error オプションの追加 Sentinelのノード一覧が更新されない不具合の修正 IPv6の実験的サポート reconnect_on_error オプションの追加 @yoheimutaさんからのプルリクエストです。 今まではネットワークエラーが発生した時のみ再接続処理が走っていましたが、 Redisがエラーを返した場合にも再接続を行うようになります。 マスタースレーブ構成をしているときに、 何らかの原因によりRedis::Fastからのコネクションを維持したまま、 マスターがスレーブに降格してしまった場合に対処するための機能です。 以下のように設定することで、新しいマスターに再接続を行うことが可能になります。
my $r = Redis::Fast->new( reconnect => 1, # 0以上で再接続有効 reconnect_on_error => sub { my ($error, $ret, $command) = @_; if ($error =~ /READONLY You can't write against a read only slave/) { return 1; # 再接続を行う。次の再接続まで最低1秒空ける } return -1; # 再接続は行わない }, ); Sentinelのノード一覧が更新されない不具合の修正 Redis::FastにはどれかひとつのSentinelノードに接続すると、 他のノードの情報を自動的に収集する機能があります。 この機能が最新のRedisでは動いていなかったので修正しました。 具体的にいつからなのかまでは追ってないのですが、 Redisのバージョン3.0.6から3.2.6の間のどこかで ノード一覧の形式が変わってしまったようです。
(最近Sentinelの話題を聞かないけど、みんな使ってるのかな・・・)
IPv6の実験的サポート サーバの指定にIPv6のアドレスが使えるようになりました。 Redis::Fast->new(server => "[::1]:6379") のような指定ができます。