API Gateway + AWS Lambda で%26が渡せなかった話

謎のInternal server error

https://example.com/v1/?n=[name]
みたいな感じに、API Gatewayでnパラメータを受けて、lambdaで処理する仕組みを作ったら、nパラメータに”a&z” (URL Encode: “a%26z”)みたいな”&”を含むデータを渡すと謎のInternal server error出てハマった。

{
message: "Internal server error"
}

ClowdWatchLogにはsignatureがどうのこうののエラーが。

  • ClowdWatchLog
Execution failed due to configuration error: 
The request signature we calculated does not match the signature you provided. 
Check your AWS Secret Access Key and signing method. 
Consult the service documentation for details.

原因

サポートに問い合わせたところ、以下の回答が。


The error is because the query string “a%26z” is decoded as “a&z” by CloudFront viz. the URL Encoded form. So, the API Gateway gets the query string as “a&z”. Now, when the invocation URL to lambda is being created it appends the Query String given in Integration Request of that resource. Now, the URL is
similar to “https://lambda..amazonaws.com/2015-03-31/functions//invocations?name=a%26z” but while calculating the signature [1] it treats as invalid because “/invocations?name=a%26z” is not a valid lambda invocation URL. The correct Lambda invocation URL is explained in this doc [2].

Moving forward, if you want to use a%26z to your backend then the correct way is to pass it like “a%2526z” In query string and have a body mapping template.

[1]. http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html
[2]. http://docs.aws.amazon.com/lambda/latest/dg/API_Invoke.html#w2ab1c67c12c49c11


知識のなさと英語力のなさでいまいち原因を理解できていないが(英語とAWS得意な誰か解説してください:pray:)、2重URLエンコードして %2526 にすればエラーにならないということは理解できた。Lambda内で一度URL Decodeしないといけないが。

解決

2重URLエンコードするのと同じようなものだが、結局、データをすべてBASE64でエンコードしてLambdaでDecodeすることにした。

疑問

既に多くの人がここではまっている気がするんだけど、ググっても全然情報ないのはなぜ?

続きを読む

Amazon SESで送信元と宛先の制限をかけてみたメモ

調べたり試したりしてみたので、メモ的な。

制限するには

 もともとSESにはサンドボックスモードと言って制限がかかった状態でサービスが利用可能になる。
  参照:http://docs.aws.amazon.com/ja_jp/ses/latest/DeveloperGuide/limits.html
 その制限をわざわざ解除申請を出して自由に利用できるようにする。

 けど、自由すぎても困って、という話。

 SESを利用する際には、SES用のIAMユーザーを払い出して利用します。
 そのIAMユーザーのポリシーを利用して幾つかの制限ができるようです。
  参照:http://docs.aws.amazon.com/ja_jp/ses/latest/DeveloperGuide/control-user-access.html

設定

送信元制限

 特に問題なくできてしまったので、その時のポリシー例を。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt1507889923000",
            "Effect": "Allow",
            "Action": [
                "ses:SendEmail",
                "ses:SendRawEmail"
            ],
            "Condition": {
                "StringLike": {
                    "ses:FromAddress": [
                        "*@example.com"
                    ]
                }
            },
            "Resource": [
                "*"
            ]
        }
    ]
}

 これで「なんちゃら@example.com」が送信元の場合に限り、IAMはSESの認証をできるようにしてくれます。
 例えば「なんちゃら@jp.xxxx.com」とかが送信元になった場合に、554 Access deniedしちゃいます。
 これが送信元制限の簡単なやり方。

送信先(宛先)制限

 送信元制限のノリで作業したらはまったので、このメモを残すことになった。
 先に参照として出したドキュメントに、「ses:Recipients」使ったらできるで、と書いてあったので、間に受けて設定してたんですが
 それが間違いでした。
 とりあえず、うまくいった例

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ses:SendEmail",
                "ses:SendRawEmail"
            ],
            "Resource": "*",
            "Condition": {
                "ForAnyValue:StringLike": {
                    "ses:Recipients": [
                        "*@example.com"
                    ]
                }
            }
        }
    ]
}

 この設定で、「*@example.com」以外にメールが送信できなくなり、先ほどの送信元制限と同様に554エラーが発行されます。
 何にはまったかというと、"ForAnyValue:StringLike" この項目。

 送信元制限の際は、「”StringLike”」でいけるんですが、サポートにお聞きしたところ、ses:Recipientsは複数の項目にまたがり値があるので
 "ForAllValues:StringLike"
 を使ってな、と。
 ドキュメントにも例でのせてるで、ってなことなので、やったんですがダメでした。
 曰く

注記
キーに複数の値が含まれる場合、設定演算子(ForAllValues:StringLike および ForAnyValue:StringLike)を使用して StringLike を修飾できます。

 なので、ダメ元で ForAnyValue:StringLike を使ったらいけたという。
 実はAllとAnyの利用の違いについては調べ切れてないんですが、やりたいことができたということなので、これにて。

両方制限したい欲張りなあなたへ

 以下の設定で実現できてます。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ses:SendEmail",
                "ses:SendRawEmail"
            ],
            "Resource": "*",
            "Condition": {
                "ForAnyValue:StringLike": {
                    "ses:Recipients": [
                        "宛先アドレス"
                    ],
                    "ses:FromAddress": [
                        "送信元アドレス"
                    ]
                }
            }
        }
    ]
}

 アドレスを指定する場合はLikeじゃなくてEqualesでもいいかもしれませんね。

続きを読む

【注意】Amazon Aurora(MySQL)でZero Downtime Patch実行時に不具合が発生するケースがある【AWS】

はじめに

先日、Amazon Aurora(MySQL)の必須アップデートの案内が来たのでアップデートの検証作業を実施しておりました。
Zero Downtime Patch(以下、ZDP)が実行され、ダウンタイム無しでアップデートが行われるはずが、不具合が発生してしまいました。

ググっても関連する情報が見つからなかったので、取り急ぎ分かっている情報について記録しておきます。

なお、対象のアップデートは下記のものになります。(Cluster Version 1.14)

Database Engine Updates 2017-08-07
http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Aurora.DatabaseEngineUpdates.20170807.html

ポイント(TL;DR)

重要なポイントは下記のとおりです。

Aurora(MySQL)に対し、DB Connection Poolを利用し、且つPrepared Statementを利用していると、Zero Downtime Patch実行時に不具合が発生する可能性が高い。
Aurora(MySQL)のアップデートを行う前に、アプリの構成・実装を調べた方が良い。

Zero Downtime Patch(ZDP)とは何か

Auroraを無停止(正確には5秒程度の遅延)でアップデートできる画期的な仕組みです。
アップデート処理中にも、クライアントからのDB接続は維持されます。

今回、Cluster Version 1.14へのアップデートとなりますが、本アップデートに関するドキュメントにも説明があります。
http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Aurora.DatabaseEngineUpdates.20170807.html

