SQLite on EFSは書き込みロックできる
はじめに
こんにちは。がれっとです。
先日ECSとRDSをやめて、AWSコストを9割削減しましたという記事を投稿したところ、興味深いコメントを見つけたので検証してみました。
結論
EFS上のファイルには通常のSQLite相当のロックを行うことができ、SQLiteの書き込みが競合してバイナリファイルが壊れるといったことは基本的にない。
検証内容
SQLiteは書き込みロックをOSによるファイルロックを使用して実現しているため、Network File System上のファイルに対してうまくいかないというコメントを発見しました。
たしかに、SQLite公式ドキュメントにもその旨が記載されています。
そのため、本当にEFS上のSQLiteは書き込みが競合して壊れるのか、検証していきます。
AWS 構成図
マウントポイントによってロックのかかり方が異なる可能性を否定できなかったため、念の為アベイラビリティゾーンを分けて検証しました。
Lambda
API Gatewayに対して、GETリクエストをするとSELECT文が、POSTリクエストをするとINSERT文が走るPythonのスクリプトを簡易的に用意しました。
以下抜粋です
def lambda_handler(event, context):
if event["requestContext"]["http"]["method"] == "POST":
# データ挿入
try:
insert_data() # 1行レコードをインサート
except Exception as e:
return {
"statusCode": 500,
"body": json.dumps({
"error": "Failed to insert data",
"message": str(e),
})
}
# データ取得
try:
body = get_data() # 最新のレコードを取得
except Exception as e:
return {
"statusCode": 500,
"body": json.dumps({
"error": "Failed to fetch data",
"message": str(e),
})
}
return {
"statusCode": 200,
"body": json.dumps(body)
}
テーブル定義は以下のものを使用しています。
CREATE TABLE posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
content TEXT
);
bodyにはテーブルにある最新レコードが返ってくるようにしています。
検証
以下の内容を検証しました。
- Lambdaへの並列リクエスト
- GET
- POST
- EC2でトランザクション中にLambdaへリクエスト
- GET
- POST
Lambdaへの並列リクエスト
並列リクエストは、ab
コマンドを利用しました。
GET
READ系は特に競合せずに捌くことができるはずですが、念の為検証します。
$ ab -n 10 -c 10 -m GET https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/
This is ApacheBench, Version 2.3 <$Revision: 1903618 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com (be patient).....done
Server Software:
Server Hostname: xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com
Server Port: 443
SSL/TLS Protocol: TLSv1.2,ECDHE-RSA-AES128-GCM-SHA256,2048,128
Server Temp Key: ECDH X25519 253 bits
TLS Server Name: xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com
Document Path: /dev/
Document Length: 38 bytes
Concurrency Level: 10
Time taken for tests: 0.421 seconds
Complete requests: 10
Failed requests: 0
Total transferred: 2000 bytes
HTML transferred: 380 bytes
Requests per second: 23.75 [#/sec] (mean)
Time per request: 420.979 [ms] (mean)
Time per request: 42.098 [ms] (mean, across all concurrent requests)
Transfer rate: 4.64 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 27 56 14.8 64 67
Processing: 71 211 61.3 244 256
Waiting: 69 211 61.7 244 256
Total: 98 266 75.5 309 323
Percentage of the requests served within a certain time (ms)
50% 309
66% 311
75% 314
80% 319
90% 323
95% 323
98% 323
99% 323
100% 323 (longest request)
問題ないようです。
ついでなので性能測定もしておきます。
下記にConnection Timesの合計を記します。
リクエスト数 | 並列数 | 最低 | 中央 | 最大 |
---|---|---|---|---|
10 | 10 | 98 | 309 | 323 |
100 | 10 | 118 | 271 | 375 |
500 | 10 | 125 | 264 | 387 |
20 | 20 | 132 | 459 | 802 |
100 | 20 | 140 | 600 | 779 |
500 | 20 | 107 | 598 | 804 |
100 | 100 | 138 | 5162 | 5368 |
500 | 100 | 156 | 6761 | 7558 |
並列リクエストには弱いようです。
POST
$ ab -n 10 -c 10 -m POST https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/
This is ApacheBench, Version 2.3 <$Revision: 1903618 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com (be patient).....done
Server Software:
Server Hostname: xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com
Server Port: 443
SSL/TLS Protocol: TLSv1.2,ECDHE-RSA-AES128-GCM-SHA256,2048,128
Server Temp Key: ECDH X25519 253 bits
TLS Server Name: xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com
Document Path: /dev/
Document Length: 38 bytes
Concurrency Level: 10
Time taken for tests: 1.906 seconds
Complete requests: 10
Failed requests: 0
Total transferred: 2000 bytes
HTML transferred: 380 bytes
Requests per second: 5.25 [#/sec] (mean)
Time per request: 1906.476 [ms] (mean)
Time per request: 190.648 [ms] (mean, across all concurrent requests)
Transfer rate: 1.02 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 53 60 2.4 61 62
Processing: 208 921 434.3 999 1584
Waiting: 207 921 434.5 999 1584
Total: 261 981 435.8 1059 1646
Percentage of the requests served within a certain time (ms)
50% 1059
66% 1199
75% 1309
80% 1449
90% 1646
95% 1646
98% 1646
99% 1646
100% 1646 (longest request)
成功したようです。リクエスト数を増やしてみます。
ab -n 50 -c 10 -m POST https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/
This is ApacheBench, Version 2.3 <$Revision: 1903618 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com (be patient).....done
Server Software:
Server Hostname: xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com
Server Port: 443
SSL/TLS Protocol: TLSv1.2,ECDHE-RSA-AES128-GCM-SHA256,2048,128
Server Temp Key: ECDH X25519 253 bits
TLS Server Name: xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com
Document Path: /dev/
Document Length: 38 bytes
Concurrency Level: 10
Time taken for tests: 10.750 seconds
Complete requests: 50
Failed requests: 39
(Connect: 0, Receive: 0, Length: 39, Exceptions: 0)
Total transferred: 10039 bytes
HTML transferred: 1939 bytes
Requests per second: 4.65 [#/sec] (mean)
Time per request: 2149.955 [ms] (mean)
Time per request: 214.995 [ms] (mean, across all concurrent requests)
Transfer rate: 0.91 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 24 37 14.0 32 73
Processing: 190 1943 2425.9 1000 10010
Waiting: 188 1942 2426.0 1000 10010
Total: 218 1980 2430.4 1026 10078
Percentage of the requests served within a certain time (ms)
50% 1026
66% 1379
75% 2101
80% 2448
90% 5966
95% 8904
98% 10078
99% 10078
100% 10078 (longest request)
39件のリクエストに失敗したようです。
SQLiteファイルが破損していれば失敗するはずのGETリクエストをcurlで送ってみます。
$ curl -X GET https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/
{"id": 138, "content": "Hello, World!"}
正常に動いているようです。少なくとも、SQLiteファイルが破損していることはなさそうです。
では、一体どのようなエラーが発生しているのでしょうか?
CloudWatchのログを確認してみます。
sqlite3.OperationalError: database is locked
というエラーが表示されていることから、SQLiteがロックされて書き込めていないことが確認できます。
※ API Gatewayが503を返すケースもあります。
EC2でトランザクション中にLambdaへリクエスト
下記手順で行います。
- EC2にSSH
- EC2にEFSをマウント
-
sqlite3
コマンドでEFS上のSQLiteファイルにアクセス - 任意のトランザクション処理を行う
なお、SQLiteのトランザクションに関しては下記ページを参照させていただきました。
GET
下記について確認します。
- 書き込みロック中にREADできるか
- SHAREDロック
- RESERVEDロック
- 読み込みロック中にREADが妨げられるか
- EXCLUSIVEロック
書き込みロック中のREAD
-
BEGIN DEFERRED;
後にSELECT文を発行し、SHAREDロックを取った後GETリクエストを行います。
$ curl -X GET https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/
{"id": 282, "content": "Hello, World!"}
-
BEGIN IMMEDIATE;
でRESERVEDロックを取った後curlでGETリクエストを行います。
$ curl -X GET https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/
{"id": 282, "content": "Hello, World!"}
問題なく読み込めます。
読み込みロック中のREAD
BEGIN EXCLUSIVE;
でEXCLUSIVEロックを取った後curlでGETリクエストを行います。
$ curl -X GET https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/
{"error": "Failed to fetch data", "message": "database is locked"}
読み込みがロックにより阻まれました。
POST
下記について確認します。
- 書き込みロック中にWRITEが妨げられるか
- SHAREDロック
- RESERVEDロック
- EXCLUSIVEロック
-
BEGIN DEFERRED;
後にSELECT文を発行し、SHAREDロックを取った後POSTリクエストを行います。
$ curl -X POST https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/
{"error": "Failed to insert data", "message": "database is locked"}
-
BEGIN IMMEDIATE;
でRESERVEDロックを取った後curlでGETリクエストを行います。
$ curl -X POST https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/
{"error": "Failed to insert data", "message": "database is locked"}
-
BEGIN EXCLUSIVE;
でEXCLUSIVEロックを取った後curlでGETリクエストを行います。
$ curl -X POST https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/
{"error": "Failed to insert data", "message": "database is locked"}
いずれもロックにより書き込めないようです。
結果
EFS上のSQLiteファイルには、通常のSQLiteと同様にロックを取って読み書きできるようです。
考察
ではなぜNFS上のSQLiteではロックが取れず、書き込みが失敗する
という意見があるのでしょうか?
もちろん公式ドキュメントにもありますし、実際にそのような例もあるようです。
上記のページではNFSのマウントにNFSv3を使用していますが、EFSではNFSv4.0をサポートしています。
NFSv4ではロック周りの処理が改善されているため、問題が生じなかったものと考えられます。
まとめ
簡易的なSQLite on EFSの検証を通じて、NFSプロトコルについて少し詳しくなることができました。
ご自身の環境でこちらの構成を扱われる際は、要件にあった性能検証を行った上での導入をおすすめいたします。
Discussion