地方エンジニアの学習日記

興味ある技術の雑なメモだったりを書いてくブログ。たまに日記とガジェット紹介。

【MySQL】読み取り専用トランザクションもmetadata lockを取る

dev.mysql.com

BEGINなどをせずにSELECTを実行しただけでも読み取り専用トランザクションというのが開始されるらしい。スロークエリが実行されているテーブルに対してDDLを実行するとmetadata lockが取られて以降のDMLが全てブロックされる問題があるがBEGINとかしてない場合は問題ないのでは?と思ったので調べてみた。

TrxA > SELECT SLEEP(3600) from HOGE;

とやってHOGEに対してDDLを発行すると3600sの間DDLは待たされ以降のDMLを全て待たされる。読み取り専用トランザクション中にALTERが実行されるとトランザクションの分離を実現できなくなるので考えてみればそれはそうという感じである。

実験してみる

こんなテーブルにALTER TABLEを実行してみる

mysql> desc test_table;
+------------+-------------+------+-----+---------+----------------+
| Field      | Type        | Null | Key | Default | Extra          |
+------------+-------------+------+-----+---------+----------------+
| id         | int(20)     | NO   | PRI | NULL    | auto_increment |
| name       | varchar(20) | NO   |     | NULL    |                |
| created_at | datetime    | YES  |     | NULL    |                |
| updated_at | datetime    | YES  |     | NULL    |                |
+------------+-------------+------+-----+---------+----------------+
4 rows in set (0.01 sec)

DMLを実行する。レコード数が少ないのでsleepでスロークエリを再現している

select sleep(30) from test_table;

ここでinformation_schemeを見てみる。trx_is_read_onlyが1でトランザクションが開始されているのがわかる。

mysql> select * from INNODB_TRX\G
*************************** 1. row ***************************
                    trx_id: 421891224008528
                 trx_state: RUNNING
               trx_started: 2023-01-14 02:49:41
     trx_requested_lock_id: NULL
          trx_wait_started: NULL
                trx_weight: 0
       trx_mysql_thread_id: 2
                 trx_query: select sleep(30) from test_table
       trx_operation_state: NULL
         trx_tables_in_use: 1
         trx_tables_locked: 0
          trx_lock_structs: 0
     trx_lock_memory_bytes: 1136
           trx_rows_locked: 0
         trx_rows_modified: 0
   trx_concurrency_tickets: 0
       trx_isolation_level: REPEATABLE READ
         trx_unique_checks: 1
    trx_foreign_key_checks: 1
trx_last_foreign_key_error: NULL
 trx_adaptive_hash_latched: 0
 trx_adaptive_hash_timeout: 0
          trx_is_read_only: 1
trx_autocommit_non_locking: 1
1 row in set (0.00 sec)

このタイミングでALTER TABLEを実行してみる。DDLは待たされてWaiting for table metadata lockとなっている。

mysql> show processlist;
+----+------+-----------+--------------------+---------+------+---------------------------------+---------------------------------------------------------+
| Id | User | Host      | db                 | Command | Time | State                           | Info                                                    |
+----+------+-----------+--------------------+---------+------+---------------------------------+---------------------------------------------------------+
|  2 | root | localhost | test               | Query   |   10 | User sleep                      | select sleep(30) from test_table                        |
|  3 | root | localhost | information_schema | Query   |    0 | starting                        | show processlist                                        |
|  4 | root | localhost | test               | Query   |    9 | Waiting for table metadata lock | ALTER TABLE test_table ADD PhoneNumber VARCHAR(20) NULL |
+----+------+-----------+--------------------+---------+------+---------------------------------+---------------------------------------------------------+

metadata lockはperformance_schemaから見ることができるので有効にしてみてみるとtest_tableに対してロックを取得されているというのがわかる。

mysql> UPDATE performance_schema.setup_instruments
    -> SET ENABLED = 'YES', TIMED = 'YES'
    -> WHERE NAME = 'wait/lock/metadata/sql/mdl';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from performance_schema.metadata_locks\G
*************************** 1. row ***************************
          OBJECT_TYPE: TABLE
        OBJECT_SCHEMA: test
          OBJECT_NAME: test_table
OBJECT_INSTANCE_BEGIN: 140415164543808
            LOCK_TYPE: SHARED_READ
        LOCK_DURATION: TRANSACTION
          LOCK_STATUS: GRANTED
               SOURCE:
      OWNER_THREAD_ID: 27
       OWNER_EVENT_ID: 13

【AWS】LambdaのCLIでパラメータを渡してinvokeする

$ aws lambda invoke --function-name pyload-test-function --payload file://payload.json --cli-binary-format raw-in-base64-out response.json