AuroraのCluster Version 1.10で初めて導入されたようです。
ZDPの内容について、下記ドキュメントにも記載があります。

Database Engine Updates 2016-12-14
http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Aurora.DatabaseEngineUpdates.20161214.html

こちらの記事も情報がまとまっていて参考になります。

Amazon Aurora のアップグレード
https://qiita.com/tonishy/items/542f7dd10cc43fd299ab

事象の内容

Aurora(MySQL)でClusterのアップデートが必要の旨の案内が来たため、AWS Management Consoleからアップデートを行いました。
AuroraのClusterアップデートは無事に完了しましたが、当該Auroraインスタンスを利用しているJavaアプリが軒並みエラーを吐き出し始めました。
その後Javaアプリを再起動し、復旧しました。

AWS Management Console上、WriterとなっているインスタンスのEventに下記のものが表示されました。
従って、Zero Downtime Patchが行われていたことは間違いありません。

The database was successfully patched with a zero downtime patch.

Javaアプリのログ上には、「Unknown prepared statement handler」といったエラーが多数出力されておりました。

事象の原因

当該JavaアプリはJDBC Connection Poolを利用し、DBとの接続を維持する構成になっています。
また、Prepared Statementを利用する実装になっております。

ZDP実行時、MySQLのクライアント(i.e. Javaアプリ)からの接続は維持されるようですが、Prepared Statementは維持されないことが原因のようでした。
時系列的に整理すると、下記のような状況になっていたようです。

  1. Javaアプリ起動時、Auroraに対して幾つかJDBC接続を確立する
  2. リクエストに応じて、JavaアプリからAuroraにクエリを発行する。この際、Prepareを発行してPrepared Statementを作る。
    Javaアプリは、同一のリクエストに対してはPrepared Statementを再利用する。
  3. AuroraのアップデートをZDPで行う。この際、JavaアプリからのJDBC接続は維持されるが、Prepared Statementは全てクリアされる。
  4. Javaアプリに対し、項番2と同様のリクエストを行う。Javaアプリは、コンパイル済みのPrepared Statementが存在しているという認識のまま、これを再利用しようとし、エラーとなる。

同一のアプリに対し、Prepared Statementを利用するもの/しないものを用意してAuroraのアップデートを実施しました。
結果、前者はエラーが発生し、後者はエラーが発生しませんでした。

なお上記の挙動は、独自に調査・検証したものとなります。
現在、AWSの公式見解・ドキュメント等の情報は見つけられておりません。
# 一応、Database Engine Updates 2016-12-14には以下の記載がありますが、これが該当するかどうかは不明です。
# Note that temporary data like that in the performance schema is reset during the upgrade process.

2017/9/26にAWSサポートへの問い合わせを行っておりますが、2017/10/19現在、回答待ちの状況です。

確認した環境・構成

上記事象は、下記構成のJavaアプリで確認しました。

  • Java 8 (Oracle JDK)
  • spring-boot-1.4.0.RELEASE
  • mybatis-spring-boot-starter 1.1.1
  • mariadb-java-client 1.5.5

併せてMyBatisGeneratorも利用しています。
特別な理由がない限り、JavaアプリからはMyBatisGeneratorで生成されたMapperとClassを利用しています。

デフォルトでは、MyBatisGeneratorではPrepared Statementを利用するMapperを生成するようです。

回避策

回避策は概ね以下の3通りになると思います。

1.Prepared Statementを利用しないよう、アプリを変更する。

ソースコード/ORマッパーの改修・設定変更を行い、Prepared Statementを利用しないようにする方法です。
おそらく、この対応は難しいケースがほとんどだと思います。

2.MySQL接続ドライバの設定で、Prepared Statementを利用しないよう変更する。 

今回のケースでは、MariaDB Connector/Jを利用しており、オプションを指定することでPrepared Statementを利用しないように変更することができました。

useServerPrepStmtsオプションをfalseにすることで実現可能です。
ちなみに、1.6.0以降のバージョンはデフォルトでfalseになっているようですので、これ以降のバージョンを利用してる場合は、本件の影響を受けません。

About MariaDB Connector/J -> Optional URL parameters -> Essential options
https://mariadb.com/kb/en/library/about-mariadb-connector-j/

3.ZDPではない、通常のアップデートを実施する

Auroraインスタンスの再起動を伴う、通常のアップデートを実行することで、本件事象を回避することが可能と考えられます(※未検証)
20~30秒程度のダウンタイムが発生します。

下記に、ZDPにならないケースについて記載がありますので、このどれかを満たせば通常のアップデートになると推測できます(※未検証)

Database Engine Updates 2017-08-07 -> Zero-Downtime Patching
http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Aurora.DatabaseEngineUpdates.20170807.html

  • Long-running queries are in progress
  • Open long-running transactions exist
  • Binary logging is enabled
  • Binary log replication is running
  • Pending parameter changes exist
  • Temporary tables are in use
  • Table locks are in use
  • Open SSL connections exist

まとめ

DBへのConnection Poolを利用し、且つPrepared Statementを利用するようアプリを実装していると、不具合が発生する可能性が高いです。

経験的な感覚ですが、JavaアプリはConnection Poolを利用しているケースが多いかと思います。
Aurora(MySQL)を利用している場合は、チェックをした方が良いかと思います。

検証作業について

古いClusterバージョンのAuroraインスタンスを作ることはできません。AWSサポートにも聞いてみましたが無理なようでした。
従って、運良く過去に構築した古いAuroraインスタンスが存在していれば検証は可能ですが、それ以外のケースでは検証手段がありません。

続きを読む

DynamoDBの予約語一覧を無理やり動的に取得してみた

DynamoDBの予約語を対処するために一覧が欲しくなったのですが、いちいちプログラム内で定義したくなかったので、AWSの予約語一覧ページから一覧を取得するサービスを作りました。

前提

  1. AWSが公開しているDynamoDB予約語一覧のWebページ( http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ReservedWords.html )をスクレイピングして予約語一覧を抽出します。
  2. 実装はAWS Lambda (Python3)、Webサービスとして動作させるためにAPI Gatewayを利用します。

結果

https://github.com/kojiisd/dynamodb-reserved-words

DynamoDB予約語一覧ページの確認

今回は以下のページをParseしたいと思います。

http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/ReservedWords.html

HTMLを見てみると、Parseしたい箇所はcodeタグで囲まれているようで、しかもこのページ内でcodeタグが出現するのは1度だけのようです。

スクリーンショット 2017-10-15 18.03.18.png

これであればすぐにパースできそうです。

HTMLの取得とParse

BeautifulSoupを利用すれば、簡単に実装ができます。BeautifulSoupのインストールは他のページでいくらでも紹介されているので、ここでは省略します。

