備忘録、はじめました。

作業したこと忘れないようにメモっておきます。

Erlangプロセスのmessage_queue_dataの影響について見てみた

前回の「Erlangのメッセージングでバッファを使う - 備忘録、はじめました。」でバッファがないときに大量のメッセージを送受信すると非常に遅くなることが分かりました。 この劣化の要因は大量のメッセージを一斉に送信していることです。

なので、プロセスのメッセージパッシングの流れについて調べてみました。

  1. 送信されたメッセージの大きさの計算
  2. メッセージに対して十分の領域をアロケーション
  3. メッセージのペイロードをコピー
  4. メタデータを含めたメッセージの箱(message container)をアロケーション
  5. “受信プロセスのメッセージキュー”にそのメッセージの箱を挿入

プロセスがメッセージを送受信するときのパフォーマンスは、上記のステップ2が大きく影響を受けます。

ステップ2は、デフォルトではプロセスのロックを取得し、ヒープの空き領域の計算、割当をします。 この処理の間、受信プロセスはメッセージを待ち続けることになります。

erlang 19.0から、ステップ2のアロケーション戦略をプロセス単位で変更できるようになりました。 アロケーション戦略を変更することで、メインへのロックなしにメッセージを送信できるようになるようです。 変更方法は以下の感じです。

process_flag(message_queue_data, on_heap|off_heap)

今回は、この「message_queue_data」について少し調べてみた話です。

続きを読む

Erlangのメッセージングでバッファを使う

Erlang Angerの3章、3.3.2あたりで過負荷についての対処法として、キューのバッファを作るのが効果的という話が載っていたので、実際にどのくらい差が出るのか簡単に計測してみた話です。

問題設定:

buffer,erlang,message passing

この二つの設定に対して、1,000〜500,000メッセージをbenchサーバーから並列で送信します。

PCの環境:

続きを読む

go-i18nで必要なjsonをgoのソースコードから生成するgoi18n-parser

モチベーションとして、

  • 多言語対応をしたい
  • goのソースコードから自動で翻訳ファイルを作成したい
  • シンプルでいい(複雑な機能は要らない)

があり、最小限で作ってみたので紹介します。

github.com

goi18n-parser

goi18n-parser ではソースコードの解析に go/ast を使用しています。

go/astを使うと、goのソースコードを抽象構文木にして中身を解析することが出来ます。

今回解析したい内容は、

  • 翻訳関数の使われている部分

となります(この「翻訳関数」とは便宜的に書いてるだけです)。 goのソースコード内に翻訳関数が使われていて、その翻訳関数の引数が翻訳対象となるメッセージのIDとして記憶されていきます。

具体的には次のようなコードです:

    ...

    fmt.Println(SomeStruct.T("server_error_something"))

    return fmt.Error(T("server_error_invalid_user_session"))

    ...

上記の例だと翻訳関数は T になります。この Tgo/ast を使って見つけていきます。

今回作成したパッケージ goi18np を使って取得する場合は次のように書けます:

a := goi18np.Analyzer{}
a.AnalyzeFromFile("/path/to/code.go")
a.SaveJSON("/path/to/translate.json")

また、複数ファイルも同時に解析できます:

a.AnalyzeFromFiles([]string{"/path/to/code.go", "/path/to/code2.go"})

入力と出力の例を以下に紹介します。

package sample

import (
    "fmt"

    "github.com/nicksnyder/go-i18n/i18n"
)

type A struct {
    T i18n.TranslateFunc
}

func SampleCaller() {
    T, _ := i18n.Tfunc("eu-US")

    fmt.Println(T("sample_uniq_key"))
    for i := 0; i > 100; i++ {
        if i%10 == 0 {
            fmt.Println(T("sample_uniq_key_2"))
            x := T("sample_uniq_key_3")
            fmt.Println(x)
        }
    }

    T2, _ := i18n.Tfunc("en-US")
    a := A{T: T2}

    fmt.Println(a.T("sample_uniq_key_4"))

    fmt.Printf("%s,%s,%s", a.T("sample_uniq_key_4"), T("sample_uniq_key_5"), T("sample_uniq_key_2"))
    return
}

このソースコードを解析した結果が次のようになります:

[
  {
    "id": "sample_uniq_key",
    "translation": ""
  },
  {
    "id": "sample_uniq_key_2",
    "translation": ""
  },
  {
    "id": "sample_uniq_key_3",
    "translation": ""
  },
  {
    "id": "sample_uniq_key_4",
    "translation": ""
  },
  {
    "id": "sample_uniq_key_5",
    "translation": ""
  }
]

翻訳内容の部分を one または other をつけて、単数形と複数形の文章に分ける処理はしていません。 そこまでの自動化はせずに、必要になったときに人の手で追加する感じになると思います。

また、実際にjsonで保存するときは go1.8 で出てきた sort.Slice を使うなどして、ID順にソートして差分が少なくなるように工夫が必要かと思います。

Dockerで手元にmysqlレプリケーション環境を構築

はじめに

手元でレプリケーションの挙動とか、遅延の再現や復旧方法を見直したい場合などに手軽に実験や確認できる環境が欲しい。 Vagrantとかで一つ一つインスタンス立ち上げるのもヘビーなのでdockerで構成させたい、といった理由で作ってみました。

TL;DR

実施内容

前準備

前準備として、

  • dockerのインストール

