Galera Cluster 状態監視に役立つ MariaDB システム変数

2025-05-27

Galera Clusterの基本的な動作に関する変数

  • wsrep_provider_options: Galera Providerライブラリの動作を制御するためのオプションを指定する変数です。例えば、セグメント化されたネットワーク環境での動作を調整するためのオプションなどがあります。
  • wsrep_provider: Galera Providerライブラリのパスを指定する変数です。通常、MariaDBのインストール時に自動的に設定されます。
  • wsrep_node_address: 現在のノードがリッスンするアドレス(IPアドレスとポート番号)を指定する変数です。他のノードがこのノードに接続するために使用します。通常、0.0.0.0:4567 のようにすべてのインターフェースでリッスンするように設定します。
  • wsrep_node_name: 現在のノードの名前を指定する変数です。ログやステータス情報でノードを識別するために使用されます。
  • wsrep_cluster_address: クラスター内の他のノードのアドレス(IPアドレスとポート番号)を指定する変数です。新しいノードがクラスターに参加する際に、このリストを使用して既存のノードを発見します。通常、クラスター内のすべてのノードのアドレスをカンマ区切りで指定します。例:gcomm://192.168.1.101:4567,192.168.1.102:4567,192.168.1.103:4567
  • wsrep_cluster_name: Galera Clusterの名前を指定する変数です。クラスターに参加するすべてのノードで同じ名前を設定する必要があります。
  • wsrep_on: Galera Clusterが有効になっているかどうかを示す変数です。
    • ON (または 1): Galera Clusterが有効です。
    • OFF (または 0): Galera Clusterが無効です(通常のMariaDBサーバーとして動作します)。

状態転送(State Snapshot Transfer - SST)に関する変数

新しいノードがクラスターに参加する際や、ノードが長時間オフラインだった後に再参加する際に、他のノードから最新のデータセットをコピーするプロセスが状態転送です。

  • wsrep_sst_receive_address: 状態転送を受け取るノードのアドレスとポート番号を指定する変数です。通常、自動的に設定されます。
  • wsrep_sst_auth: 状態転送を実行するノード間の認証情報を指定する変数です。通常、ユーザー名とパスワードをカンマ区切りで指定します(例:sst_user:password)。
  • wsrep_sst_method: 状態転送に使用する方法を指定する変数です。一般的な方法には、rsyncmysqldumpxtrabackup などがあります。選択する方法によって、パフォーマンスや要件が異なります。

書き込みセットの制御に関する変数

Galera Clusterは、トランザクションを「書き込みセット」として他のノードにレプリケートします。

  • wsrep_max_ws_size: レプリケートされる書き込みセットの最大サイズを指定する変数です。大きなトランザクションは分割される可能性があります。
  • wsrep_certify_non_primary: プライマリコンポーネントに属さないノードがトランザクションをコミットする前に証明を行うかどうかを制御する変数です。通常は ON に設定されます。
  • wsrep_slave_threads: レプリケートされた書き込みセットを適用するスレッドの数を指定する変数です。この値を大きくすることで、並列処理が可能になり、レプリケーションの遅延を減らすことができます。

Flow Controlに関する変数

Galera Clusterは、ノード間で負荷のバランスを取り、過負荷を防ぐためにフロー制御メカニズムを使用します。

  • wsrep_flow_control_high: フロー制御が停止されるキューの最大サイズを指定する変数です。
  • wsrep_flow_control_low: フロー制御が開始されるキューの最小サイズを指定する変数です。
  • wsrep_flow_control_interval: フロー制御メッセージを送信する間隔を指定する変数です。
  • wsrep_reject_queries: ノードが同期されていない場合や状態転送中の場合に、クエリを拒否するかどうかを制御する変数です。
  • wsrep_trx_isolation_level: クラスター全体のトランザクション分離レベルを設定する変数です。通常は REPEATABLE-READ が推奨されます。
  • wsrep_sync_wait: トランザクションをコミットする前に、指定された数の他のノードがトランザクションを受信したことを保証するために待機する数を制御する変数です。0 は非同期レプリケーション(推奨されません)、1 は少なくとも1つの他のノードが受信したことを保証します。

これらのシステム変数は、MariaDBの設定ファイル(通常は my.cnf または my.ini)で設定するか、実行時に SET GLOBAL コマンドを使用して動的に変更できます(ただし、一部の変数は再起動が必要な場合があります)。