import sys
import os


sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'lib'))
from bs4 import BeautifulSoup
import requests

URL = os.environ['TARGET_URL']

def run(event, context):

    response = requests.get(URL)
    soup = BeautifulSoup(response.content, "html.parser")

    result_tmp = soup.find("code")
    if result_tmp == None:
        return "No parsing target"

    result = result_tmp.string

    return result.split('n');

とりあえず申し訳程度にパースできない場合(AWSのページがcodeタグを使わなくなった時)を想定してハンドリングしています。

API Gatewayを定義してサービス化

コードが書けてしまえばAPI Gatewayを定義してサービス化するだけです。

設定は非常に単純なので割愛で。

スクリーンショット 2017-10-15 18.12.11_deco.png

アクセスしてみる

実際にアクセスしてみます。

$ curl -X GET https://xxxxxxxxxxx.execute-api.us-east-1.amazonaws.com/prod
["", "ABORT", "ABSOLUTE", "ACTION", "ADD", "AFTER", "AGENT", "AGGREGATE", "ALL", "ALLOCATE", "ALTER", "ANALYZE", "AND", "ANY", "ARCHIVE", "ARE", "ARRAY", "AS", "ASC", "ASCII", "ASENSITIVE", "ASSERTION", "ASYMMETRIC", "AT", "ATOMIC", "ATTACH", "ATTRIBUTE", "AUTH", "AUTHORIZATION", "AUTHORIZE", "AUTO", "AVG", "BACK", "BACKUP", "BASE", "BATCH", "BEFORE", "BEGIN", "BETWEEN", "BIGINT", "BINARY", "BIT", "BLOB", "BLOCK", "BOOLEAN", "BOTH", "BREADTH", "BUCKET", "BULK", "BY", "BYTE", "CALL", "CALLED", "CALLING", "CAPACITY", "CASCADE", "CASCADED", "CASE", "CAST", "CATALOG", "CHAR", "CHARACTER", "CHECK", "CLASS", "CLOB", "CLOSE", "CLUSTER", "CLUSTERED", "CLUSTERING", "CLUSTERS", "COALESCE", "COLLATE", "COLLATION", "COLLECTION", "COLUMN", "COLUMNS", "COMBINE", "COMMENT", "COMMIT", "COMPACT", "COMPILE", "COMPRESS", "CONDITION", "CONFLICT", "CONNECT", "CONNECTION", "CONSISTENCY", "CONSISTENT", "CONSTRAINT", "CONSTRAINTS", "CONSTRUCTOR", "CONSUMED", "CONTINUE", "CONVERT", "COPY", "CORRESPONDING", "COUNT", "COUNTER", "CREATE", "CROSS", "CUBE", "CURRENT", "CURSOR", "CYCLE", "DATA", "DATABASE", "DATE", "DATETIME", "DAY", "DEALLOCATE", "DEC", "DECIMAL", "DECLARE", "DEFAULT", "DEFERRABLE", "DEFERRED", "DEFINE", "DEFINED", "DEFINITION", "DELETE", "DELIMITED", "DEPTH", "DEREF", "DESC", "DESCRIBE", "DESCRIPTOR", "DETACH", "DETERMINISTIC", "DIAGNOSTICS", "DIRECTORIES", "DISABLE", "DISCONNECT", "DISTINCT", "DISTRIBUTE", "DO", "DOMAIN", "DOUBLE", "DROP", "DUMP", "DURATION", "DYNAMIC", "EACH", "ELEMENT", "ELSE", "ELSEIF", "EMPTY", "ENABLE", "END", "EQUAL", "EQUALS", "ERROR", "ESCAPE", "ESCAPED", "EVAL", "EVALUATE", "EXCEEDED", "EXCEPT", "EXCEPTION", "EXCEPTIONS", "EXCLUSIVE", "EXEC", "EXECUTE", "EXISTS", "EXIT", "EXPLAIN", "EXPLODE", "EXPORT", "EXPRESSION", "EXTENDED", "EXTERNAL", "EXTRACT", "FAIL", "FALSE", "FAMILY", "FETCH", "FIELDS", "FILE", "FILTER", "FILTERING", "FINAL", "FINISH", "FIRST", "FIXED", "FLATTERN", "FLOAT", "FOR", "FORCE", "FOREIGN", "FORMAT", "FORWARD", "FOUND", "FREE", "FROM", "FULL", "FUNCTION", "FUNCTIONS", "GENERAL", "GENERATE", "GET", "GLOB", "GLOBAL", "GO", "GOTO", "GRANT", "GREATER", "GROUP", "GROUPING", "HANDLER", "HASH", "HAVE", "HAVING", "HEAP", "HIDDEN", "HOLD", "HOUR", "IDENTIFIED", "IDENTITY", "IF", "IGNORE", "IMMEDIATE", "IMPORT", "IN", "INCLUDING", "INCLUSIVE", "INCREMENT", "INCREMENTAL", "INDEX", "INDEXED", "INDEXES", "INDICATOR", "INFINITE", "INITIALLY", "INLINE", "INNER", "INNTER", "INOUT", "INPUT", "INSENSITIVE", "INSERT", "INSTEAD", "INT", "INTEGER", "INTERSECT", "INTERVAL", "INTO", "INVALIDATE", "IS", "ISOLATION", "ITEM", "ITEMS", "ITERATE", "JOIN", "KEY", "KEYS", "LAG", "LANGUAGE", "LARGE", "LAST", "LATERAL", "LEAD", "LEADING", "LEAVE", "LEFT", "LENGTH", "LESS", "LEVEL", "LIKE", "LIMIT", "LIMITED", "LINES", "LIST", "LOAD", "LOCAL", "LOCALTIME", "LOCALTIMESTAMP", "LOCATION", "LOCATOR", "LOCK", "LOCKS", "LOG", "LOGED", "LONG", "LOOP", "LOWER", "MAP", "MATCH", "MATERIALIZED", "MAX", "MAXLEN", "MEMBER", "MERGE", "METHOD", "METRICS", "MIN", "MINUS", "MINUTE", "MISSING", "MOD", "MODE", "MODIFIES", "MODIFY", "MODULE", "MONTH", "MULTI", "MULTISET", "NAME", "NAMES", "NATIONAL", "NATURAL", "NCHAR", "NCLOB", "NEW", "NEXT", "NO", "NONE", "NOT", "NULL", "NULLIF", "NUMBER", "NUMERIC", "OBJECT", "OF", "OFFLINE", "OFFSET", "OLD", "ON", "ONLINE", "ONLY", "OPAQUE", "OPEN", "OPERATOR", "OPTION", "OR", "ORDER", "ORDINALITY", "OTHER", "OTHERS", "OUT", "OUTER", "OUTPUT", "OVER", "OVERLAPS", "OVERRIDE", "OWNER", "PAD", "PARALLEL", "PARAMETER", "PARAMETERS", "PARTIAL", "PARTITION", "PARTITIONED", "PARTITIONS", "PATH", "PERCENT", "PERCENTILE", "PERMISSION", "PERMISSIONS", "PIPE", "PIPELINED", "PLAN", "POOL", "POSITION", "PRECISION", "PREPARE", "PRESERVE", "PRIMARY", "PRIOR", "PRIVATE", "PRIVILEGES", "PROCEDURE", "PROCESSED", "PROJECT", "PROJECTION", "PROPERTY", "PROVISIONING", "PUBLIC", "PUT", "QUERY", "QUIT", "QUORUM", "RAISE", "RANDOM", "RANGE", "RANK", "RAW", "READ", "READS", "REAL", "REBUILD", "RECORD", "RECURSIVE", "REDUCE", "REF", "REFERENCE", "REFERENCES", "REFERENCING", "REGEXP", "REGION", "REINDEX", "RELATIVE", "RELEASE", "REMAINDER", "RENAME", "REPEAT", "REPLACE", "REQUEST", "RESET", "RESIGNAL", "RESOURCE", "RESPONSE", "RESTORE", "RESTRICT", "RESULT", "RETURN", "RETURNING", "RETURNS", "REVERSE", "REVOKE", "RIGHT", "ROLE", "ROLES", "ROLLBACK", "ROLLUP", "ROUTINE", "ROW", "ROWS", "RULE", "RULES", "SAMPLE", "SATISFIES", "SAVE", "SAVEPOINT", "SCAN", "SCHEMA", "SCOPE", "SCROLL", "SEARCH", "SECOND", "SECTION", "SEGMENT", "SEGMENTS", "SELECT", "SELF", "SEMI", "SENSITIVE", "SEPARATE", "SEQUENCE", "SERIALIZABLE", "SESSION", "SET", "SETS", "SHARD", "SHARE", "SHARED", "SHORT", "SHOW", "SIGNAL", "SIMILAR", "SIZE", "SKEWED", "SMALLINT", "SNAPSHOT", "SOME", "SOURCE", "SPACE", "SPACES", "SPARSE", "SPECIFIC", "SPECIFICTYPE", "SPLIT", "SQL", "SQLCODE", "SQLERROR", "SQLEXCEPTION", "SQLSTATE", "SQLWARNING", "START", "STATE", "STATIC", "STATUS", "STORAGE", "STORE", "STORED", "STREAM", "STRING", "STRUCT", "STYLE", "SUB", "SUBMULTISET", "SUBPARTITION", "SUBSTRING", "SUBTYPE", "SUM", "SUPER", "SYMMETRIC", "SYNONYM", "SYSTEM", "TABLE", "TABLESAMPLE", "TEMP", "TEMPORARY", "TERMINATED", "TEXT", "THAN", "THEN", "THROUGHPUT", "TIME", "TIMESTAMP", "TIMEZONE", "TINYINT", "TO", "TOKEN", "TOTAL", "TOUCH", "TRAILING", "TRANSACTION", "TRANSFORM", "TRANSLATE", "TRANSLATION", "TREAT", "TRIGGER", "TRIM", "TRUE", "TRUNCATE", "TTL", "TUPLE", "TYPE", "UNDER", "UNDO", "UNION", "UNIQUE", "UNIT", "UNKNOWN", "UNLOGGED", "UNNEST", "UNPROCESSED", "UNSIGNED", "UNTIL", "UPDATE", "UPPER", "URL", "USAGE", "USE", "USER", "USERS", "USING", "UUID", "VACUUM", "VALUE", "VALUED", "VALUES", "VARCHAR", "VARIABLE", "VARIANCE", "VARINT", "VARYING", "VIEW", "VIEWS", "VIRTUAL", "VOID", "WAIT", "WHEN", "WHENEVER", "WHERE", "WHILE", "WINDOW", "WITH", "WITHIN", "WITHOUT", "WORK", "WRAPPED", "WRITE", "YEAR", "ZONE "]