をしておきます。また、masterとslaveのDockerfileを分けたいので、以下のように階層構造を作ります。

├── master
│   ├── Dockerfile
│   ├── my-master.cnf
│   └── start-master.sh
├── README.md
├── slave1
│   ├── Dockerfile
│   ├── my-slave.cnf
│   └── start-slave1.sh
└── ...
続きを読む

httptestでWriteHeaderができなかった話

はじめに goでhttpのhandlerのテストを書いているときに詰まったところを書きます。 詰まったところは、httptest.NewRecoder()の再利用ができなかったことです。 正確には、再利用する方法はあるかもしれないがよくわからなかったので、再度NewRecoder()を呼び出して、新規作成した、で解決しました。

内容

package mytest                                                                    
 
import (
    "bytes"
    "encoding/json"
    "net/http"
    "net/http/httptest"
    "testing"
)
 
func TestOverride(t *testing.T) {
    handler := http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
        var js map[string]interface{}
        decoder := json.NewDecoder(req.Body)
        if err := decoder.Decode(&js); err == nil {
            if js["msg"] == "error" {
                res.WriteHeader(http.StatusBadRequest)
                res.Write([]byte(`{"result": false}`))
            } else {
                res.WriteHeader(http.StatusOK)
                res.Write([]byte(`{"result": true}`))
            }
        }
    })
 
    // 1: StatusOK
    jsons := `{"msg": "test"}`
    w := httptest.NewRecorder()
    r, err := newRequest([]byte(jsons), "POST", "application/json")
    if err != nil {
        t.Errorf("%s", err)
    }
 
    handler.ServeHTTP(w, r)
    if w.Code != http.StatusOK {
        t.Errorf("Expected status code is 200 but got %d", w.Code)
    }
 
    // 2: BadRequest error code
    jsons = `{"msg": "error"}`
    w = httptest.NewRecorder() // ここで再度NewRecoder()を呼ぶ
 
    r, err = newRequest([]byte(jsons), "POST", "application/json")
    if err != nil {
        t.Errorf("%s", err)
    }
 
    handler.ServeHTTP(w, r)
    if w.Code == http.StatusOK {
        t.Errorf("Expected status code is NOT 200 but got %d", w.Code)
    }
}
 
func newRequest(data []byte, method string, c string) (*http.Request, error) {
    req, err := http.NewRequest(
        method,
        "",
        bytes.NewBuffer([]byte(data)),
    )
    req.Header.Set("Content-Type", c)
    return req, err
}

上記は、テストが通ります

はじめは、コメント部分をw.Body.Reset()をすれば大丈夫なのかなぁと安易にやったのですが、そうするとHeader部分への上書きはできませんでした。おそらく、GOの公式ドキュメントに記載されている内容なのかと思います。したがって、w.Body.Reset()で行うと、2回目のBadRequestのエラーを期待するのですが200が返ってくる結果となってしまいます。

goのバージョンを切り替えるだけのスクリプトコマンドを作成

はじめに

goのバージョンだけを単に切り替えたかったので、goのバージョンを切り替えるだけのスクリプトコマンドを作りました。 自分用でサクッと作ったため、installのファイルはlinux-amd64のみ対象としちゃってます。

インストール

ソースをダウンロードして、パスが通った場所にファイルを置くだけです。

$ git clone https://github.com/tkyshm/goenv.git
$ mv bin/goenv /usr/local/bin

また、bashzshの補完が使いたい場合は、補完関数も用意したのでcompletionsが格納されたディレクトリに放り投げるか読み込んであげれば補完もできるようになります。

使い方

helpを使っていただければほとんどわかるかと思います(中身単純なので)。

$ goenv help

切り替えはuseのsubcommandを使えばできます。 下みたいな感じです。

$ goenv use go1.6rc2
switched go version.
go version go1.6rc2 linux/amd64

github.com

Chefで複数リソースに更新があった時だけbashを実行

はじめに

cookbookのfilesの中に大量のtomlファイルが入っていて、各tomlファイルに対してコマンドを実行したい場面に直面しました。 ファイルの更新が合った時だけ、そのコマンドを実行したかったので、その時の作業をメモします。

目的

  • confファイルが更新されているかをチェック
  • 更新されていればファイルを差し替えしてスクリプトを実行

作業

作業環境は、仮想マシンにChef-ServerとChef-Clientを用意し、作業(knifeを実行するマシン)はホストマシンになります。 下記、レシピになります。

recipe.rb

files = run_context.cookbook_collection["my-cookbook-name"].file_filenames
files.select!{ |file| file =~ /\.toml/ }

files.each do |file| 
  # 2. 配置したtomlを使ってコマンドを実行
  bash "exec-#{File.basename(file,".*")}" do
    action :nothing # 実行しない
    user "vagrant"
    cwd "/home/vagrant/conf"
    code "echo #{File.basename(file,".*")} >> /home/vagrant/exec-stamp.txt" # 処理したいコマンド
  end
end

files.each do |file| 
  # 1. tomlを配置
  cookbook_file "/home/vagrant/conf/#{File.basename(file)}" do
    owner "vagrant"
    group "vagrant"
    mode 0644
    source File.basename file
    not_if { FileUtils.cmp("/home/vagrant/conf/#{File.basename(file)}", file) } # ファイルの比較
    notifies :run, "bash[exec-#{File.basename(file,".*")}]" # bash[exec-hoge] の内容を実行しにいく
  end
end
続きを読む