今年もおつかれさまでした

今年もお仕事してました確認。来年もがんばるぞい

f:id:karahiyo:20151227142354p:plain

いつなにをやってたのかとかあまりはっきりとは覚えていないのですが、データベースまわりの運用を頑張ってた時期があったのは記憶にあります。お仕事ではScala, PHP, Gaucheなどを書いてました。AWSとはまたちょっと友達になれた気がします。趣味では、Gauche ちょっと Golang, Swiftなど。Ruby最近さわってないなぁ

!

今年仕事をしていて考えてたのが学生の頃に参加したインターンで学んだリーンスタートアップの話。

kotobank.jp

芸歴2年といくらか経験を積んだ今だとあのときのリーンの話はより訴えるものがあります。

"継続的な改善をする。で、必要な時に必要なことをする" (とざっくり解釈してます)

  • 優先順位の確認
    • まず何かしらの課題があること
    • その課題は本当に課題か。今本当に必要なものか。必要なら課題の確認という仕事も出てくる
  • その課題を解決できる機能をつくる
    • 意図が分かりやすいコード
      • そのコードが提供する機能はその課題の解決
        • そのコーダが提供する機能がシンプルならコードの説明のためのドキュメント、コミットメッセージは不要?
    • 認識している課題以外のことを意識しすぎていないか(ついつい色々目に付いてしまう。。
  • 継続的な改善のために
    • あまり将来のことは考えすぎないが、そのときになったらしやすいように
    • 意図がわかりやすいコードなら改善しやすい。意図がわかりやすいコードとは、、、。意図が分かりにくい、というのはまだ分かりやすい。プンプンにおう。
  • 必要な時を把握するために
    • 気づける仕組みを作る
      • test ... コードにバグがあったならそのエラーを再現をするテストを書いてからコードの改修をする、とか
      • 監視 ... コトが起きたときのアラート。コトが起きるとあれなのでその事前のアラート。アラートがあがっても何もしないのならその監視は不要そう
  • 普段意識する必要があることを減らす
    • リーンに働く(?) ... 例えば、「この案件の担当は?」「この機能で質問があるんだけど?」 -> その人しかできない仕事を作らない。であるなら人を選ばずチームに聞けばいい
    • リーンな運用(?) ... アラートがあがったら対応。監視設定の破棄とかの作業もアラート駆動(?)だとしっくりしそう

良いお年を ?

自社アドネットワークサービスのドッグフーディングはじめてみた

(この記事はAdvent Calendar 2015 - VOYAGE GROUP 10日目の記事になります)


こんにちは!Zucksアドネットワークでエンジニアしてます@karahiyoです。

Zucksアドネットワークはスマートフォン向けのアドネットワークのサービスをしています。 今回はそのサービスのドッグフーディングをはじめてみたという話です。*1

Zucksでは、毎週金曜にチーム全員で1時間ほどのMTGをしています。 開発、営業だいたい全員が揃うMTGで、今週したこと、数字報告、あとはざっくばらんな質問タイムなどがあります。話の内容によっては、必要ならこの場で話をもう少し掘り下げ議論、相談をしたりもします。などなど、チームで会話するための貴重な機会となっています。

MTGの際には、毎回その場で司会者を一人ランダムに決めています。 よくやるランダムな選び方は、"ペンをなげてそのペン先の方向の人を司会者とする"、という方法です。

しかしこの方法だと、毎回ペンをなげてたけどペンが壊れないかという心配がありました。

あとは、"ペン先の方向の人"というのはあいまいさがあったり、MTGの場所や人の配置によってはこの方法がとれない時もあったりなどなど課題がありました。

そこで、司会者をランダムに決めるために、自社アドネットワークが使えるのではないかと思ったのがきっかけでした。

次のような仕組みを考えます。

f:id:karahiyo:20151211124608j:plain

本来のアドネットワークのサービス(図左)とは、広告主が広告をアドネットワークに入稿し、それをアドネットワークがいい感じに出し分けつつメディアに配信するというものです。 それを今回は(図右)、各チームメンバがおのおののクリエイティブを入稿し、で用意した社内用の枠にそれらクリエイティブを配信します。各チームメンバのクリエイティブのどれかひとつがその枠に表示されます。その表示されたクリエイティブのメンバーが今日の司会者となる、と。

この仕組みのためには、アプリを作り、広告表示のSDKを組み込み、クリエイティブを入稿し配信の設定をするということが必要になります。

アプリ開発・SDKの組み込み

スマホアプリは何年か前に興味から触った程度なので多少実装が不安なところがありました。

まずはXcodeのupdateから...

アプリを作ります。

できました。

f:id:karahiyo:20151210233741j:plain

今回は興味からSwiftで作成してみました。

次にSDKを組み込みます。Zucksアドネットワークのサイトをたどって https://ms.zucksadnetwork.com/media/admin/#/login からユーザ登録、アプリケーションの登録をしてSDKを組み込みます。

Bridging-Header.hを作成し、SwiftからObjective-Cのクラスを参照できるようにします。

#import "FluctSDK.h"
#import "FluctBannerView.h"

あとは、ViewControllerで次のように広告を表示させます。

  ad = FluctBannerView()
  ad.setMediaID(MEDIA_ID)
  ad.frame = CGRectMake(0, 0, 320, 50)
  self.view.addSubview(ad)

入稿

クリエイティブを作成して入稿します。

こだわったところは、面白いクリエイティブをつくりたいなぁと。自分のクリエイティブが出て欲しいと思ってもらえるような。(今回はとくにブログ掲載のこともちょっと意識していたので、FBの顔写真、slackのアイコンとかを載せるとなるとそれはそれでまたいくつか確認することが必要だったので避けるなど)

ということで今回は恐れ多いですが二つ名的なものを考えてみましたmmmmmm

f:id:karahiyo:20151210233838j:plain

クリエイティブ一部

f:id:karahiyo:20151211133522p:plain

感想:

週例MTGの司会者を決めるために自社アドノットワークの広告配信を使ったアプリを作成しました。 アプリデベロッパが広告配信のためのSDKを埋め込む作業、チームメンバのクリエイティブをアドネットワークに入稿する作業などドッグフーディングをすることができました。 ドッグフーディングをしよう!みたいな話から始まったものではありませんが、結果、アプリデベロッパーと広告主それぞれのユーザ視点に近い体験ができたと思います。

今後、チームに新しいメンバーが入られた時には、その新しいメンバのクリエイティブを入稿する仕事が出てきます。 これをその新しく入られたメンバー自身でおこなってもらうようにするのはいいかもしれません。広告入稿の作業の練習になりそうです。

今後、チームの面白い文化のひとつになったらなぁと。

次回は @rail44です。お楽しみに!! ?

ユーザーストーリーマッピング

ユーザーストーリーマッピング

小さなチーム、大きな仕事〔完全版〕: 37シグナルズ成功の法則

小さなチーム、大きな仕事〔完全版〕: 37シグナルズ成功の法則

  • 作者: ジェイソン・フリード,デイヴィッド・ハイネマイヤー・ハンソン,黒沢 健二,松永 肇一,美谷 広海,祐佳 ヤング
  • 出版社/メーカー: 早川書房
  • 発売日: 2012/01/11
  • メディア: 単行本
  • 購入: 21人 クリック: 325回
  • この商品を含むブログ (36件) を見る

Team Geek ―Googleのギークたちはいかにしてチームを作るのか

Team Geek ―Googleのギークたちはいかにしてチームを作るのか

*1:思い立ってからまだ金曜を迎えていないので実際に週例MTGではまだ使っていませんmm

Gorを試してみたmemo

github.com

Gorとはlog replayを自動化するためのツールです。go製。

log replayとは、log(httpのトラフィック)をreplay(再生)することで、よりリアルなデータ(プロダクションのトラフィック)をうまいこと使い、より効果時なテスト・確認をしようという考えからなるものです。ざっくりと。

たとえば、プロダクションのトラフィックをステージング環境に転送することでステージング環境にデプロイした実装のテストをしたり、転送するトラフィックの割合を制限しつつ負荷テストをしたり、あるいはtcpdumpのようにトラフィックをファイルに書き出し、tcpreplayのようにそれを再生したりと。

テストがないコードで、テストの回数が制限されているようなiscon5予選のときに使えたのではとgorが気になってたので試してみたメモです。(gorの作者の方がfluetndのアーキテクチャが好きでそれを参考にしているということもあり気になってもいました*1

f:id:karahiyo:20151128174658p:plain

!

Gorでできること

提供しているのはlog replayのための機能です。指定したポートでのtcpトラフィックを取得し、そのトラフィックを指定した環境に転送することができます。その転送については、転送するトラフィックの割合を制限したり、フィルタをかけたり、rewriteしたり。あるいは転送先にファイルを指定することでtcpdumpのようにトラフィックをファイルに書き出し、入力にトラフィックを書き出したファイルを指定することで、tcpreplayのようにトラフィックを再生することもできます。 そこには、さまざまなオプションがあり、ほかのlog replayのツールと比較しかなり細かいところまで手が届くものになっているように見えます。

gor/README.md at master · buger/gor · GitHub

$ gor -h
Version: 0.10.1
Gor is a simple http traffic replication tool written in Go. Its main goal is to replay traffic from production servers to staging and dev environments.
Project page: https://github.com/buger/gor
Author: <Leonid Bugaev> leonsbox@gmail.com
Current Version: 0.10.1

  -cpuprofile="": write cpu profile to file
  -debug=false: Turn on debug output, shows all itercepted traffic. Works only when with `verbose` flag
  -http-allow-header=[]: A regexp to match a specific header against. Requests with non-matching headers will be dropped:
         gor --input-raw :8080 --output-http staging.com --http-allow-header api-version:^v1
  -http-allow-method=[]: Whitelist of HTTP methods to replay. Anything else will be dropped:
        gor --input-raw :8080 --output-http staging.com --http-allow-method GET --http-allow-method OPTIONS
  -http-allow-url=[]: A regexp to match requests against. Filter get matched against full url with domain. Anything else will be dropped:
         gor --input-raw :8080 --output-http staging.com --http-allow-url ^www.
  -http-disallow-header=[]: A regexp to match a specific header against. Requests with matching headers will be dropped:
         gor --input-raw :8080 --output-http staging.com --http-disallow-header "User-Agent: Replayed by Gor"
  -http-disallow-url=[]: A regexp to match requests against. Filter get matched against full url with domain. Anything else will be forwarded:
         gor --input-raw :8080 --output-http staging.com --http-disallow-url ^www.
  -http-header-limiter=[]: Takes a fraction of requests, consistently taking or rejecting a request based on the FNV32-1A hash of a specific header:
         gor --input-raw :8080 --output-http staging.com --http-header-imiter user-id:25%
  -http-original-host=false: Normally gor replaces the Host http header with the host supplied with --output-http.  This option disables that behavior, preserving the original Host header.
  -http-param-limiter=[]: Takes a fraction of requests, consistently taking or rejecting a request based on the FNV32-1A hash of a specific GET param:
         gor --input-raw :8080 --output-http staging.com --http-param-limiter user_id:25%
  -http-rewrite-url=[]: Rewrite the request url based on a mapping:
        gor --input-raw :8080 --output-http staging.com --http-rewrite-url /v1/user/([^\/]+)/ping:/v2/user/$1/ping
  -http-set-header=[]: Inject additional headers to http reqest:
        gor --input-raw :8080 --output-http staging.com --http-set-header 'User-Agent: Gor'
  -http-set-param=[]: Set request url param, if param already exists it will be overwritten:
        gor --input-raw :8080 --output-http staging.com --http-set-param api_key=1
  -input-dummy=[]: Used for testing outputs. Emits 'Get /' request every 1s
  -input-file=[]: Read requests from file:
        gor --input-file ./requests.gor --output-http staging.com
  -input-http=[]: Read requests from HTTP, should be explicitly sent from your application:
        # Listen for http on 9000
        gor --input-http :9000 --output-http staging.com
  -input-raw=[]: Capture traffic from given port (use RAW sockets and require *sudo* access):
        # Capture traffic from 8080 port
        gor --input-raw :8080 --output-http staging.com
  -input-tcp=[]: Used for internal communication between Gor instances. Example:
        # Receive requests from other Gor instances on 28020 port, and redirect output to staging
        gor --input-tcp :28020 --output-http staging.com
  -memprofile="": write memory profile to this file
  -middleware="": Used for modifying traffic using external command
  -output-dummy=[]: Used for testing inputs. Just prints data coming from inputs.
  -output-file=[]: Write incoming requests to file:
        gor --input-raw :80 --output-file ./requests.gor
  -output-http=[]: Forwards incoming requests to given http address.
        # Redirect all incoming requests to staging.com address
        gor --input-raw :80 --output-http http://staging.com
  -output-http-elasticsearch="": Send request and response stats to ElasticSearch:
        gor --input-raw :8080 --output-http staging.com --output-http-elasticsearch 'es_host:api_port/index_name'
  -output-http-header=[]: WARNING: `--output-http-header` DEPRECATED, use `--http-set-header` instead
  -output-http-header-filter=[]: WARNING: `--output-http-header-filter` DEPRECATED, use `--http-allow-header` instead
  -output-http-header-hash-filter=[]: WARNING: `output-http-header-hash-filter` DEPRECATED, use `--http-header-hash-limiter` instead
  -output-http-method=[]: WARNING: `--output-http-method` DEPRECATED, use `--http-allow-method` instead
  -output-http-redirects=0: Enable how often redirects should be followed.
  -output-http-rewrite-url=[]: WARNING: `--output-http-rewrite-url` DEPRECATED, use `--http-rewrite-url` instead
  -output-http-stats=false: Report http output queue stats to console every 5 seconds.
  -output-http-timeout=0: Specify HTTP request/response timeout. By default 5s. Example: --output-http-timeout 30s
  -output-http-url-regexp=[]: WARNING: `--output-http-url-regexp` DEPRECATED, use `--http-allow-url` instead
  -output-http-workers=0: Gor uses dynamic worker scaling by default.  Enter a number to run a set number of workers.
  -output-tcp=[]: Used for internal communication between Gor instances. Example:
        # Listen for requests on 80 port and forward them to other Gor instance on 28020 port
        gor --input-raw :80 --output-tcp replay.local:28020
  -output-tcp-stats=false: Report TCP output queue stats to console every 5 seconds.
  -split-output=false: By default each output gets same traffic. If set to `true` it splits traffic equally among all outputs.
  -stats=false: Turn on queue stats output
  -verbose=false: Turn on more verbose output

install

$ wget https://github.com/buger/gor/releases/download/v0.10.1/gor_0.10.1_x64.tar.gz
$ tar zxvf gor_0.10.1_x64.tar.gz
$ mv ./gor /usr/local/bin/

run

サンプルのアプリケーションのコードはgistにあげてあります。

$ sudo setcap CAP_NET_RAW=ep gor # 必要なら
$ go run server1.go
$ go run server2.go
$ gor --input-raw :8081 -output-http :8082
$ http localhost:8081
HTTP/1.1 200 OK
Content-Length: 7
Content-Type: text/plain; charset=utf-8
Date: Wed, 25 Nov 2015 13:37:42 GMT

server1

ngrepでみてみます

$ sudo ngrep -d lo -W byline port 8081
interface: lo (127.0.0.0/255.0.0.0)
filter: ( port 8081 ) and (ip or ip6)
######
T 127.0.0.1:45925 -> 127.0.0.1:8081 [AP]
GET / HTTP/1.1.
Host: localhost:8081.
Connection: keep-alive.
Accept-Encoding: gzip, deflate.
Accept: */*.
User-Agent: HTTPie/0.9.2.
.

##
T 127.0.0.1:8081 -> 127.0.0.1:45925 [AP]
HTTP/1.1 200 OK.
Date: Wed, 25 Nov 2015 13:37:42 GMT.
Content-Length: 7.
Content-Type: text/plain; charset=utf-8.
.
server1
####

転送されています?

$ sudo ngrep -d lo -W byline port 8082
interface: lo (127.0.0.0/255.0.0.0)
filter: ( port 8082 ) and (ip or ip6)
#
T 127.0.0.1:51706 -> 127.0.0.1:8082 [AP]
GET / HTTP/1.1.
Host: :8082.
Connection: keep-alive.
Accept-Encoding: gzip, deflate.
Accept: */*.
User-Agent: HTTPie/0.9.2.
.

#
T 127.0.0.1:8082 -> 127.0.0.1:51706 [AP]
HTTP/1.1 200 OK.
Date: Wed, 25 Nov 2015 13:40:49 GMT.
Content-Length: 7.
Content-Type: text/plain; charset=utf-8.
.
server2
#

header情報を指定してみます

User-Agent、Cookie、リファラを指定してみます

$ http localhost:8081 User-Agent:"Mozilla/5.0 (Linux; Android 4.3; Ne
xus 7 Build/JSS15Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2307.2 Safari/537.36" '
Cookie:uuid=gor-test-12345' Referer:http://full.com.bo
HTTP/1.1 200 OK
Content-Length: 7
Content-Type: text/plain; charset=utf-8
Date: Sat, 28 Nov 2015 08:27:37 GMT

server1

UA、Cookie、リファラも問題なく転送できてます

$ sudo ngrep -d lo -W byline port 8082
interface: lo (127.0.0.0/255.0.0.0)
filter: ( port 8082 ) and (ip or ip6)
#
T 127.0.0.1:47130 -> 127.0.0.1:8082 [AP]
GET / HTTP/1.1.
Host: :8082.
Accept-Encoding: gzip, deflate.
Accept: */*.
User-Agent: Mozilla/5.0 (Linux; Android 4.3; Nexus 7 Build/JSS15Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2307.2 Safari/537.36.
Connection: keep-alive.
Cookie: uuid=gor-test-12345.
Referer: http://full.com.bo.
.

##
T 127.0.0.1:8082 -> 127.0.0.1:47130 [AP]
HTTP/1.1 200 OK.
Date: Sat, 28 Nov 2015 08:27:37 GMT.
Content-Length: 7.
Content-Type: text/plain; charset=utf-8.
.
server2
#

複数のoutput先を指定

ここらへんの転送先の設定をレゴのように組み合わせるのはfluentd由来ぽい

$ gor --input-raw :8081 --output-http :8082 --output-http :8083 --output-http :8084

リクエストをファイルに書き出し再送する

$ gor --input-raw :8081 --output-file gor-request.log
$ gor --input-file gor-request.log -output-http :8082

gor-request.logには次のような文字列が書き出されます 区切り文字のセンス???

1 f07d3261aafa4a8e7056bd812fb58c6e0ea3e978 1448459937338780583
GET / HTTP/1.1
Host: localhost:8081
Connection: keep-alive
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: HTTPie/0.9.2

???
2 f07d3261aafa4a8e7056bd812fb58c6e0ea3e978 395185
HTTP/1.1 200 OK
Date: Wed, 25 Nov 2015 13:58:57 GMT
Content-Length: 7
Content-Type: text/plain; charset=utf-8

server1
???

ファイルに書き出したリクエストのログからリクエストを再生する

$ gor --input-file "gor-request2.log|200%" -output-http :8082

mysqlのポートを指定してcatch???

package main

import (
        "database/sql"
        _ "github.com/go-sql-driver/mysql"
)

func main() {
        db, err := sql.Open("mysql", "karahiyo@/test")
        if err != nil {
                panic(err.Error())
        }
        defer db.Close()

        db.QueryRow("SELECT 1").Scan(&one)
}

とれては...いますね

1 12aaebef061677e56df3e983c639fd437c9c2808 1448466432628463498
SELECT @@max_allowed_packet
???
2 12aaebef061677e56df3e983c639fd437c9c2808 175777
*def@@max_allowed_packet
                        �1048576�
???
1 7cebd7122df7e346ebcb48bc3236b0e6e38bdc63 1448466432628711332
        SELECT 1
???
2 7cebd7122df7e346ebcb48bc3236b0e6e38bdc63 101965
def1
    ��1�
???

!

他にも、fluetndのout_execみたいな機能がmiddlewareという形であるようです。

f:id:karahiyo:20151128181359p:plain


!

log replayができるほかのツール

!

感想

ローカルでのテストやステージング環境で確認できることと、本番系で確認できることではたしかに得られるものに大きな差があると思います。その差を少しでも小さくするためにgorがつくられたようです。

実際にはサービスでは予期していない様々なことが起きます。どれだけ事前にテストをしたとしてもカバーするのは難しく、事は起こるときには起こるものです。 できそうなことは例えば、起き得る事を考え、その影響範囲を確認し、必要ならその影響範囲をより少なくするための方法を考え、対応し、そしてリカバリ手順を確認したうえで、えいやと本番系で確認するという方法があります。1リリース1機能の方針など。ほかには、auto scalingな構成のサービスでならリリース先のサーバを数台に限定してデプロイするなどの手段もあるかと思います。 サービスに直接利益があるわけではないけど安定したサービスのために無視できないそれら心配事が、gorなどを使った仕組みによって意識しなければいけないことが減るのならそれはいい話だなと思いました。

惑星間レプリケーションとRDSのレプリケーション

RDSのMultiAZでは、データベースの障害時の影響を最小化するために同期レプリケーションを行っています。一方、リードレプリカであるスレーブとのレプリケーションは非同期で行っています。
非同期レプリケーションでは、"パケット通信時間 + スレーブでの更新反映の時間 + α"分のレプリケーション遅延が発生します。RDSでは、データセンタ間を高速な回線でつなぐことでネットワーク通信を高速化し遅延を減らしています、が遅延がなくなるというわけではなく、更新頻度、更新処理をスレーブに反映させるのにかかる時間によっては、遅延は常に発生していますし、大きな遅延にもなりえます。
そして、その遅延が発生しうる仕組み上、マスターとスレーブ間でどの時点までの更新が反映されているかは異なりうるため、その更新のズレがあるときに特定の操作が行われた場合発生しうるレプリケーションエラーがいくつかあります。

今回話すこと

RDSのレプリケーション。とくに、レプリケーションエラーまわりについて(mysql 5.5)

話さないこと

惑星間レプリケーション



!


(リードレプリカであるスレーブの状態を確認するには、"SHOW SLAVE STATUS"コマンドが使えます)

mysql> SHOW SLAVE STATUS\G


f:id:karahiyo:20151019214553g:plain



Errno:1062 レプリケーションエラー

スレーブでバイナリログの反映処理に失敗した場合に発生し、これによってSQLスレッドが停止し、そのスレーブとマスタ間のレプリケーションが停止します。発生しうるケースは、スレーブ側を直接更新した場合、STATEMENTベースのレプリケーションで非決定性の更新クエリを実行した場合、長いトランザクション、バイナリログの不整合(例えば、Multi-AZ設定のマスタのフェイルオーバを原因とした)など。

以下は、リードレプリカで"duplicate entry"エラーが発生しているときの、"SHOW SLAVE STATUS"の出力一部。

mysql> show slave status\G
*************************** 1. row ***************************
   Last_Errno: 1062
   Last_Error: Error 'Duplicate entry '123456789-2101-01-01 0:00:00' for key 'PRIMARY'' on query. Default database: 'test'. Query: 'INSERT INTO ...'

参考:

応急処置

エラーの内容を確認し、レプリケーションエラーをスキップします。それには、"CALL mysql.rds_skip_repl_error" 手続きが使えます。mysqlでいう、SQL_SLAVE_SKIP_COUNTERにあたります。
複数のレプリケーションエラーがある場合、最初のエラー(Last_Error)のみスキップします。

mysql> show slave status\G
*************************** 1. row ***************************
   Slave_IO_State: Waiting for master to send event
       Last_Errno: 1062
       Last_Error: Error 'Duplicate entry '123456789-2101-01-01 0:00:00' for key 'PRIMARY'' on query. Default database: 'test'. Query: 'INSERT INTO ...'
mysql> call mysql.rds_skip_repl_error;
mysql> show slave status\G


参考:

予防

1. バイナリログを同期する

sync_binlog =1
innodb_support_xa=1
innodb_flush_logs_at_trx_commit=1

参考:


ちなみに、RDSのmasterのbinlog_formatはMixedから変更できません。

現時点では、Amazon RDS for MySQL は行ベースのレプリケーションへの明示的な変更をサポートしていません。

よくある質問 - Amazon RDS(リレーショナルデータベースサービス Amazon Relational Database Service) | アマゾン ウェブ サービス(AWS 日本語)

!

Errno:1236 レプリケーションIOエラー

スレーブがマスタに存在しないバイナリログの位置を指定しています、というエラーです。IOスレッドが停止し、結果そのスレーブとマスタ間のレプリケーションが停止します。
発生しうるケースは、スレーブ側でバイナリログの位置の指定を間違えた場合、マスタ側でバイナリログ情報を破棄してしまった場合、その他バイナリログの不整合(たとえばMulti-AZ設定のマスタのフェイルオーバを原因とした)などが発生した場合に発生する可能性があります。

以下は、リードレプリカでIOエラーが発生しているときの、"SHOW SLAVE STATUS"の出力一部。

mysql> show slave status\G
*************************** 1. row ***************************
           Slave_IO_State:
          Master_Log_File: mysql-bin-changelog.012345
    Relay_Master_Log_File: mysql-bin-changelog.012345
            Last_IO_Errno: 1236
            Last_IO_Error: Got fatal error 1236 from master when reading data from binary log: 'Client requested master to start replication from impossible position; the first event 'mysql-bin-changelog.013406' at 1219393, the last event read from '/rdsdbdata/log/binlog/mysql-bin-changelog.012345' at 4, the last byte read from '/rdsdbdata/log/binlog/mysql-bin-changelog.012345' at 4.'
応急処置

レプリケーションマスタのログ位置を、マスタ上にある次のバイナリログの開始位置に変更します。これには、"mysql.rds_next_master_log"手続きが使えます。

CALL mysql.rds_next_master_log(curr_master_log);

mysqlでいう`CHANGE MASTER TO`構文にあたります

mysql> show slave status\G
*************************** 1. row ***************************
           Slave_IO_State:
          Master_Log_File: mysql-bin-changelog.012345
    Relay_Master_Log_File: mysql-bin-changelog.012345
            Last_IO_Errno: 1236
            Last_IO_Error: Got fatal error 1236 from master when reading data from binary log: 'Client requested master to start replication from impossible position; the first event 'mysql-bin-changelog.013406' at 1219393, the last event read from '/rdsdbdata/log/binlog/mysql-bin-changelog.012345' at 4, the last byte read from '/rdsdbdata/log/binlog/mysql-bin-changelog.012345' at 4.'
mysql> call mysql.rds_next_master_log(12345);

+-------------------------------------+
| Message                             |
+-------------------------------------+
| Statement in error has been skipped |
+-------------------------------------+
1 row in set (0.01 sec)

+---------------------------+
| Message                   |
+---------------------------+
| Slave is running normally |
+---------------------------+
1 row in set (2.01 sec)
mysql> show slave status\G

参考:

予防

1. バイナリログを同期する

sync_binlog =1
innodb_support_xa=1
innodb_flush_logs_at_trx_commit=1

2. レプリケーションエラーが発生しうる作業のときだけレプリケーションを一時的に止める

call mysql.rds_start_replacation;
call mysql.rds_stop_replication;

参考:

その他のレプリケーションエラーの場合

たとえば、リードレプリカを新しく作り直す。


!



まとめ

  • レプリケーションIOエラー 1062,1236 エラーについてと、その応急処置、予防
  • レプリケーションエラーが発生するとレプリケーションは停止しますが、その原因のIOスレッドもしくはSQLスレッドの問題を解消すればレプリケーションは再開します
  • 一番ツヨいのはリードレプリカを新しく作り直す?

Gaucheでベンチマーク

Gaucheでベンチマークするためのマクロを組んでみました。github.com

ベンチマーク取るならgauche.timeかsrfi-19でだいたい事足りるはずなんですが、

  • ベンチマークのためのcounterについて、実コードを書く上であまり意識したくなかった
    • ベンチマークするまでに必要な手順を減らしたい
    • 実コードの広いスコープにcounterが見えてると気になる
  • ベンチマークの関数には、対象の式の実行時間を計測した上で返り値としてはその対象の式の結果を返して欲しい場面があった
    • 例えば、
gosh> (bench (+ 1 1))
<実行時間の出力>
2 <= 式の結果を返す

という理由から書きましたmm


コード

  (define-syntax bench
    (syntax-rules ()
                  ((_ exprs) (bench-with-msg exprs (quote exprs)))
                  ((_ msg exprs) (bench-with-msg exprs msg))))

  (define-macro (bench-with-msg exprs msg)
                (let1 counter (gensym)
                      `(let1 ,counter (make <real-time-counter>)
                             (dynamic-wind
                               (lambda () (time-counter-start! ,counter))
                               (lambda () ,exprs)
                               (lambda () (begin
                                            (time-counter-stop! ,counter)
                                            (print "** " ,msg ": " (time-counter-value ,counter))))))))

だいたい動く(はず)

gosh> (bench (+ 1 1))
** (+ 1 1): 5.0e-6
2
gosh> (bench (sys-sleep 1))
** (sys-sleep 1): 1.000249
0
gosh> (bench "with msg" (+ 1 1))
** with msg: 4.0e-6
2
gosh> (bench "2?" (begin
              (sys-sleep 1)
              (bench "1?" (sys-sleep 1))))
** 1?: 1.002626
** 2?: 2.007946
0
gosh> (dotimes (i 3) (bench "1?" (sys-sleep 1)))
** 1?: 1.005189
** 1?: 1.004585
** 1?: 1.00527
#t

(let1 counter "it's a dummy counter"
      (bench "variable capture ok?" (print counter)))
it's a dummy counter
** variable capture ok?: 2.6e-6
#<undef>

これで、こんな感じにベンチできます。

(let* ((a (bench "ベンチマーク取りたくなる何かしらの重い処理1"))
       (b (bench "ベンチマーク取りたくなる何かしらの重い処理2")))
  (bench "a,bの結果を使って何か処理する式"))
** ベンチマーク取りたくなる何かしらの重い処理1: 4.0e-6
** ベンチマーク取りたくなる何かしらの重い処理2: 2.0e-6
** a,bの結果を使って何か処理する式: 2.0e-6
"a,bの結果を使って何か処理する式"

© karahiyo