うまくできました。

まとめ

思いつきで作成してみましたが、AWSのページが閉鎖されたりHTML構造が変更されない限り、仕様変更などは気にせずに使えそうです。

あとBeautifulSoup初めて使いましたが、かなり便利。

続きを読む

AWS Elasticsearch Serviceが律儀にgzipを返すようになって死んだ [解決済み]

エラー内容

今朝起きたら、 Elasticsearch + Rails にこんなエラーが起こっていた。

MultiJson::ParseError: unexpected character at line 1, column 1 [parse.c:664]

なんと、いままで Accept-Encoding を無視していた AWS Elasticsearch Service が、急に仕事をしだして、 gzip 形式で返すようになった模様。

追記: なお、 elasticsearch-rails で利用されている faraday は、 Accept-Encoding: gzip がデフォなようです。

バージョン

追記しました

elasticsearch (5.0.0)
elasticsearch-api (5.0.0)
elasticsearch-transport (5.0.0)
elasticsearch-dsl (0.1.4)
elasticsearch-model (0.1.9)
elasticsearch-rails (0.1.9)
elasticsearch-transport (5.0.0)
faraday (0.9.2)
faraday_middleware (0.12.2)
faraday_middleware-aws-signers-v4 (0.1.5)

最新版からは若干古いです。

解決法

faraday_middleware gemを使って、gzipを受け取れるように変更する。

Gemfile

...
gem 'faraday_middleware'
...

config/initializers/aws.rb


Elasticsearch::Model.client = Elasticsearch::Client.new(
  host: ENV.fetch('ELASTICSEARCH_ENDPOINT'),
  port: 80
) do |faraday|
  faraday.use FaradayMiddleware::Gzip # ここを追加
  faraday.request :aws_signers_v4,
                  credentials: Aws::Credentials.new(ENV.fetch('AWS_KEY_ID'), ENV.fetch('AWS_ACCESS_KEY')),
                  service_name: 'es',
                  region: 'ap-northeast-1'
  faraday.adapter Faraday.default_adapter
end

被害者の会

他にも被害者はいる模様。
https://forums.aws.amazon.com/thread.jspa?threadID=223784

Posted on: Sep 26, 2017 10:56 AM

Just my personal experience –
Without any client changes, one day the ES responses stopped working. When I inspected the response, it was gzipped. The client library I was using requested gzip. Since I didn’t make any changes to my client code, I am guessing that the following things are true, but none of them are really proven:

a) the client library had always been requesting gzipped responses ( evidence: https://github.com/elastic/elasticsearch-ruby/issues/457#issuecomment-326204925 )
b) the client library was not equipped to receive gzipped responses ( evidence: my code doesn’t work )
c) the AWS ES service used to ignore the request for gzipped response, and now it honors it. ( evidence: my code used to work )

