AWS, RDS/Aurora Serverless! Slow Query, ElasticSearch를 사용한 시각화
Slow Query 는 공공의 적이다. 전체 서비스 품질을 저하 시킬 뿐만 아니라, Lock 이 걸리는 순간 제대로 된 트랜잭션이 이뤄지지 않는 경우도 있다. Slow Query 가 발생하는 이유는 여러가지다. 개발자의 잘못일 수도 있지만, 시스템의 오류일 수도 있다. (때문에 너무 개발 탓만 하지 말자) AWS는 CloudWatch Logs에 RDS 를 편입시켰다. 그러나, CloudWatch Dashboard 는 가장 단순화된 ‘상태 값’을 보여주기만 한다.
Slow Query 가 있다고 해서 시스템 성능이 바로 저하되는건 아니며, 추이를 살펴 보려면 로그 수집과 수집된 로그의 시각화는 안정적인 시스템 운용에 필수다. 앞선 Nginx Log의 시각화, Lambda 응용, MySQL 인덱스의 이해 등의 포스트는 이 포스트를 설명하기 위한 기본 작업이었다.
- API Gateway/Lambda 를 통한 RESTful API 만들기 : https://grip.news/archives/1397
- Nginx Log 수집 및 Kibana 를 사용한 시각화 : https://grip.news/archives/1344
- MySQL 인덱스의 이해 : https://grip.news/archives/1428
처리 구조
형태는 명확하다. RDS Service 의 로그 모니터링 API 를 사용하려면 CloudWatch를 사용하고, Lambda Function을 생성해 ElasticSearch 로 보낸다. 관리자는 Kibana 와 같은 시각화 도구로 모니터링이 가능하다. (이와 별개로 RDS 모니터링은 Zabbix 로 수집해 Telegram 으로 알람을 받고 있다)
이 포스트에서는 다음 과정을 다루고 있다. 모든 서비스는 Serverless 구조이며, AWS 의 인프라를 적극 활용하고 있다. (관리 서비스를 관리하는 인적 요소를 줄일 수 있다.)
- AWS RDS 로그를 CloudWatch Logs 로 내보내기
- AWS ElasticSearch Service 에 LogPass 설정
- AWS Lambda 에 LogPass 추가
- AWS CloudWatch Logs 를 ElasticSearch Service 에 전달
RDS/Aurora 로그 CloudWatch Logs 로 내보내기
AWS CLI를 사용해 Slow Query 를 CloudWatch Logs 로 내보내도록 설정한다. 이 옵션은 웹 콘솔에서 작업할 수 없으므로 CLI 사용이 필수다.
# Serverless RDS Cluster 사용시 PS C:\Works> aws rds modify-db-cluster --db-cluster-identifier ${DB_CLUSTER_NAME} --cloudwatch-logs-export-configuration EnableLogTypes=slowquery # RDS Instance 사용시 PS C:\Works> aws rds modify-db-instance --db-instance-identifier ${DB_INSTANCE_NAME} --cloudwatch-logs-export-configuration EnableLogTypes=slowquery
DB Instance 와 Serverless DB Cluaster 에 따라 차이가 있기 때문에 주의하자. 우리는 RDS/Aurora Serverless 를 사용중이며, CLI 실행 결과는 다음과 같다. EnabledCloudwatchLogsExports 부분에 slowquery 가 추가 된 것을 확인할 수 있다.
{ "DBCluster": { "AllocatedStorage": 1, "AvailabilityZones": [ "ap-northeast-1d", "ap-northeast-1a", "ap-northeast-1c" ], "BackupRetentionPeriod": 1, "DBClusterIdentifier": "mtlabs-tokyo-dv-mtworks-serverless", "DBClusterParameterGroup": "default.aurora5.6", "DBSubnetGroup": "mtlabs-tokyo-dv-rds", "Status": "available", "EarliestRestorableTime": "2019-06-10T18:28:39.667Z", "Endpoint": "mtlabs-tokyo-dv-...-ap-northeast-1.rds.amazonaws.com", "MultiAZ": false, "Engine": "aurora", "EngineVersion": "5.6.10a", "LatestRestorableTime": "2019-06-12T01:21:37.325Z", "Port": 3306, "MasterUsername": "mtworks", "PreferredBackupWindow": "18:15-18:45", "PreferredMaintenanceWindow": "wed:14:56-wed:15:26", "ReadReplicaIdentifiers": [], "DBClusterMembers": [], "VpcSecurityGroups": [ { "VpcSecurityGroupId": "sg-045a2762", "Status": "active" } ], "HostedZoneId": "Z000000L0SGTNB", "StorageEncrypted": true, "KmsKeyId": "arn:aws:kms:ap-northeast-1:211234567895:key/4000000-2169-4452-8a40-fbfasd3e3546", "DbClusterResourceId": "cluster-J7ABCDEFGI726EZIRQ", "DBClusterArn": "arn:aws:rds:ap-northeast-1:211234567895:cluster:mtlabs-tokyo-dv-mtworks-serverless", "AssociatedRoles": [], "IAMDatabaseAuthenticationEnabled": false, "ClusterCreateTime": "2018-09-06T05:42:50.353Z", "EnabledCloudwatchLogsExports": [ "slowquery" ], "Capacity": 2, "EngineMode": "serverless", "ScalingConfigurationInfo": { "MinCapacity": 2, "MaxCapacity": 64, "AutoPause": true, "SecondsUntilAutoPause": 300, "TimeoutAction": "RollbackCapacityChange" }, "DeletionProtection": true, "HttpEndpointEnabled": false, "CopyTagsToSnapshot": false } }
그리고, Slow Query 를 활성화 하려면 RDS > 파라미터 그룹에 slow_query_log 값을 1로 수정해야 한다.
※ 원인과 이유를 모르겠지만 CloudWatch Logs 에 slowquery 가 생성되는데 3시간 가까이 걸렸다. 참고로, RDS Instance 에서 테스트 하면 거의 바로 생성된다. 물론, 강제로 Slow Query 를 만들어 테스트 했을 때 이야기 (sleep(30). MySQL 의 기본은 10초다)
ElasticSearch Service 로그패스 설정
(ElasticSearch 는 이미 생성되었다는걸 가정으로 한다. 또는 앞선 Nginx Log 를 수집 및 저장했던 도메인을 활용해도 무관하다) ElasticSearch Service 의 Ingest Node 를 사용해 전달 받은 로그 메시지 내 Slow Query를 분석하는데 사용할 SQL 구문 및 처리 실행시간 그리고 의미 있는 문자열의 패턴을 지정한다. 그리고 curl 을 이용해 Ingest Pipeline을 설정하자.
MySQL 계열(MySQL, Maria, Aurora)를 사용한다면 패턴에서의 차이는 거의 없다. 단, 아래 예제는 PowerShell 이기 때문에 Linux 계열에서 curl 을 사용한다면 약간의 수정이 필요하다.
# JSON 변수에 패턴 등을 넣는다. PS C:\Works> $JSON = ' { "processors" : [ { "gsub": { "field": "@message", "pattern": "\n", "replacement": "" } }, { "grok" : { "field" : "@message", "patterns" : ["# User@Host: %{WORD:user}\\[%{WORD}\\] @ %{WORD:host} \\[%{IP:ip}\\]%{SPACE}Id:%{SPACE}%{NUMBER:id}%{SPACE}# Query_time: %{NUMBER:query_time:float}%{SPACE}Lock_time: %{NUMBER:lock_time:float}%{SPACE}Rows_sent: %{NUMBER:rows_sent:int}%{SPACE}Rows_examined: %{NUMBER:rows_examined:int}%{SPACE}(use %{WORD:database};%{SPACE})?(SET timestamp=%{NUMBER:timestamp:int};%{SPACE})?%{GREEDYDATA:sql}"], "ignore_failure" : true } }, { "date": { "field": "timestamp", "formats": ["UNIX"], "ignore_failure" : true } }, { "remove": { "field": ["timestamp", "@message"], "ignore_failure" : true } } ] }' # curl 실행 PS C:\Works> curl -Method PUT -Uri "{ElasticSearch VPC Domain}/_ingest/pipeline/slowquerylog" -Headers @{"Content-Type"="application/json"} -Body $JSON -ContentType "application/json"
문제가 없다면 다음과 같은 내용이 회신된다.
StatusCode : 200 StatusDescription : OK Content : {"acknowledged":true} RawContent : HTTP/1.1 200 OK Connection: keep-alive Access-Control-Allow-Origin: * Content-Length: 21 Content-Type: application/json; charset=UTF-8 Date: Wed, 19 Jun 2019 02:13:37 GMT {"acknowledged":true}... Forms : {} Headers : {[Connection, keep-alive], [Access-Control-Allow-Origin, *], [Content-Length, 21], [Content-Type, a pplication/json; charset=UTF-8]...} Images : {} InputFields : {} Links : {} ParsedHtml : mshtml.HTMLDocumentClass RawContentLength : 21
CloudWatch Logs 의 로그 데이터를 ElasticSearch 로 내보내기
앞서 RDS/Aurora 는 Slow Query 에 대한 내용을 CloudWatch 로 보내기 시작했다. 그리고 ElasticSearch 는 로그 데이터를 받을 준비가 완료됐다. 이제 CloudWatch Logs 에 저장된 로그 데이터를 ElasticSearch Service 로 전달하면 된다. 로그 그룹 명칭은 /aws/rds/cluster/{Cluster Name}/slowquery 이다.
로그 그룹 선택 후 ‘작업(Action) > Amazon Elasticsearch Service 측 스트림’을 선택하면 스트림의 Lambda Function 이 자동 생성된다. 특별한 주의사항은 없지만, IAM 은 반드시 Lambda 의 실행 권한을 갖고 있어야 하며(Execution Permission) 로그 형식은 기타(Other)를 선택한다.
구독 항목에 LogToElasticsearch 가 추가된 것을 확인할 수 있다.
Lambda Function 수정
AWS Lambda Function 을 살펴보면 새로 생긴 Function 이 보인다. (위에서 구독 중인 명칭) 자동 생성된 Lambda Function 을 수정해야 한다.
- pipeline Query String 을 지정한다
- canonicalString 에 pipline 을 추가한다
- request path 를 변경한다.
변경된 내용을 요약해 보면 다음과 같다.
var endpoint = 'vpc-nrt-dv-...ap-northeast-1.es.amazonaws.com'; var pipeline = 'pipeline=slowquerylog'; // pipline 추가 . . var canonicalString = [ request.method, request.path, // ,'' 제거 pipeline, // 추가 canonicalHeaders, '', signedHeaders, hash(request.body, 'hex'), ].join('\n'); . . request.path = request.path + "?" + pipeline; // 추가 return request;
큰 차이점은 없다. 오타만 나지 않게 조심하면 된다. 잘 동작하는지 여부는 CloudWatch Logs 내 로그 스트림을 확인해 보자.
Kibana 에서 확인
AWS ElasticSearch Service에서의 자료는 Kibana 에서 시각화 된다. 먼저 인덱스 패턴을 지정해야 한다. 물론, 매칭되는 인덱스 패턴이 없다. cwl* 을 선택하고, 시간 기준은 @timestamp 를 선택 하면 된다. 이는 앞서 작성한 Lambda Function 에 그렇게 선언 했었기 때문.
var timestamp = new Date(1 * logEvent.timestamp); // index name format: cwl-YYYY.MM.DD var indexName = [ 'cwl-' + timestamp.getUTCFullYear(), // year ('0' + (timestamp.getUTCMonth() + 1)).slice(-2), // month ('0' + timestamp.getUTCDate()).slice(-2) // day ].join('.'); var source = buildSource(logEvent.message, logEvent.extractedFields); source['@id'] = logEvent.id; source['@timestamp'] = new Date(1 * logEvent.timestamp).toISOString(); source['@message'] = logEvent.message;
Discover 를 누르면 로그 데이터를 볼 수 있다. (아래는 쌓인 데이터가 없어서 이쁘지않음..)
결론
관리를 위한 관리 도구를 관리해야한다. 참 난해한 문제다. 더욱이 콘솔에 붙어 로그를 봐야하고, 통계도 내기 쉽지 않다. AWS 의 Lambda 와 ElasticSearch 의 조합은 확장 가능한 사례가 무궁무진하다. 이 포스트의 RDS/Aurora 뿐만 아니라, EC2 등 다양한 서비스로의 확장이 가능하다. 물론 .. 아마존 자체의 문제로 삽질을 좀 했지만.