harukin721

主に学習記録

MySQLのレプリケーション

複数のMySQL サーバーのデータを同期するための仕組み

MySQLレプリケーションの仕組み

MySQLレプリケーション構成

1. マスターとスレーブに同一のデータを用意
2. マスターに更新SQLを実行
3. マスターからスレーブに更新SQLの内容を転送
4. スレーブが転送された更新SQLの内容をリプレイ

「結果整合性モデル」でレプリケーションは動作する。 「結果整合性モデル」= 一貫性があるとは言い切れない。

cloud.google.com

特性 非同期レプリケーション グループレプリケーション 備考
スレープの性能や負荷がマスターに影響を及ぼさない Yes No バッチ専用スレーブ、バックアップ専用スレープなどは非同期レプリケーションが有利
自動フェイルオーバー No Yes アプリケーションからの接続先まで自動で切り替えるにはMySQL Routerとの連携が必要
更新のレイテンシー 低い 高い ただし、どちらもレプリケーション遅延は起きうる
系全体としてのデータ保護の堅牢性 オペレーションに依存 高い 正しいオペレーションのもとでは両者の堅牢性はほぼ同等

非同期レプリケーションのデータ同期

マスター

クライアントから受け取った更新SQLの内容をマスター上のデータに反映して、バイナリログ(ビンログ)ファイルに記録(バイナリログイベントと呼ぶ)

Binlog Dump (Binlog Sender)と呼ばれるスレッドが待機しており、このスレッドがバイナリログの更新を検知してスレーブにバイナリログイベントを送信する。マスターと接続しているスレーブに1対1で常駐。

スレーブ

Binlog Dump スレッドに対応する形でスレーブの I/O(Binlog Receiver)スレッドが起動している。 この2つのスレッドがTCP通信でコネクションからバイナリログイベントが送信されてくると、I/Oスレッドが受信してリレーログと呼ばれるファイルに記録する。

また、リレーログの更新を待ち受ける SQL(Binlog Applier)スレッドが常駐している。 これがリレーログからバイナリログイベントを取り出し、スレーブ上のデータに対してリプレイすることで、マスターとスレープのデータが同一となる。

SQLスレッドは slave_parallel_workers オプションによって、リレーログに記録されたバイナリログイベントを並列で適用できる。 2つ以上に設定された状態をマルチスレッドスレーブと呼ぶ。

バイナリログフォーマット

MySQLはバイナリログイベントを「マスターが受け付けた更新SQLそのもの」として記録する方式(binlog_format = STATEMENT)がデフォルトだった。 -> 1行のSQLとして記録されるためコンパクトだけど、更新前の行の情報をいっさい保持しないため、万一「マスターとスレープでデータに不合が発生」しており「マスターで実行された更新 SQLとスレープでリプレイされたバイナリログイベントの更新した行の数が違って」いてもそれを検出できない。

これに対して、最近はバイナリログイベントを「マスターが受け付けた更新 SQLで更新された行の情報」とするのがデフォルト(binlog_format = ROW)となっている ★これが8系になるタイミングでやりたいこと -> 更新された行の情報が記録されるため、100万行を更新するステートメントであれば100万のバイナリログイベントが作成される。更新前の行を1行ずつ検索して対象の行を特定するため、不整合があった場合はSQLスレッドにエラーを発生させられる。

「マスターとスレーブでカラムの数やデータ型が違ってもレプリケーションが組める」という柔軟さがある

1行の単純なINSERTステートメントでは大きな違いはないが、UPDATE、DELETE や INSERT ...SELECT... 形式で 1ステートメントが複数行を更新する場合はファイルサイズなどに大きな違いが生まれる。

※ どちらにしても、トランザクションによる保護は行われるため、分割されても一貫性には問題はない。

ROWはDML (INSERT/UPDATE/DELETE など)のみ有効。DDL (CREATETABLEやALTER TABLE など)は常にSTATEMENTで記録。

参考