I was not able to figure out how to get my client library to stop requesting gzipped responses (seems it would require modifying the faraday library), but by changing my code to look like https://github.com/elastic/elasticsearch-ruby/issues/457#issuecomment-326020618 , I was able to get my client library to understand the gzipped response.

Hope those data points help.

ただ、時系列がずれているので、AWSが順次アップデートをしていったのだろうか?
詳細はAWSにコンタクトする予定。

こちらは、ほぼ同時刻の被害者の方
https://qiita.com/s_nakamura/items/49b19f2544aa61229bbc

コメント

gzip対応してないのにAccept-encodingでgzip投げてた利用者側が悪いのでは?

仰る通りです。。
OSSのライブラリを使っている以上、そのライブラリの挙動の責任は利用者にありますね。この記事のタイトルから「AWSやべー」という声が聞こえてきそうですが、どちらかと言うと、「いままでgzip無視しててごめんね、今朝直しといたから」と言った感じですね。

Amazon Elasticsearchのgzipの件、全ノードでgzipが返ってくるわけじゃなくてあるノードでだけgzip返ってくる罠
挙動が戻ってまたエラーになったら…

レスポンスがgzipでない場合、何もせず通過させるので、この変更を入れておけば、仮に挙動がもとに戻っても大丈夫なはずです。

https://github.com/lostisland/faraday_middleware/blob/master/lib/faraday_middleware/gzip.rb#L25

追記 10/4 11:15 p.m. JST

とここまで書いてて気づいたが、 faraday_middleware によると、 Faraday.default_adapter == :net_http を使うと、Gzipは自動解凍されるよう。。。なんだこれ

  # This middleware is NOT necessary when these adapters are used:
  # - net_http on Ruby 1.9+
  # - net_http_persistent on Ruby 2.0+
  # - em_http

追記 10/5 12:10 a.m. JST

真の原因は、これかな?(未検証)

https://github.com/elastic/elasticsearch-ruby/issues/457#issuecomment-326204925
faraday_middleware-aws-signers-v4 は IAM Role から Elasticsearch Service の認証通す middleware

Can this be the reason why compression is enabled by default ?
https://github.com/winebarrel/faraday_middleware-aws-signers-v4/blob/588b8c1086a72e2762ea3834bfff3d9f71b66024/lib/faraday_middleware/request/aws_signers_v4.rb#L70

if Net::HTTP::HAVE_ZLIB
  env.request_headers['Accept-Encoding'] ||= 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3'
end

続きを読む

【Laravel5.4】migrationができない。SQLSTATE[HY000]: General error: 1709 Index column size too large. The maximum column size is 767 bytes.

はじめに

Laravel5.4の環境でmigrationを実行したら、下記エラーが出ました。。。
解決するまでにかなり苦労したので、備忘録的に記載します。

●migration時のエラー
SQLSTATE[HY000]: General error: 1709 Index column size too large. The maximum column size is 767 bytes.

環境

  • OS: CentOS7
  • RDS: Aurora
  • DB文字コード: utfmb4
  • InnoDB

原因

InnoDBの行の最大長が約8KBで、それを超えるテーブルを作成しようとして無理だよと怒られていた模様です。

解決するためにやったこと

下記①と②の対応がどちらも必要そうです(あくまで対応策の1つです)。

①RDSのパラメーターグループを下記の設定に変更

  • innodb_large_prefix: 1
  • innodb_file_format: Barracuda
  • innodb_file_per_table: 1

②Laravelのconfig/database.phpを下記のように編集

●修正前
        'mysql' => [
            'driver' => 'mysql',
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '3306'),
            'database' => env('DB_DATABASE', 'forge'),
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => '',
            'strict' => true,
            'engine' => 'null',

●修正後
        'mysql' => [
            'driver' => 'mysql',
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '3306'),
            'database' => env('DB_DATABASE', 'forge'),
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => '',
            'strict' => true,
            'engine' => 'InnoDB ROW_FORMAT=DYNAMIC', ★ここを修正

調査時に行ったこと

> show variables like '%char%';
  • innodb_large_prefixの確認
> SHOW VARIABLES LIKE '%large_prefix%';
+---------------------+-------+
| Variable_name       | Value |
+---------------------+-------+
| innodb_large_prefix | OFF   |
+---------------------+-------+
1 row in set (0.00 sec)
  • innodb_file_formatの確認
> SHOW VARIABLES LIKE '%file_format%';
+--------------------------+----------+
| Variable_name            | Value    |
+--------------------------+----------+
| innodb_file_format       | Antelope |
| innodb_file_format_check | ON       |
| innodb_file_format_max   | Antelope |
+--------------------------+----------+
3 rows in set (0.00 sec)
  • innodb_file_per_tableの確認
> SHOW VARIABLES LIKE '%file_per_table%';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| innodb_file_per_table | ON    |
+-----------------------+-------+
1 row in set (0.00 sec)
  • テーブルの状態を確認(ここでROW_FOMATがCOMPACTかDYNAMICか判断する)
> use <Database name>;
> show table status like '%<Table name>%';
+-------------+--------+---------+------------+------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+-------------+------------+--------------------+----------+----------------+---------+
| Name        | Engine | Version | Row_format | Rows | Avg_row_length | Data_length | Max_data_length | Index_length | Data_free | Auto_increment | Create_time         | Update_time | Check_time | Collation          | Checksum | Create_options | Comment |
+-------------+--------+---------+------------+------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+-------------+------------+--------------------+----------+----------------+---------+
| wp_comments | InnoDB |      10 | Compact    |    0 |              0 |       16384 |               0 |        81920 |         0 |              2 | 2017-09-08 19:10:27 | NULL        | NULL       | utf8mb4_unicode_ci |     NULL |                |         |
+-------------+--------+---------+------------+------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+-------------+------------+--------------------+----------+----------------+---------+
1 row in set (0.00 sec)

おわりに

ROW_FOMATをCOMPACTから、DYNAMICに変更して、migrationを実行するとうまく動作しました。
MySQL5.7からは、デフォルトでROW_FOMATはDYNAMICになっているようですが、
AWSのAuroraエンジンは、MySQL5.6に準拠してあり、デフォルトだとCOMPACTになっています。

皆様も気を付けてください。。。。

その他メモ

  • テーブルのROW_FOMATをSQL文で変更する。
> use <Database name>;
> ALTER TABLE `<Table name>` ROW_FORMAT=DYNAMIC;
Query OK, 0 rows affected (0.08 sec)
Records: 0  Duplicates: 0  Warnings: 0

続きを読む

AWSでガッチリログ解析

はじめに

