Redshift Data APIのレスポンスを様々な異常系パターンで検証する
本記事では、Redshift Data APIの異常系レスポンスを調べていきます。先にパターン別の結果を載せておきます。
- テーブルの中身が0件
get_statement_result
のレスポンスのRecords
が空のリスト- Pythonは正常終了
- テーブルが存在しない場合
describe_statement
のレスポンスのStatus
とError
にエラー情報- Pythonは正常終了
- SQLのシンタックスエラー
describe_statement
のレスポンスのStatus
とError
にエラー情報- Pythonは正常終了
- パラメータ間違え
Database
・DbUser
describe_statement
のレスポンスのStatus
とError
にエラー情報- Pythonは正常終了
ClusterIdentifier
- エラーメッセージと共にPythonがエラー終了
- パラメータ不足
- エラーメッセージと共にPythonがエラー終了
- クラスタが停止中
- エラーメッセージと共にPythonがエラー終了
Redshiftは最近のアップデートで、JDBCドライバーがなくてもSQLを投げれるようになりました。
非同期APIのため、クエリを実行し結果を取得するまで数回の手続きが発生します。その流れをおさらいしながら、異常時にはどのような挙動になるのかを確認していきます。
Using the Amazon Redshift Data API - Amazon Redshift
正常系の流れ
まず正常時のAPIの挙動を確認して行きます。今回はboto3を使用して、LambdaからData APIを叩いていきます。
redshift-data — AWS CLI 1.19.44 Command Reference
1ノード構成のdc2.large
で、「パブリックアクセス無効」「セキュリティグループは全クローズ」にしたプライベートなクラスタに対して実行してみます。LambdaからDataAPIを叩くサンプルは、以下のブログに修正を加えて流用しています。
import json import boto3 import time # Redshift接続情報 CLUSTER_NAME='haruta-data-api' DATABASE_NAME='dev' DB_USER='awsuser' client = boto3.client('redshift-data') def lambda_handler(event, context): sql = event['sql'] exec_query(sql) def exec_query(sql): result = client.execute_statement( ClusterIdentifier=CLUSTER_NAME, Database=DATABASE_NAME, DbUser=DB_USER, Sql=sql, ) # 実行IDを取得 id = result['Id'] # クエリが終わるのを待つ statement = '' status = '' while status != 'FINISHED' and status != 'FAILED' and status != 'ABORTED': statement = client.describe_statement(Id=id) status = statement['Status'] print("Status:", status) time.sleep(1) print('') print(json.dumps(statement, indent=4, default=str)) print('') # 結果の表示 try: statement = client.get_statement_result(Id=id) print(json.dumps(statement, indent=4, default=str)) except Exception as e: print(e)
boto3(ver1.17.45)で用意されているAPIメソッドは以下の通りです。クエリを投げて結果を得る基本操作には、execute_statement
→ describe_statement
→ get_statement_result
を順次実行します。
- cancel_statement
- describe_statement
- describe_table
- execute_statement
- get_statement_result
- list_databases
- list_schemas
- list_statements
- list_tables
まずはCREATE TABLE AS
を実行してみました。結果を持たないクエリなので、get_statement_result
ではResourceNotFoundException
が返ってきていますね。
create table hoge as select 1 as id, 'Taro' as name union select 2 as id, 'Jiro' as name;
Status: PICKED Status: FINISHED { "ClusterIdentifier": "haruta-data-api", "CreatedAt": "2021-04-06 05:42:42.925000+00:00", "Duration": 48020206, "Id": "69810e81-c3ae-4ac2-a64b-fc597a015e44", "QueryString": "drop table hoge;", "RedshiftPid": 7831, "RedshiftQueryId": -1, "ResultRows": 0, "ResultSize": 0, "Status": "FINISHED", "UpdatedAt": "2021-04-06 05:42:43.697000+00:00", "ResponseMetadata": { "RequestId": "4cdf734a-76f7-4564-8064-e584c2ed24d4", "HTTPStatusCode": 200, "HTTPHeaders": { "x-amzn-requestid": "4cdf734a-76f7-4564-8064-e584c2ed24d4", "content-type": "application/x-amz-json-1.1", "content-length": "305", "date": "Tue, 06 Apr 2021 05:42:44 GMT" }, "RetryAttempts": 0 } } An error occurred (ResourceNotFoundException) when calling the GetStatementResult operation: Query does not have result. Please check query status with DescribeStatement.
続いて作成したテーブルに対して、SELECT文を試してみます。当たり前ですが、こちらは正常に取得できました。
select * from hoge;
Status: SUBMITTED Status: FINISHED { "ClusterIdentifier": "haruta-data-api", "CreatedAt": "2021-04-06 05:56:09.907000+00:00", "Duration": 192198311, "Id": "c3f7d0ff-d243-4708-80d5-2f790f3f0b3d", "QueryString": "select * from hoge;", "RedshiftPid": 9675, "RedshiftQueryId": 2475, "ResultRows": 2, "ResultSize": 30, "Status": "FINISHED", "UpdatedAt": "2021-04-06 05:56:10.748000+00:00", "ResponseMetadata": { "RequestId": "c186a8e6-073e-4b35-9295-c13a7e2a9850", "HTTPStatusCode": 200, "HTTPHeaders": { "x-amzn-requestid": "c186a8e6-073e-4b35-9295-c13a7e2a9850", "content-type": "application/x-amz-json-1.1", "content-length": "311", "date": "Tue, 06 Apr 2021 05:56:11 GMT" }, "RetryAttempts": 0 } } { "ColumnMetadata": [ { "isCaseSensitive": false, "isCurrency": false, "isSigned": true, "label": "id", "length": 0, "name": "id", "nullable": 1, "precision": 10, "scale": 0, "schemaName": "public", "tableName": "hoge", "typeName": "int4" }, { "isCaseSensitive": true, "isCurrency": false, "isSigned": false, "label": "name", "length": 0, "name": "name", "nullable": 1, "precision": 4, "scale": 0, "schemaName": "public", "tableName": "hoge", "typeName": "varchar" } ], "Records": [ [ { "longValue": 1 }, { "stringValue": "Taro" } ], [ { "longValue": 2 }, { "stringValue": "Jiro" } ] ], "TotalNumRows": 2, "ResponseMetadata": { "RequestId": "96465921-d6c3-4e25-ab26-031cf4e7a134", "HTTPStatusCode": 200, "HTTPHeaders": { "x-amzn-requestid": "96465921-d6c3-4e25-ab26-031cf4e7a134", "content-type": "application/x-amz-json-1.1", "content-length": "525", "date": "Tue, 06 Apr 2021 05:56:12 GMT" }, "RetryAttempts": 0 } }
一通り正常系の挙動が見れたところで、異常系の挙動を確認して行きます。
異常時の挙動
テーブルの中身が0件
SQLで参照しているテーブルの中身がなかったケースです。エラーにはならず、Records
内が空のリストとなってレスポンスが返ってきました。
create table null_table (id integer, name varchar); select * from null_table;
Status: SUBMITTED Status: STARTED Status: FINISHED { "ClusterIdentifier": "haruta-data-api", "CreatedAt": "2021-04-06 06:22:12.597000+00:00", "Duration": 1363452202, "Id": "162128c8-2153-49e6-9a57-ae5f3051a682", "QueryString": "select * from null_table;", "RedshiftPid": 14180, "RedshiftQueryId": 2840, "ResultRows": 0, "ResultSize": 0, "Status": "FINISHED", "UpdatedAt": "2021-04-06 06:22:14.895000+00:00", "ResponseMetadata": { "RequestId": "d3fbddaf-c996-4a66-827e-927b1400c5c5", "HTTPStatusCode": 200, "HTTPHeaders": { "x-amzn-requestid": "d3fbddaf-c996-4a66-827e-927b1400c5c5", "content-type": "application/x-amz-json-1.1", "content-length": "318", "date": "Tue, 06 Apr 2021 06:22:15 GMT" }, "RetryAttempts": 0 } } { "ColumnMetadata": [ { "isCaseSensitive": false, "isCurrency": false, "isSigned": true, "label": "id", "length": 0, "name": "id", "nullable": 1, "precision": 10, "scale": 0, "schemaName": "public", "tableName": "null_table", "typeName": "int4" }, { "isCaseSensitive": true, "isCurrency": false, "isSigned": false, "label": "name", "length": 0, "name": "name", "nullable": 1, "precision": 256, "scale": 0, "schemaName": "public", "tableName": "null_table", "typeName": "varchar" } ], "Records": [], "TotalNumRows": 0, "ResponseMetadata": { "RequestId": "17dbfff7-f1e4-4e94-bb2e-4d06d585558f", "HTTPStatusCode": 200, "HTTPHeaders": { "x-amzn-requestid": "17dbfff7-f1e4-4e94-bb2e-4d06d585558f", "content-type": "application/x-amz-json-1.1", "content-length": "458", "date": "Tue, 06 Apr 2021 06:22:16 GMT" }, "RetryAttempts": 0 } }
テーブルが存在しない場合
SQLで参照しているテーブルがそもそも存在していなかったケースです。この場合describe_statement
で、"Status": "FAILED"
、"Error": "ERROR: relation "not_exist_table" does not exist"
という情報が含まれたレスポンスが返され、プログラム自体は正常終了します。エラー終了させたい場合は、Status
から条件分岐させてraise
する必要があります。
一方、get_statement_result
ではResourceNotFoundException
エラーが返ってきています。あくまでクエリ結果がないというだけの情報なので、詳細を把握するためにはdescribe_statement
のレスポンスが必要です。
select * from not_exist_table;
Status: SUBMITTED Status: FAILED { "ClusterIdentifier": "haruta-data-api", "CreatedAt": "2021-04-06 06:24:42.729000+00:00", "Duration": -1, "Error": "ERROR: relation \"not_exist_table\" does not exist", "Id": "197892f7-febb-4ab1-9e63-2b13cf42e158", "QueryString": "select * from not_exist_table;", "RedshiftPid": 14966, "RedshiftQueryId": -1, "ResultRows": -1, "ResultSize": -1, "Status": "FAILED", "UpdatedAt": "2021-04-06 06:24:43.572000+00:00", "ResponseMetadata": { "RequestId": "dff3d4bc-c078-4cc3-a9a5-6f72c3050b6f", "HTTPStatusCode": 200, "HTTPHeaders": { "x-amzn-requestid": "dff3d4bc-c078-4cc3-a9a5-6f72c3050b6f", "content-type": "application/x-amz-json-1.1", "content-length": "375", "date": "Tue, 06 Apr 2021 06:24:44 GMT" }, "RetryAttempts": 0 } } An error occurred (ResourceNotFoundException) when calling the GetStatementResult operation: Query does not have result. Please check query status with DescribeStatement.
SQLのシンタックスエラー
シンタックスエラーのSQLが実行されたケースでは、テーブルが存在しない場合と同じようにPython自体は正常終了し、describe_statement
の方でエラー情報が含まれたレスポンスが返ってきます。
elect * from hoge;
Status: SUBMITTED Status: FAILED { "ClusterIdentifier": "haruta-data-api", "CreatedAt": "2021-04-06 06:32:17.668000+00:00", "Duration": -1, "Error": "ERROR: syntax error at or near \"elect\"\n Position: 1", "Id": "bb48e994-1543-4835-b158-1a96f3e1d800", "QueryString": "elect * from hoge;", "RedshiftPid": 15677, "RedshiftQueryId": -1, "ResultRows": -1, "ResultSize": -1, "Status": "FAILED", "UpdatedAt": "2021-04-06 06:32:18.488000+00:00", "ResponseMetadata": { "RequestId": "742ba7b6-3c3a-44c5-85e7-10a62896897e", "HTTPStatusCode": 200, "HTTPHeaders": { "x-amzn-requestid": "742ba7b6-3c3a-44c5-85e7-10a62896897e", "content-type": "application/x-amz-json-1.1", "content-length": "368", "date": "Tue, 06 Apr 2021 06:32:19 GMT" }, "RetryAttempts": 0 } } An error occurred (ResourceNotFoundException) when calling the GetStatementResult operation: Query does not have result. Please check query status with DescribeStatement.
パラメータ間違え
Redshiftのパラメータ情報が間違っていた場合の挙動です。CLUSTER_NAME
が間違っている場合はexecute_statement
自体がエラー終了となりましたが、DATABASE_NAME
やDB_USER
が間違っている場合、describe_statement
のレスポンス内にエラー情報が含まれ、プログラム自体は正常終了します。クラスタが存在していれば、APIサービス自体はリクエストを受け入れる挙動になってるんですねー。
CLUSTER_NAME
# Redshift接続情報 CLUSTER_NAME='haruta-data-api-dummy' DATABASE_NAME='dev' DB_USER='awsuser'
{ "errorMessage": "An error occurred (ValidationException) when calling the ExecuteStatement operation: Cluster doesn't exist in this region.", "errorType": "ValidationException", "stackTrace": [ " File \"/var/task/lambda_function.py\", line 14, in lambda_handler\n exec_query(sql)\n", " File \"/var/task/lambda_function.py\", line 22, in exec_query\n Sql=sql,\n", " File \"/var/runtime/botocore/client.py\", line 357, in _api_call\n return self._make_api_call(operation_name, kwargs)\n", " File \"/var/runtime/botocore/client.py\", line 676, in _make_api_call\n raise error_class(parsed_response, operation_name)\n" ] }
DATABASE_NAME
# Redshift接続情報 CLUSTER_NAME='haruta-data-api' DATABASE_NAME='dev-dummy' DB_USER='awsuser'
Status: SUBMITTED Status: FAILED { "ClusterIdentifier": "haruta-data-api", "CreatedAt": "2021-04-06 06:43:31.474000+00:00", "Duration": -1, "Error": "FATAL: database \"dev-dummy\" does not exist", "Id": "b4178d6a-3bd5-4b7b-a0ac-db7ad9fb947f", "QueryString": "select * from hoge;", "RedshiftPid": 0, "RedshiftQueryId": -1, "ResultRows": -1, "ResultSize": -1, "Status": "FAILED", "UpdatedAt": "2021-04-06 06:43:32.058000+00:00", "ResponseMetadata": { "RequestId": "04d365c3-5e04-4ef1-91ee-6c436e18b305", "HTTPStatusCode": 200, "HTTPHeaders": { "x-amzn-requestid": "04d365c3-5e04-4ef1-91ee-6c436e18b305", "content-type": "application/x-amz-json-1.1", "content-length": "354", "date": "Tue, 06 Apr 2021 06:43:33 GMT" }, "RetryAttempts": 0 } } An error occurred (ResourceNotFoundException) when calling the GetStatementResult operation: Query does not have result. Please check query status with DescribeStatement.
DB_USER
# Redshift接続情報 CLUSTER_NAME='haruta-data-api' DATABASE_NAME='dev' DB_USER='awsuser-dummy'
Status: SUBMITTED Status: FAILED { "ClusterIdentifier": "haruta-data-api", "CreatedAt": "2021-04-06 06:44:56.492000+00:00", "Duration": -1, "Error": "FATAL: user \"awsuser-dummy\" does not exist", "Id": "34053a23-9db4-4846-a123-06afe6e85dd0", "QueryString": "select * from hoge;", "RedshiftPid": 0, "RedshiftQueryId": -1, "ResultRows": -1, "ResultSize": -1, "Status": "FAILED", "UpdatedAt": "2021-04-06 06:44:57.194000+00:00", "ResponseMetadata": { "RequestId": "d29f0661-57a1-4082-80b3-51b83d15fa33", "HTTPStatusCode": 200, "HTTPHeaders": { "x-amzn-requestid": "d29f0661-57a1-4082-80b3-51b83d15fa33", "content-type": "application/x-amz-json-1.1", "content-length": "354", "date": "Tue, 06 Apr 2021 06:44:58 GMT" }, "RetryAttempts": 0 } } An error occurred (ResourceNotFoundException) when calling the GetStatementResult operation: Query does not have result. Please check query status with DescribeStatement.
パラメータ不足
execute_statement
で必要なパラメータが指定されていない場合では、以下の例では全てPython上のエラー終了となりました。boto3のドキュメント上でREQUIRED
となっているものはMissing required parameter in input
というエラーとなり、それ以外は個別のエラーメッセージが返されています。REQUIRED
以外のパラメータは、DataAPIを叩く認証設定にもよりそうですね。
ClusterIdentifier
result = client.execute_statement( #ClusterIdentifier=CLUSTER_NAME, Database=DATABASE_NAME, DbUser=DB_USER, Sql=sql, )
{ "errorMessage": "Parameter validation failed:\nMissing required parameter in input: \"ClusterIdentifier\"", "errorType": "ParamValidationError", "stackTrace": [ " File \"/var/task/lambda_function.py\", line 15, in lambda_handler\n exec_query(sql)\n", " File \"/var/task/lambda_function.py\", line 23, in exec_query\n Sql=sql,\n", " File \"/var/runtime/botocore/client.py\", line 357, in _api_call\n return self._make_api_call(operation_name, kwargs)\n", " File \"/var/runtime/botocore/client.py\", line 649, in _make_api_call\n api_params, operation_model, context=request_context)\n", " File \"/var/runtime/botocore/client.py\", line 697, in _convert_to_request_dict\n api_params, operation_model)\n", " File \"/var/runtime/botocore/validate.py\", line 297, in serialize_to_request\n raise ParamValidationError(report=report.generate_report())\n" ] }
Database
result = client.execute_statement( ClusterIdentifier=CLUSTER_NAME, #Database=DATABASE_NAME, DbUser=DB_USER, Sql=sql, )
{ "errorMessage": "An error occurred (ValidationException) when calling the ExecuteStatement operation: Database and SQL String are required attributes.", "errorType": "ValidationException", "stackTrace": [ " File \"/var/task/lambda_function.py\", line 15, in lambda_handler\n exec_query(sql)\n", " File \"/var/task/lambda_function.py\", line 23, in exec_query\n Sql=sql,\n", " File \"/var/runtime/botocore/client.py\", line 357, in _api_call\n return self._make_api_call(operation_name, kwargs)\n", " File \"/var/runtime/botocore/client.py\", line 676, in _make_api_call\n raise error_class(parsed_response, operation_name)\n" ] }
DbUser
result = client.execute_statement( ClusterIdentifier=CLUSTER_NAME, Database=DATABASE_NAME, #DbUser=DB_USER, Sql=sql, )
{ "errorMessage": "An error occurred (ValidationException) when calling the ExecuteStatement operation: To use IAM Authorization both Cluster ID and DB User have to specified.", "errorType": "ValidationException", "stackTrace": [ " File \"/var/task/lambda_function.py\", line 15, in lambda_handler\n exec_query(sql)\n", " File \"/var/task/lambda_function.py\", line 23, in exec_query\n Sql=sql,\n", " File \"/var/runtime/botocore/client.py\", line 357, in _api_call\n return self._make_api_call(operation_name, kwargs)\n", " File \"/var/runtime/botocore/client.py\", line 676, in _make_api_call\n raise error_class(parsed_response, operation_name)\n" ] }
Sql
result = client.execute_statement( ClusterIdentifier=CLUSTER_NAME, Database=DATABASE_NAME, DbUser=DB_USER, #Sql=sql, )
{ "errorMessage": "An error occurred (ValidationException) when calling the ExecuteStatement operation: To use IAM Authorization both Cluster ID and DB User have to specified.", "errorType": "ValidationException", "stackTrace": [ " File \"/var/task/lambda_function.py\", line 15, in lambda_handler\n exec_query(sql)\n", " File \"/var/task/lambda_function.py\", line 23, in exec_query\n Sql=sql,\n", " File \"/var/runtime/botocore/client.py\", line 357, in _api_call\n return self._make_api_call(operation_name, kwargs)\n", " File \"/var/runtime/botocore/client.py\", line 676, in _make_api_call\n raise error_class(parsed_response, operation_name)\n" ] }
クラスタが一時停止中
最後に、クラスタが一時停止中の場合です。これもPython上のエラーとなりました。
{ "errorMessage": "An error occurred (ValidationException) when calling the ExecuteStatement operation: Redshift cluster status 'PAUSED' is not allowed.", "errorType": "ValidationException", "stackTrace": [ " File \"/var/task/lambda_function.py\", line 15, in lambda_handler\n exec_query(sql)\n", " File \"/var/task/lambda_function.py\", line 23, in exec_query\n Sql=sql,\n", " File \"/var/runtime/botocore/client.py\", line 357, in _api_call\n return self._make_api_call(operation_name, kwargs)\n", " File \"/var/runtime/botocore/client.py\", line 676, in _make_api_call\n raise error_class(parsed_response, operation_name)\n" ] }
最後に
「パラメータ間違え」のパターンだけ、パラメータによって挙動が変わってくることだけ頭に入れておけば大丈夫かなと思います。Redshift Data APIのエラーハンドリングにお役てください!