www.shoeisha.co.jp

weblabo.oscasierra.net

2023年

12/27に仕事納めをして、12/28に新幹線に揺られて帰省した。

2023年を軽く振り返ってみる。

はじめてがいっぱい

  • はじめてシステムメンテナンスを実施した

  • はじめて登壇した(自社開催のものだけど)

speakerdeck.com

  • はじめて社外の人と交流した

  • はじめてテックカンファレンスに参加した

harukin721.hatenablog.com

  • はじめてテックブログ書いた

tech.pepabo.com

  • はじめてソフトウェア( Ingress NGINX Controller ) のバージョンアップデートをした
  • はじめて K8sクラスタ作成をした
  • はじめて K8s(EKS)のクラスターアップデートをした ...etc

他にもあるけど、ざっとこんな感じで「はじめて」の経験をいっぱいさせていただいた。Amazon RDS のブルー/グリーンデプロイ の検証など、おもしろいこともいっぱいできている。 大きめタスクに対しては、見積もりをたてて、チェックリスト形式で分割して、期限を設定しつつ細かくタスクを区切って進めることができるようになってきた。

「はじめて」のことに対しても、わからないなりになんとか進めることはできたのかなとは思っている。

課題

ここまでやったことをつらつら書いたけど、2023年は課題がいっぱい見つかった。

いっぱい課題があるけど、ここでは特に焦点を当てたいものを挙げようと思う。

業務

システムの全体的な理解が浅い。

はじめてソフトウェア( Ingress NGINX Controller ) のバージョンアップデートをした

これを上で書いていたけど、アップデートの際に「自分が運用しているシステムの構成を理解する」ことが不十分だったので、意図していないタイミングでproduction環境に変更を適用させてしまうことがあった。

幸運なことに、事前にstaging環境で検証をしていたのと production環境との大きな差分がなかったので、障害を引き起こすということはなかった。

まだまだ自分が運用するシステムの理解が十分とは到底言えない。このままだとトラブルシューティングも十分にできないと思う。

サービスのドメイン知識や使用技術について、もっともっと深く理解する必要があるので、意識してやっていきたい。

業務外

自分の中で決めていた期限通りに目標が達成できていない。

基本情報の科目Bに合格できるぐらいにはアルゴリズムを学ぶと決めていたけど、業務に関連するものにほとんど時間を使ってしまい、うまく進めることができなかった。

空いた時間にこっちの勉強もしよう。というぐらいの気持ちでいたのがよくなかった。

単に意志だけに頼るのではなく、なにか強制力を持たせてやっていかないと後回しになる...。

勉強を始める前に、資格の申し込みを済ませたり、あるいは特定の目標を達成しなかった場合にはネガティブな影響があるようにしたりするといいかなと思っているのでそうしていこうと思う。

私生活

社会人になってから、はじめて体調を崩した。コロナになった。身体がなんとなくだるいと感じることも多かった気がする。

体調管理にも気を遣うようにする。

2024 やる

目の前の仕事だけじゃなくて、それに関連する・しないに関わらずに知識の領域を広げることを意識したい。

  • 単に意志だけに頼るのではなく、強制力を持たせてやる
  • 自分が運用しているシステムの構成を理解する
  • トラブルシューティングをもっとできるようにする
  • 自信の持てる分野を見つける(業務内外でやったことでも十分に自信が持てない、理解が足りていないのだろう )

2023年はあんまりよくなかったので、2024年はよかったと思えるようにやっていく。

Go言語でソケットプログラミング

「ソケット」が見えたので買った。積読がすごい勢いで増えてる。最初から全部読む必要もないし、いいかなと思っている。

Goならわかるシステムプログラミング 第2版www.lambdanote.com

GitHub Copilot にも手伝ってもらった。ありがとうありがとう。

第6章 TCPソケットとHTTPの実装

ソケットを繋いでからできることは、書き込み、読み込み、クローズのみでシンプル。ソケットは、アドレスとポート番号がわかればローカルのコンピューター内だけではなく外部のコンピュータとも通信が行える。