現在EC2上で動いているサービスのログを解析することになり、方法を調べた結果AWS上のサービスを使用してする方法がベストだと考え実装した。これらのサービスは比較的に最近できた(たぶん)のであまりこの方法で紹介している日本語の記事がなかった。今でもログ解析といえば、fluentd + Elasticsearch + Kibanaが圧倒的に王道みたいだがわざわざAWSが色々と提供してくれているのでそちらを使う。以下、EC2でサーバーが動きログファイルがあることを前提にしています。インフラの人でもなんでもないので間違いがあれば、優しくご指導ください。

参考資料

この記事で詰まった際は下記を参照ください。また私は下記の資料を通して実装しました。
AWS公式 ログ解析のチュートリアル
AWS Kinesis Firehose
AWS Kinesis Analytics
AWS Elastic Search
Amazon Kinesis Agent

全体図

Screenshot 2017-09-06 02.55.05.png

全体的な図は上記のようになります。画像ではインスタンスで走っているサーバーがApacheとなっていますが、Nginxや他のサーバーを使用でも設定次第で問題にはなりません。

全体の流れ

システムを構築している時、記述通りしているつもりでも簡単な間違いで動かなくなることが多々あります。そんなときに全体の流れ、繋がりをしっかりと理解することによりデバックが容易になります。

  1. EC2インスタンスから、サービスとサービスをつなげる役割を持つAmazon Kinesis Firehose(以Firehose)にログを流す。
  2. このFirehoseはS3 Bucketにデータを流す先としている。
  3. 次に、Amazon Kinesis AnalyticsはFirehoseからS3 Bucketに流れているログデータを解析し、解析後のデータを他のFirehoseに渡す。
  4. 解析後のログを渡されたFirehoseはAmazon Elasticsearch Service(以下Elasticsearch)にデータを受け流す。
  5. Elasticsearchに保存されたデータはkibinaを通じデータを可視化し、ユーザーにたどり着く。

注意: ヘッダーの右上からRegionをN.Virginiaにしてから以下を進めてください。(N.Virginia を含む特定のRegionでしかここで使用するAmazon Elasticsearch Serviceがないためです。)

ステップ1:一つ目のFirehoseを作成

EC2インスタンスとS3へログを流すFirehoseを作成します。
1. Amazon Kinesis へアクセスします。

2. Firehoseへ行き、 Create Deliver Stream(デリバリーストリームを作成) ボタンを押し作成します。

3. Delivery stream nameを入力しますが、ここはこのFirehoseの名前なのでなんでも構いません。何を入力すればよいかわからない方は 「log-ingestion-stream」としましょう。

4. 次に情報元をDirect PUT or other sources(直接のPUTまたは他の情報源)に選択します。

先にお話したとおりFirehoseは情報を一つの場所からもう一つの場所へ移行させる役割を持ちます。ここではKinesis StreamまたはDirect PUT or other sourcesの選択肢があり、Kinesis Streamを情報元にしている場合はKinesis Streamからそれ以外はDirect PUT or other sourcesを選択します。今回の情報源はEC2なのでDirect PUT or other sourcesとなります。

5. 一番下まで行き、Nextボタンを押します。押した後、次の選択肢はデフォルトのDisabledを選択します。

Firehoseではただ情報を受け渡すだけではなく、AWS Lambdaを使用して情報に変更を加えてから渡すことができるようです。ここではそれの選択肢として、Disable(しない)とEnable(する)があります。私は使用したことがないのでこれ以上の解説はできません。

6. Nextボタンを押し、次は送り先(Destination)をAmazon S3に選択します。

一つ目はEC2からS3へ情報(データ)を送ります。

7. S3のバケットとして、ログの保管する場所を指定します。ここではCreate Newボタンを押してバケットの名前をなんでも構いませんのでつけてください。何にすればいいかわからない方は「log-bucket」としてください。

8. Nextボタンを押し、一番したまで行けば、IAM roleをしていするフォームがありますので、Create new or Chooseボタンを押しましょう。

9. タブが開けば、Create new IAM roleを選択したまま他はデフォルトで右下のAllowを押します。

10. 完了すればNextを押し確認に問題がないようでしたらCreate delivery streamボタンを押し、作成します。

ステップ2:Amazon Kinesis AgentをEC2にインストール

先程作成した、firehoseにEC2からログデータを送るために、AWSが公式に開発しているJAVA製のEC2からFirehoseにリアルタイムでファイル情報を送ってくれるAmazon Kinesis Agentをインストールしましょう。ここが最も間違いが起きやすいところかと思います。

1. ダウンロード方法を参考にAmazon Kinesis Agentインストールしましょうしてください。

yumが入っていれば以下のコマンドでインストールできます。

sudo yum install –y aws-kinesis-agent

私の場合は、 Java JDEなかったのでtools.jar が見つからないというようなエラーが出ました。そのようなエラーが出た方は

sudo apt-get install openjdk-7-jdk

JDKをインストールしましょう。(参考)

2. agentの設定をする

amazon kinesis agentに「どのファイルログのデータをどこに送るのか」または「どのような形で送るのか」という事などを設定していきます。
Amazon Kinesis Agentの設定ファイルは /etc/aws-kinesis/agent.jsonにあるかと思います。そのファイルを以下のように設定してください。
なお、
filePattern: "full-path-to-log-file"full-path-to-log-file解析したいログファイルへのフルパスに(nginxをご使用の方は etc/nginx/access.log かと思います)してください。

deliveryStream: "name-of-delivery-stream"name-of-delivery-streamを送りたいfirehoseの名前に(ステップ1で作成したfirehoseの名前)してください。

/etc/aws-kinesis/agent.json
{
  "cloudwatch.endpoint": "monitoring.us-east-1.amazonaws.com",
  "cloudwatch.emitMetrics": true,
  "firehose.endpoint": "firehose.us-east-1.amazonaws.com",
  "flows": [
  {
    "filePattern": "full-path-to-log-file",
    "deliveryStream": "name-of-delivery-stream",
    "dataProcessingOptions": [
    {
      "initialPostion": "START_OF_FILE",
      "maxBufferAgeMillis":"2000",
      "optionName": "LOGTOJSON",
      "logFormat": "COMBINEDAPACHELOG"
    }]
 }
 ]
}

長くなってしまいますが、親切にするためにはここで気にしなくてはならないことが幾つかあります。

エンドポイント(firehose.endpoint)

一番初めの注意通り、N.VirginiaにFirehoseを作成している方はなんの問題もありませんが、そのようにしていない方はエンドポイント一覧を参考にして, cloudwatch.endpointfirehose.endpointを変更してください。なお、はじめの注意でも述べたようにElasticsearchでは数少ないリージョンにしか対応していないため、他のリージョンにしている方は最後になってやり直さなければならない可能性もあります。

情報処理の設定(dataProcessingOptions)

Apacheを使用している方はなんの問題もありませんが、nginxを使用している方はここに少し変更が必要です。こちらの設定でログデータの処理方法を変更できます。
設定のオプションとしてmatchPatternがあり、こちらと正規表現を使用してどのようなログフォーマットでも処理が可能になります。下のものは処理をしたいログとそのmatchPatternの一つの例ですので参考にしてご自身のものを変更してください。(参照)