クラスターへの参加失敗

  • 原因と対策
    • wsrep_cluster_name の不一致
      クラスター内のすべてのノードで wsrep_cluster_name が完全に一致しているか確認してください。大文字・小文字も区別されます。
    • ネットワークの問題
      ファイアウォールがポート(通常は 4567、4568、および SST 用のポート)をブロックしていないか確認してください。ノード間のネットワーク接続が安定しているかPingなどで確認します。
    • wsrep_cluster_address の設定ミス
      新しいノードの wsrep_cluster_address に、既存の稼働中のノードのアドレスが正しく含まれているか確認してください。IPアドレスやポート番号の誤りがないか注意が必要です。
    • wsrep_node_address の競合
      各ノードの wsrep_node_address が一意であることを確認してください。同じIPアドレスとポート番号が複数のノードで使用されていると、参加に失敗します。
    • wsrep_provider の不一致または破損
      すべてのノードで wsrep_provider のパスが同じであり、ファイルが存在し、正しいバージョンであることを確認してください。
    • 認証の問題 (wsrep_sst_auth)
      SSTメソッドが認証を必要とする場合(例:mysqldump)、新しいノードの wsrep_sst_auth に指定されたユーザー名とパスワードが、状態転送元のノードに存在し、適切な権限を持っているか確認してください。
  • エラー
    新しいノードがクラスターに参加できない。ログに [ERROR] WSREP: failed to join group[ERROR] WSREP: handshake with remote endpoint failed などのエラーが出力される。

状態転送 (SST) の失敗

  • 原因と対策
    • SSTメソッドの不適切な選択
      選択した SST メソッド(rsync, mysqldump, xtrabackup など)が環境に適しているか確認してください。例えば、データベースサイズが大きい場合に mysqldump を使用すると、非常に時間がかかるか失敗する可能性があります。xtrabackup の利用を検討してください。
    • SSTに必要なツールの不足
      選択した SST メソッドに必要なツール(例:rsync, xtrabackup)がすべてのノードにインストールされているか確認してください。
    • ディスク容量の不足
      状態転送先のノードに、転送されるデータ全体を格納するのに十分なディスク容量があるか確認してください。
    • タイムアウト
      ネットワークの状態が悪い場合や、データベースサイズが大きい場合に、SSTがタイムアウトすることがあります。関連するタイムアウト設定(SSTメソッド固有の設定が存在する場合もあります)を確認してください。
    • 権限の問題 (wsrep_sst_auth)
      SSTに必要な認証情報が正しく設定され、状態転送元のノードで認証が成功しているか確認してください。
  • エラー
    新しいノードがクラスターに参加しようとする際に、状態転送が途中で失敗する。ログに SST 関連のエラーメッセージが出力される。

クラスターの不安定性とパフォーマンスの問題

  • 原因と対策
    • ネットワークの遅延と不安定性
      クラスターノード間のネットワーク遅延が大きい、またはパケットロスが多い場合、クラスターの安定性とパフォーマンスに悪影響を与えます。ネットワーク環境を調査し、改善を試みてください。
    • wsrep_slave_threads の不適切な設定
      レプリケーションスレッドの数が少なすぎると、書き込み負荷が高い場合に遅延が発生する可能性があります。CPUコア数などを考慮して、適切な値に調整してください。ただし、値を大きくしすぎるとコンテキストスイッチが増え、逆効果になることもあります。
    • フロー制御 (wsrep_flow_control_interval, wsrep_flow_control_low, wsrep_flow_control_high)
      フロー制御の設定が厳しすぎると、書き込み処理が頻繁に一時停止し、パフォーマンスが低下する可能性があります。これらの変数を調整することで、フロー制御の動作を微調整できます。
    • 大きなトランザクション
      非常に大きなトランザクションは、レプリケーションに時間がかかり、他のノードの動作を妨げる可能性があります。アプリケーション側でトランザクションを小さく分割することを検討してください。wsrep_max_ws_size で書き込みセットの最大サイズを制限することもできます。
    • wsrep_sync_wait の不適切な設定
      wsrep_sync_wait の値を大きくすると、書き込みの整合性は高まりますが、レイテンシが増加します。アプリケーションの要件に合わせて適切な値を設定してください。0 は非同期レプリケーションとなり、データ整合性のリスクが高まるため推奨されません。
    • リソースの枯渇
      ノードのCPU、メモリ、ディスクI/Oなどがボトルネックになっている場合、クラスター全体のパフォーマンスに影響を与えます。各ノードのリソース使用状況を監視し、必要に応じてリソースを増強してください。
  • エラー
    クラスターが頻繁に分割したり、ノードが離脱したりする。書き込み処理が遅い、またはタイムアウトする。