HTTP はどういった仕組みで下位のレイヤーを使っているか。それはほとんどの OS で、アプリケーション層からトランスポート層プロトコルを利用するときの API としてソケットを利用する。TLS はソケットと HTTP の間に入る。通常ブラウザを利用した HTTP 通信では、サーバーの TCP ポート80番に対して、ソケットを使ったプロセス間通信を行う。

ソケット通信の基本構造

基本構成

  • サーバー : ソケットを開いて待ち受ける
  • クライアント : 開いているソケットに接続し、通信を行う

Go言語の場合

  • サーバーが呼ぶのは Listen() メソッド
  • クライアントが呼ぶのは Dial() メソッド

Go言語でHTTPサーバーを実装する

Go言語に組み込まれている TCPの機能(net.Conn) だけを使って HTTP 通信を実現してみる。GitHub Copilot にコメント入れてもらった。

  • listenerは、特定のネットワークインターフェースとポートでの接続を待ち受けるためのソケットを表す
  • net.Listen("tcp", "localhost: 8888") は、指定したアドレスとポートで新しいTCPリスナーを作成、サーバーは新しい接続を待ち受ける状態になる
  • listener.Accept()は、新しいクライアントからの接続を待ち、接続が確立されるとその接続を表す net.Conn オブジェクトを返す。これを通じて、クライアントとの間でデータの送受信が行われる
package main

import (
    "bufio"
    "fmt"
    "io"
    "net"
    "net/http"
    "net/http/httputil"
    "strings"
)

func main() {
    // TCPネットワーク上のlocalhost:8888でリッスンを開始
    listener, err := net.Listen("tcp", "localhost: 8888")
    if err != nil {
        panic(err) // エラーが発生した場合はpanicを発生
    }
    fmt.Println("Server is running at localhost: 8888")
    for {
        // 新しい接続を受け入れる
        conn, err := listener.Accept()
        if err != nil {
            panic(err) // エラーが発生した場合はpanicを発生
        }
        go func() { // 新しいゴルーチンを起動
            fmt.Printf("Accept %v\n", conn.RemoteAddr()) // 接続のリモートアドレスをログに出力
            // HTTPリクエストを読み取る
            request, err := http.ReadRequest(
                bufio.NewReader(conn))
            if err != nil {
                panic(err) // エラーが発生した場合はpanicを発生
            }
            // リクエストをダンプしてログに出力
            dump, err := httputil.DumpRequest(request, true)
            if err != nil {
                panic(err) // エラーが発生した場合はpanicを発生
            }
            fmt.Println(string(dump))
            // HTTPレスポンスを作成
            response := http.Response{
                StatusCode: 200, // ステータスコード200(成功)
                ProtoMajor: 1,   // HTTP/1.0を使用
                ProtoMinor: 0,   // HTTP/1.0を使用
                Body: io.NopCloser(
                    strings.NewReader("Hello World\n")), // ボディに"Hello World\n"を含む
            }
            response.Write(conn) // レスポンスを接続に書き込む
            conn.Close()         // 接続を閉じる
        }()
    }
}

サーバーを起動して確認する

% go run server.go 
Server is running at localhost: 8888

# 以下で curl 実行したら出た
Accept 127.0.0.1:64219
GET / HTTP/1.1
Host: localhost:8888
Accept: */*
User-Agent: curl/8.1.2


% curl -v localhost:8888
*   Trying 127.0.0.1:8888...
* Connected to localhost (127.0.0.1) port 8888 (#0)
> GET / HTTP/1.1
> Host: localhost:8888
> User-Agent: curl/8.1.2
> Accept: */*
> 
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< 
Hello World
* Closing connection 0

この処理は新しい接続が来るたびに繰り返されるので、このサーバーは同時に複数の接続を処理することができる。

はじめて1人でテックブログを書いた