111.111.111.111 - - [02/Dec/2016:13:58:47 +0000] "POST /graphql HTTP/1.1" 200 1161 "https://www.myurl.com/endpoint/12345" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36" 0.172 "query user userMessages hasPermissions F0 F1" 11111;222222
{
    "dataProcessingOptions": [
    {
      "optionName": "LOGTOJSON",
      "logFormat": "COMMONAPACHELOG",
      "matchPattern": "^([\d.]+) - - \[([\w:/]+).*\] "(\w+) /(\w+).*(\d.+)" (\d{3}) (\d+) "(\S+)" "(.*?)" ([\d.]+) "(.*?)" (\d+);(\d+)",
      "customFieldNames": ["client_ip", "time_stamp", "verb", "uri_path", "http_ver", "http_status", "bytes", "referrer", "agent", "response_time", "graphql", "company_id", "user_id"]
    }]
}

3. Amazon Kinesis Agent始動

以下のコマンドでagentを動かします。

sudo service aws-kinesis-agent start

これで終わってもいいのですが、Agentのログファイルでしっかり動いていることを確認しましょう。

tail -f /var/log/aws-kinesis-agent/aws-kinesis-agent.log

tailコマンドを利用してAgentの活動ログを監視します。無事動いているようなら

2017-09-08 08:47:29.734+0000 ip-10-0-0-226 (Agent.MetricsEmitter RUNNING) com.amazon.kinesis.streaming.agent.Agent [INFO] Agent: Progress: 9135 records parsed (1257812 bytes), and 9132 records sent successfully to destinations. Uptime: 88380043ms

このようなログが定期的に流れてきます。

考えられるエラーと解決方法を紹介します

agentがログファイルを読めていない

考えれる原因の考えられる原因は、
1. ログファイルへのフルパスが間違えている
2. ログファイルを読む権限がない

1.のフルパスが間違えている場合は、先程のagentの設定ファイルで変更した。”filePattern”のパスに問題があることになるので、そのパスが実際存在するか確認しましょう。

tail ログファイルへのパス

file does not exists 的なメッセージがでたらファイルが存在しないということなので正しいログファイルへのフルパスに変えてあげましょう。

2.のログファイルを読む権限がない場合は、agentがログファイルを読み込めれるように

sudo chmod o+r ログファイルへのパス

で誰でもログファイルを読めるようにしましょう。(インフラエンジニア的にこれはありなのか疑問)

ログデータの送り先が不在

これが該当する場合は、確か404のエラーコードを大量にログファイルに出力されます。
この場合は、agentのログファイルのdeliveryStreamのプロパティの名前と一番初めに作成したAmazon Kinesis Firehoseの名前があっていることを確認しましょう。
それがタイポもない場合はagentを再インストールしたらなおるかもしれません。

アクセスキーとシークレットキーがみつからない

credentialsなのでAWSのアクセスキーとシークレットキーが見つからない方は、awsのcredentialsに登録してもいいですが、確実なのはagentの設定ファイルで設定することです。

/etc/aws-kinesis/agent.json
{
  "awsAccessKeyId": "あなたのアクセスキー",
  "awsSecretAccessKey": あなたのシークレットキー
}

ステップ3:Amazon Elasticsearch Serviceドメインの作成

  1. Elasticsearchのページに行き、新たなドメインを作成するためにGet StartもしくはCreate a new domainのボタンをクリックします。
  2. はじめにドメインに名前をつけます。他と同じくなんでも構いませんが、何をつければいいのかがわからない方は「log-summary」にでもしましょう。
  3. 次の選択肢はElasticsearchのバージョンですが、特にこだわりがないかたは最新でものでいいと思います。なのでそのままにして、Nextボタンをおしましょう。
  4. 次の設定も同じく特にこだわりがない方はそのままにしておいて、Nextボタンをおして次にいってください。
  5. その次では、このElasticsearchへのアクセスを制限をします。Templateから簡単に設定ができますのでそちらから各自設定してください。難しいことはわからないしめんどくさいのも嫌な方はTemplateからAllow open access to the domainを選択してください。こちらは特に制限なくアクセスが可能になるのでAWS側ではおすすめしていません。重要なデータをお使う場合は必ずきちんと設定しましょう。
  6. 次の画面でもろもろの設定を確認してConfirmボタンをおしましょう。そうすればElasticsearchドメインが作成されます。(起動までしばらく時間がかかります)

ステップ3:二つ目のFirehoseの作成

次に先程作成したAmazon Elasticsearch Serviceへデータを送るためにfirehoseを作成します。これは、EC2から一つ目のFirehoseを通じ、(まだ作ってない)Amazon Kinesis Analyticsにより処理された情報をElasticsearchに送るためのFirehoseです。

  1. Amazon Kinesis へアクセスします。
  2. Firehoseへ行き、 Create Deliver Stream(デリバリーストリームを作成) ボタンを押し作成します。
  3. Delivery stream nameを入力しますが、ここはこのFirehoseの名前なのでなんでも構いません。何を入力すればよいかわからない方は 「log-summary-stream」としましょう。
  4. 次に情報元をDirect PUT or other sources(直接のPUTまたは他の情報源)に選択します。
  5. 一番下まで行き、Nextボタンを押します。押した後、次の選択肢はデフォルトのDisabledを選択します。
  6. Nextボタンを押し、次は送り先(Destination)をAmazon Elasticsearch Serviceに選択します。
  7. 送り先のElasticsearchとして、先ほど作成したドメインを選択します
  8. Indexは、request_dataにして、typesはrequestsにします。その他のIndex RotationとRetry durationはそのままにしておいてください。
  9. Elasticsearchに送るのを失敗した場合にS3にバックアップとしてデータを送ります。Backup S3 bucketとして新しく、バケットを作成しましょう。名前はなんでも構いませんが、思いつかない方は「log-summary-failed」とでもしておいてください。
  10. Nextボタンを押し、一番したまで行けば、IAM roleをしていするフォームがありますので、Create new or Chooseボタンを押しましょう。
  11. タブが開けば、Create new IAM roleを選択したまま他はデフォルトで右下のAllowを押します。
  12. 完了すればNextを押し確認に問題がないようでしたらCreate delivery streamボタンを押し、作成します。

ステップ4: Amazon Kinesis Analytics を作成