データ不整合

  • 原因と対策
    • 非同期レプリケーション (wsrep_sync_wait = 0)
      wsrep_sync_wait0 に設定されている場合、書き込みがすべてのノードに確実に反映される保証がないため、データ不整合が発生する可能性があります。wsrep_sync_wait >= 1 を設定することを強く推奨します。
    • ネットワークの分断 (Split-Brain)
      一時的なネットワーク障害などにより、クラスターが複数の独立したグループに分かれてしまうことがあります。この状態でそれぞれのグループで書き込みが行われると、データ不整合が発生します。適切なクォーラム設定 (wsrep_desync) や、ネットワークの冗長化などで対策する必要があります。
    • 手動でのデータ変更
      クラスターの動作中に、いずれかのノードで直接SQLコマンドを実行してデータを変更すると、他のノードとの整合性が失われる可能性があります。クラスターを通して整合性のある書き込みを行うようにアプリケーションを設計する必要があります。
  • エラー
    クラスター内のノード間でデータが一致しない。
  • 公式ドキュメントの参照
    MariaDB Galera Cluster の公式ドキュメントには、より詳細な情報やトラブルシューティングの手順が記載されています。困った場合は、公式ドキュメントを参照することをお勧めします。
  • 設定ファイルの確認
    各ノードの MariaDB 設定ファイル (my.cnf または my.ini) を確認し、関連する Galera Cluster のシステム変数が意図したとおりに設定されているか確認してください。
  • ステータス変数の確認
    SHOW STATUS LIKE 'wsrep_%'; コマンドを実行すると、現在のクラスターの状態や各ノードの状況に関する多くの情報を確認できます。例えば、wsrep_local_state_comment でノードの状態を確認したり、wsrep_cluster_size でアクティブなノード数を確認したりできます。
  • ログの確認
    MariaDBのエラーログ(通常は hostname.err)は、問題の原因を特定するための重要な情報源です。エラーメッセージや警告メッセージを注意深く確認してください。


以下に、システム変数の設定や状態を間接的に利用したり、クラスターの動作を意識したプログラミングの例をいくつか示します。

トランザクション分離レベルの確認と設定 (アプリケーション側での考慮)

Galera Clusterでは、すべてのノードでトランザクション分離レベルが一致している必要があります。アプリケーション側で意図しない分離レベルでトランザクションを実行しないように注意する必要があります。

import mysql.connector

config = {
    'user': 'your_user',
    'password': 'your_password',
    'host': 'your_galera_node', # Galeraクラスターのいずれかのノード
    'database': 'your_database'
}

try:
    cnx = mysql.connector.connect(**config)
    cursor = cnx.cursor()

    # 現在のトランザクション分離レベルを確認 (参考)
    cursor.execute("SELECT @@tx_isolation")
    isolation_level = cursor.fetchone()[0]
    print(f"現在のトランザクション分離レベル: {isolation_level}")

    # 必要であれば、トランザクション分離レベルを設定 (通常はサーバー側で設定)
    # cursor.execute("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ")
    # cnx.commit()

    # 通常のデータベース操作
    cursor.execute("SELECT * FROM your_table")
    results = cursor.fetchall()
    for row in results:
        print(row)

    cnx.commit()

except mysql.connector.Error as err:
    print(f"エラー: {err}")

finally:
    if cnx.is_connected():
        cursor.close()
        cnx.close()

この例では、アプリケーションが接続しているノードの現在のトランザクション分離レベルを確認しています。通常、トランザクション分離レベルはサーバー側の設定ファイル (my.cnf) または SET GLOBAL コマンドで設定しますが、アプリケーション側でも意識しておくことが重要です。Galera Clusterでは REPEATABLE-READ が推奨されることが多いです。

デッドロックのリトライ処理 (アプリケーション側での考慮)

Galera Clusterは、同時実行性の高い環境でデッドロックが発生しやすい場合があります。アプリケーション側でデッドロックを検知し、自動的にリトライするロジックを実装することで、ユーザーへの影響を軽減できます。

import mysql.connector
import time

config = {
    'user': 'your_user',
    'password': 'your_password',
    'host': 'your_galera_node',
    'database': 'your_database'
}

MAX_RETRIES = 3
RETRY_DELAY = 1 # 秒