🎄GMOペパボエンジニア Advent Calendar 2023 の15日目の記事として、はじめてテックブログを書きました。

レビューしていただいた方々、ありがとうございました。

tech.pepabo.com

前職ではWantedlyの記事は3つぐらい書いてたけど、削除なのかアーカイブなのかわからないけどもう見れなくなっていると最近知った。

2023

1年3ヶ月前にペパボに入社して、この記事を書いてからもう1年経っている。早い。雑に今年を振り返ってみるとあんまりよくなかった気がしているので、どうすれば来年は振り返ったときによかったと思えるようになるかを今から考えたい。

harukin721.hatenablog.com

ざっくり MySQL 勉強した

MySQLアーキテクチャ

  • ストレージエンジンによってデータを格納するレイヤを仮想化
  • シングルプロセス / マルチスレッド
  • 1つのコネクションに対して1つのスレッドが対応
  • すべての更新系クエリを保存するバイナリログ
  • 2種類のスレッドで実装された非同期型レプリケーション ...etc

ストレージエンジン

データの格納や抽出、排他制御トランザクション制側に関するローレベルな実装を仮想化する仕組み。ユーザーがクライアントから SQL文を実行すると、テーブルへのアクセスはすべてストレージエンジンAPIを通じて行われる。

シングルプロセス / マルチスレッドであり、クライアントからの各々の接続に対して、1つのスレッドを作成してレスポンスを返す。MySQL 採用のマルチスレッドでは、スレッド同士は同じメモリを共有しているため連携が高速、同じメモリをアクセスする可能性がある場合には排他処理を必要となる。

InnoDB

MySQL の標準で組み込まれていて、非常に優れた性能と必要十分な機能を備えている。

MVCC ( MultiVersion Concurrency Control ) によって高い同時実行性能を備えており、行単位で排他処理を行うので同じテーブルを複数のセッションから同時にすることも可能。バッファプール(メモリ領域)には、行データの他にインデックスがキャッシュされる。100GB級のメモリをバッファプールに割り当てて、テーブルアクセスの性能向上を図る。

MySQL レプリケーション

MySQL に実装されているものは非同期型。

アプリケーションからの更新を受け付けるマスターと、マスターから更新を受け取るレプリカによって構成される。マスターは更新内容を連続的にレプリカへ転送し、レプリカはその差分を元にテーブルを更新し続ける。

1つのマスターへ行われた更新を同時に複数のレプリカへ転送することも可能。この特性を利用して、参照系のクエリをレプリカから行うことにより負荷分散を行う使い方がよく利用される。Redash の参照用など。

クエリキャッシュ

SELECT を実行すると SELECT文を Key,結果を Value としてメモリ上にキャッシュする。そのため同じ SELECT文であればキャッシュから結果が返される。Key は SELECT文のハッシュ値を計算して利用するため、一字一句まったく同じでないと認識されない。

SELECT の対象になるテーブルが更新された場合、同じ SELECT文によって異なる結果が返ることになるので、そのテーブルからの SELECT を含むキャッシュエントリは無効になる。テーブルの更新が多い場合にはクエリキャッシュはまったく効果がなく、むしろオーバーヘッドとなることもある。参照系のクエリが多い Webサービスでは効果を発揮する。

バイナリログ

ミッションクリティカルシステムにおいて役立つ機能が利用できる。

MySQLトランザクション

トランザクションはデータベース管理システムの操作の基本単位。トランザクションの管理やデータの格納および抽出の処理は、すべてストレージエンジンに任せられている。MySQL においては InnoDB

ACID準拠

  • Atomicity (原始性)
    • トランザクションの実行結果は、すべての操作が成功するか失敗するかという2通りだけしか存在してはいけない。部分的に成功することはあり得ない。
  • Consistency (一貫性)
    • トランザクションを実行した結果、それがデータベースにとって論理的に正しいものでなければならない。
  • Isolation (分離性)
  • Durability (永続性)

参考

gihyo.jp