Amazon Kinesis Analyticsでははじめに作成したEC2からログデータを取ってくる一つ目のFirehoseと先ほど作成したElasticsearchを目的地にしている二つ目のFirehoseの中間にあるものになります。つまりは一つ目のFirehoseからログ情報を所得し、それを処理した後、二つ目のFirehoseに渡しそれがElasticsearchへ流れ着きます。では実際に作っていきましょう。

  1. Amazon Kinesis Analyticsへアクセスする。
  2. Create Applicationに行き、Applicationの名前をつけます。何をつければいいかわからない方は、log-aggregationとでもしておいてください。なんでも構いません。
  3. Descriptionの方も何か書きておいた方は書いていただき、特にわからない方は空欄のままCreate Applicationボタンをおしましょう。

情報元(Source)を選択する

  1. 作成した後、作成されたアプリケーションのホームページに行くと思います。 そのままConnect to a sourceボタンをおして情報元を選択します。
  2. 情報元を選択するページに行くと2つのFirehoseの名前が出てくると思いますが、ここのステップのはじめにも紹介したように一つ目のFirehose(EC2から情報を取ってきている方)を選択してください。
  3. 選択した後しばらく待つと、流れてきているログをAnalyticsアプリケーションが所得するので、それが確認出来しだいSave and continueボタンをおしましょう。

SQLを使用して処理をする

今まではクリックするだけだっだのですが、ここがおそらく最も大変なところです。SQLをもともとちゃんと書けるエンジニアは簡単に実装できていいじゃんという感じですが、私みたいなエセエンジニアは困ったものです。

Go to SQL editorでFirehoseにホーム画面からSQLおエディターに移動できます。
ここで好きなようにデータを処理変更してくださいと言っても何をどうすればいいかわかんない方がいると思います。
このSQLではtableの代わりにstreamを変更します。SOURCE_SQL_STREAM_001が情報が流れているstreamでこちらからログ情報を取ってきて、処理をしDESTINATION_SQL_STEAMという名前のストリームに変換します。そうしたらそのストリームを次のFirehoseに送ってくれます。
以下に一つの処理方法の例を載せておきます。これはresponseというカラムの数の合計をstatusCountに入れています。つまりresponse(200, 404とか)の数をまとめて数を数えているということです。
ここからわかることは、”(ダブルクオーテーションマーク)で囲むことで、SOURCE_SQL_STREAM__001のカラムの値が手に入ることです。

CREATE OR REPLACE STREAM "DESTINATION_SQL_STREAM" (
 datetime VARCHAR(30),
 status INTEGER,
 statusCount INTEGER);
CREATE OR REPLACE PUMP "STREAM_PUMP" AS
 INSERT INTO "DESTINATION_SQL_STREAM"
 SELECT
 STREAM TIMESTAMP_TO_CHAR('yyyy-MM-dd''T''HH:mm:ss.SSS',
LOCALTIMESTAMP) as datetime,
 "response" as status,
 COUNT(*) AS statusCount
 FROM "SOURCE_SQL_STREAM_001"
 GROUP BY
 "response",
 FLOOR(("SOURCE_SQL_STREAM_001".ROWTIME - TIMESTAMP '1970-01-01 00:00:00') minute / 1 TO MINUTE);

こちらが僕が実際に使っているコードです。 SOURCE_SQL_STREAM_001のdatetimeのフォーマットyyyy-MM-dd''T''HH:mm:ss.SSSのように変換してElasticsearchへ保存しています。

CREATE OR REPLACE STREAM "DESTINATION_SQL_STREAM" (
 datetime VARCHAR(30),
 host VARCHAR(16),
 request VARCHAR(32),
 response INTEGER,
 bytes INTEGER,
 agent VARCHAR(32));
CREATE OR REPLACE PUMP "STREAM_PUMP" AS
 INSERT INTO "DESTINATION_SQL_STREAM"
 SELECT
 STREAM 
 TIMESTAMP_TO_CHAR('yyyy-MM-dd''T''HH:mm:ss.SSS', CHAR_TO_TIMESTAMP('dd/MMM/yyyy:HH:mm:ss Z', "datetime")) as datetime,
 "host" as host,
 "request" as request,
 "response" as response,
 "bytes" as bytes,
 "agent" as agent
 FROM "SOURCE_SQL_STREAM_001"

より詳しくは公式ページドキュメントを参照してください。
コメントで質問していただければ頑張って答えます。

目的地を選択する

そのままDestinationタブを選択して、処理された情報の送り先を選択します。Select a streamから二つ目のFirehose(Elasticsearchにつながっている方)を選択し、他のものはそのままでSave and continueをクリックしましょう。

これで全てのパイプラインは繋がり作業はほとんど完成です。

ステップ5:Kibanaで可視化されたデータをみる

Amazon Elsacticsearch serviceにはデータを可視化してくれるKibanaが含まれています。

Elasticsearchの作成したドメインのホーム画面に行きkibanaという文字の横にリンクが有ると思います。そこをクリックするとKibinaのページに飛びます。はじめて訪れた場合はindex-patternを入力しなければなりません。そこにはrequest_dataと入力してください。

仮に、request_dataと入力しても続けれない方はしばらく時間をおいてみてもう一度試してください。データがElasticsearchに貯まるまで時間がかかるためだと思われます。
KibanaはDatetimeのカラムを自動で認識します。ただし、フォーマットに制限があるらしくyyyy-MM-dd''T''HH:mm:ss.SSSのような形で情報を保存すればKibanaは必ずDatetimeだと認識します。

そのまま続行を押すとKibanaのサイトで自由に可視化されたデータをみれます。

長い作業、お疲れ様でした。他にはAmazon Kinesis Analyticsでデータを複雑に処理をしたりも可能なので挑戦してみてください。
編集リクエストお待ちしております。m(_ _)m

続きを読む

MySQLの全文検索の性能調査結果

MySQLの全文検索と、like検索を比較し、性能にどのくらい差があるのか検証した。

参考URL:Amazon RDS for MySQL と全文検索

検索対象

  • 1レコード24000桁の文字データを約9000件入れたテーブルを作成。
  • データは青空文庫から取得

環境

  • AWSを利用
  • EC2:t2.micro
  • RDS:db.t2.micro

検証手順

1.テーブルを作成

CREATE TABLE `search` ( 
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 
  `search_text` text,   
  r_datetime datetime,  
  u_datetime datetime,  
  PRIMARY KEY (`id`),   
  FULLTEXT INDEX search_text (search_text) WITH PARSER ngram    
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;

2.テーブルにデータを入れる。PHPのプログラムで約5分かかった。
3.データ取得時間を計測

SELECT count(*) from search where match(search_text)against('確信にみちみちた' in boolean mode);

処理時間:0.03sec

SELECT count(*) from search where search_text LIKE '%確信にみちみちた%';

処理時間:9.71sec

SELECT count(*) from search where match(search_text)against('電車にのった' in boolean mode);

処理時間:0.02sec

SELECT count(*) from search where search_text LIKE '%電車にのった%';

処理時間:0.51sec

LIKEで検索する場合の数十倍の速さで検索できることが検証できた。

続きを読む