try:
    cnx = mysql.connector.connect(**config)
    cursor = cnx.cursor()

    for attempt in range(MAX_RETRIES):
        try:
            cursor.execute("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
            cursor.execute("UPDATE accounts SET balance = balance + 100 WHERE id = 2")
            cnx.commit()
            print("トランザクション成功")
            break
        except mysql.connector.Error as err:
            if err.errno == 1213: # デッドロックエラーコード
                print(f"デッドロックが発生しました。リトライ中 ({attempt + 1}/{MAX_RETRIES})...")
                time.sleep(RETRY_DELAY)
                cnx.rollback() # ロールバック
            else:
                print(f"予期せぬエラー: {err}")
                break
    else:
        print("トランザクションは指定された回数リトライしましたが失敗しました。")

except mysql.connector.Error as err:
    print(f"接続エラー: {err}")

finally:
    if cnx.is_connected():
        cursor.close()
        cnx.close()

この例では、デッドロックエラー(エラーコード 1213)が発生した場合に、数回リトライを行う処理を実装しています。Galera Clusterのような並行処理環境では、このようなリトライロジックが重要になることがあります。

ノードの状態に応じた読み取り先の制御 (高度な利用)

通常、Galera Clusterはどのノードに読み書きしても整合性が保たれますが、特定の状況下では、読み取り処理を特定のノードに限定したい場合があります。例えば、負荷分散のために読み取り専用のノードを用意するなどです。アプリケーション側で、接続するノードを制御することで、これを実現できます。

import mysql.connector
import random

# Galeraクラスターのノードリスト
read_nodes = ['192.168.1.101', '192.168.1.102']
write_node = '192.168.1.103'

config_read = {
    'user': 'your_read_user',
    'password': 'your_read_password',
    'database': 'your_database'
}

config_write = {
    'user': 'your_write_user',
    'password': 'your_write_password',
    'host': write_node,
    'database': 'your_database'
}

def execute_read_query(query):
    host = random.choice(read_nodes)
    config_read['host'] = host
    try:
        cnx = mysql.connector.connect(**config_read)
        cursor = cnx.cursor()
        cursor.execute(query)
        results = cursor.fetchall()
        cursor.close()
        cnx.close()
        return results
    except mysql.connector.Error as err:
        print(f"読み取りエラー ({host}): {err}")
        return None

def execute_write_query(query):
    try:
        cnx = mysql.connector.connect(**config_write)
        cursor = cnx.cursor()
        cursor.execute(query)
        cnx.commit()
        cursor.close()
        cnx.close()
        return True
    except mysql.connector.Error as err:
        print(f"書き込みエラー ({write_node}): {err}")
        cnx.rollback()
        return False

# 読み取り処理
data = execute_read_query("SELECT * FROM your_table WHERE some_condition = 1")
if data:
    for row in data:
        print(f"読み取りデータ: {row}")

# 書き込み処理
if execute_write_query("INSERT INTO your_table (column1, column2) VALUES ('value1', 'value2')"):
    print("書き込み成功")
else:
    print("書き込み失敗")

この例は、読み取り処理を複数の読み取り専用ノードに分散させ、書き込み処理を特定のノードに集中させるための基本的な考え方を示しています。実際には、ノードの状態を監視し、より動的に接続先を制御する複雑なロジックが必要になる場合があります。

  • 接続管理
    アプリケーションは、接続先のノードがダウンした場合の再接続処理や、負荷分散のための接続管理を適切に行う必要があります。
  • クラスターの知識
    Galera Clusterの動作原理(同期レプリケーション、クォーラム、状態転送など)を理解した上で、アプリケーションを設計することが重要です。
  • システム変数の直接的な操作
    アプリケーションコード内で SET GLOBAL wsrep_on = OFF; のように直接システム変数を変更するべきではありません。これらの変数はサーバーの設定ファイルや管理コマンドを通じて制御されるべきです。


OrchestratorやProxySQLなどのミドルウェアの利用

アプリケーションが直接クラスターのノードに接続するのではなく、OrchestratorやProxySQLのようなミドルウェアを経由することで、より柔軟かつ高度な制御が可能になります。

  • ProxySQL
    高機能なMySQLプロキシです。読み書き分離、ロードバランシング、クエリキャッシュ、ファイアウォールなどの機能を提供します。アプリケーションはProxySQLに接続し、ProxySQLが設定されたルールに基づいてクエリを適切なバックエンドノードに転送します。wsrep_cluster_status などのGalera固有のステータス変数をProxySQLが認識し、それに基づいてルーティングを制御することも可能です。
  • Orchestrator
    トポロジ管理、フェイルオーバー、リカバリなどを自動化するツールです。アプリケーションはOrchestratorが提供するエンドポイントに接続し、Orchestratorが最適なノードへの接続をルーティングします。システム変数の状態を監視し、自動的に構成を変更することも可能です。

プログラミングの観点

  • アプリケーションのコード自体は、クラスターのトポロジの変化やノードの状態を意識する必要が少なくなり、よりシンプルになります。
  • ミドルウェアが提供するAPIや設定インターフェースを利用して、クラスターの動作やルーティングルールを制御します。
  • アプリケーションは、クラスター内の個々のノードではなく、OrchestratorやProxySQLのエンドポイントに接続するように設定を変更します。

例 (ProxySQLを使った読み書き分離)

アプリケーションは常に ProxySQL の同じアドレスに接続しますが、ProxySQL の設定によって SELECT クエリは読み取り専用ノードへ、INSERT/UPDATE/DELETE クエリは書き込み可能なノードへ自動的にルーティングされます。ProxySQL はバックエンドノードの wsrep_local_state_comment などのステータス変数を監視し、書き込み可能なノードを判断します。

Galera Clusterの状態を監視する外部ツールの利用

アプリケーション自体が直接システム変数をポーリングするのではなく、Zabbix、Prometheus + Grafana、Nagios などの監視ツールを利用してクラスターの状態を監視し、必要に応じてアプリケーションの動作を調整する方法です。

プログラミングの観点

  • 例えば、クォーラムが失われた場合に、アプリケーションからの書き込み処理を一時的に停止するなどの対応が考えられます。
  • 取得した情報に基づいて、アプリケーションの接続先を切り替えたり、特定の処理を一時停止したりするロジックを実装します。
  • 監視ツールが提供するAPIを通じて、クラスターの状態(ノードの稼働状況、同期状態、クォーラムの状態など)を取得します。

例 (Prometheus + Grafanaでの監視とアプリケーションへの通知)

Prometheus が各 MariaDB ノードの wsrep_upwsrep_cluster_size などのメトリクスを収集し、Grafana で可視化します。特定の条件(例:アクティブノード数が閾値を下回った場合)が発生した場合に、Alertmanager を経由してアプリケーションに通知を送信する仕組みを構築します。アプリケーションは、この通知を受け取って、安全な動作モードに移行するなどの処理を行います。

MariaDB Connector/J (Java) などのドライバが提供する機能の利用

一部のデータベースドライバは、Galera Clusterの特性を考慮した接続管理機能を提供している場合があります。例えば、MariaDB Connector/J は、自動フェイルオーバーやロードバランシングの機能をサポートしていることがあります。

プログラミングの観点

  • ドライバが内部的に wsrep_local_state_comment などのステータス変数を監視し、最適なノードを選択する場合があります。
  • ドライバが提供する設定オプション(例:ha.loadbalance=true)を有効にすることで、読み取りクエリを複数のノードに分散させることができます。
  • ドライバの接続URLに複数のホストを指定することで、ドライバが自動的に利用可能なノードに接続を試みます。

例 (MariaDB Connector/J でのロードバランシング)

Javaアプリケーションの JDBC 接続URL に複数のノードのアドレスをカンマ区切りで指定し、ha.loadbalance=true オプションを設定することで、ドライバが自動的に読み取りクエリをラウンドロビンなどの方式で分散します。

String url = "jdbc:mariadb://host1:3306,host2:3306,host3:3306/database?user=user&password=password&ha.loadbalance=true";
Connection conn = DriverManager.getConnection(url);
// ... データベース操作 ...

Galera Cluster Aware Load Balancer の利用

HAProxy や Keepalived などのロードバランサーの中には、Galera Cluster の状態をヘルスチェックで監視し、書き込み可能なノードにのみ書き込みトラフィックをルーティングできるものがあります。

プログラミングの観点

  • アプリケーションは、クラスターのトポロジを意識する必要がなくなり、単一のデータベースサーバーに接続するのと同じようにプログラミングできます。
  • ロードバランサーがバックエンドノードのヘルスチェックを行い、書き込み可能なノードを自動的に判別します。ヘルスチェックでは、SHOW STATUS LIKE 'wsrep_local_state_comment'; の結果などを利用して、ノードが Primary であるかなどを確認します。
  • アプリケーションはロードバランサーの仮想IPアドレスに接続します。

これらの代替的な方法は、アプリケーションの複雑さを軽減し、より堅牢でスケーラブルなシステムを構築するのに役立ちます。どの方法を選択するかは、アプリケーションの要件、インフラストラクチャの構成、運用体制などによって異なります。