あ、できたんかいとなった。これまでGUIの操作で何十時間使ったんだろうと。。。AWSCLIの機能が充実してるのでGUIからやるだけじゃなくCLIでやる場合は?みたいなのを考えてやっていくとよさそうかなぁと思った。やっていこう

open-groove.net

【Linux】MTU以上のパケットがキャプチャされる理由

milestone-of-se.nesuke.com

GSO (Generic Segmentation Offload) は、NICTCP セグメンテーションできない場合に、代わりに処理を行うソフトウェアです。TSO は NIC がハードウェアレベルで TSO 機能に対応している必要がありますが、対応していない NIC の場合は GSO が有効になります。

とのこと。tcpdumpNICカーネルの間でキャプチャをしてるのでカーネルが分割しないとMTU以上の値がtcpdumpのlengthに表示される。

ロックフリーを知った

ja.wikipedia.org

lock-freeについて最近学び始めた。lock-freeとはwikiから参照すると

Lock-freeとWait-freeアルゴリズムとは、共有データにロックをかけてアクセスを防ぐアルゴリズムとは違い、複数のスレッドが同時並行的に、ある対象データを壊すことなしに読み書きすることを可能にするアルゴリズムである。Lock-free とはスレッドがロックしないことを意味しており、全てのステップにおいてシステムが必ず進行する。これはLock-free ではミューテックスセマフォといった、排他制御のためのプリミティブを使ってはならないことを意味する。なぜならロックを持っているスレッドの実行が中断した場合、全体の進行を阻止しうるからである。Wait-free とは、他のスレッドの動作に関係なく、スレッドがいかなる操作も有限のステップで操作を完了させられることを指す。あるアルゴリズムがLock-freeであるがWait-freeでないことはありうる。Wait-free なアルゴリズムは Lock-free である。

ロックをかけずともマルチスレッドで動作をし続けるというものである。Goを例に出すとロックも何もかけずにgoroutineそれぞれである値をインクリメントする例をあげるとこんな感じ。

func (c *counter) Inc(wg *sync.WaitGroup) {
    defer wg.Done()
    c.v["wakuwaku"]++
}

func main() {
    wg := sync.WaitGroup{}

    c := counter{v: make(map[string]int)}
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go c.Inc(&wg)
    }

    wg.Wait()
    fmt.Printf("%v", c.v)
}

mapはgoroutine safeじゃないので数値はおかしな値になる。なのでインクリメントしている処理をクリティカルセクションとして排他制御をしてあげる。そうすると意図した結果が得られるようになる。

func (c *counter) Inc(wg *sync.WaitGroup) {
    defer wg.Done()
    c.mu.Lock()
    c.v["wakuwaku"]++
    c.mu.Unlock()
}

インクリメントする直前にロックをかけてインクリメントしたらロックを解除している。このロックされた区間が短かかったり同時に実行されるgoroutineが少なければプログラムの実行時間に対して大きな影響はないと言える。一方でクリティカルセクションでIOをしたりコンテキストスイッチが発生したりしたらどうなるだろうか?ロックを持っているスレッドがIO待ちになった瞬間クリティカルセクション待ちのgoroutineが大量に発生して実行時間が伸び続ける。さらに並列数が多ければ多いほどロック獲得待ちのgoroutineは増え続けてある処理が全く進まないように見えることも起き得る。ここで登場するのがlock-freeである。

wikiにはLock-freeとWait-freeアルゴリズムとは、共有データにロックをかけてアクセスを防ぐアルゴリズムとは違い、複数のスレッドが同時並行的に、ある対象データを壊すことなしに読み書きすることを可能にするアルゴリズムである。と書いてある。Lock-free や Wait-free アルゴリズムを作るには、CPUが専用のアトミックな命令を提供しているのでこれを使う。CASというのがある。goではsync パッケージの sync.Mutex を使う方法を先ほどあげたがCASを使うにはsync/atomic パッケージの atomic.AddUintXXというのがこちらを使うことで実現できる。

CASとはコンペア・アンド・スワップ(Compare-and-Swap、CAS)は、CPUの特別な命令の一種。不可分操作として、あるメモリ位置の内容と指定された値を比較し、等しければそのメモリ位置に別の指定された値を格納する。この操作の結果、置換が行われたかどうかを示す必要があり、単純な真理値を返すか、そのメモリ位置から読み込んだ内容(書き込んだ内容ではない)を返す。と書かれている。上記の AddUintは値の取得、比較、置換(比較の結果次第)までをatomicに実行することでロックを取らずにデータを正しく更新していくというものである。

ja.wikipedia.org