RDSのスロークエリログをmysqldumpslowに突っ込む

JSON形式で取得されるRDS(MySQL)のスロークエリログをcatして絶望したときに、心を落ち着けてmysqldumpslowに突っ込めるようにします。

用意するもの

  • awscli
  • jq
  • mysqldumpslow 1

スロークエリログのダウンロード

スロークエリログのリストを取得

$ aws rds describe-db-log-files \
  --db-instance-identifier hoge-instance \
  | jq '.DescribeDBLogFiles[].LogFileName' -r  \
  | grep slow
slowquery/mysql-slowquery.log
slowquery/mysql-slowquery.log.0
...

スロークエリログのダウンロード

describe-db-log-files取得されたファイル名には/が含まれていて邪魔なので、ファイルに保存するときに取っています。

for log in $(cat slowlog-list.txt); do \
  aws rds download-db-log-file-portion \
  --db-instance-identifier hoge-instance \
  --log-file-name $log \
  > $(echo $log | cut -d / -f 2); done

スロークエリログの読み込み

取得されたスロークエリログの中身はJSONデータの奥に格納されたテキスト文字列なので、jqで抽出してからmysqldumpslowに食わせます。

$ cat mysql-slowquery.log \
  | jq -rs .[].LogFileData \
  | mysqldumpslow -
# 遅い順にソートされたクエリたち...

  1. Ubuntuではmysql-clientをインストールするとついてきます。 

続きを読む

jqでちょっぴり複雑な検索をする

 jqコマンドで配列の要素かつ複雑な構造をもつ要素(ここではaws ec2 describe-instancesの結果)に対して検索を行ってみました。

はじめに

 株式会社アイリッジのCommon Lisp大好きサーバサイドエンジニア、tanaka.lispです。

 AWS EC2を利用していると、特定のインスタンスの情報(インスタンスIDやインスタンスタイプ、などなど…)を知りたくなることがあります。ただ、そのためだけに管理コンソールをぽちぽちして取得していると、なんだか負けた気がするし(これ大事)、シェルスクリプトなどで結果を利用するときに不便です。

 awscliaws ec2 describe-instancesで取得した結果をjqに食わせ、インスタンスにつけている名前からイイかんじに検索・整形したいところですが、awscliはけっこう複雑な返り値を返してきますよね…:

{
  "Reservations": [
    "Instances": [
      {
        "InstanceId": "i-commonlisp",
        "Tags": [
          {
            "Key": "Name",
            "Value": "app.commonlisp"
          },
          {
            "Key": "aws.cloudformation:stack-name",
            "Value": "stack.commonlisp"
          }
        ],
        ...
      },
      ...
    ],
    ...
  ]
}

 そこで、名前(TagsKeyNameの値)によるインスタンスの検索方法を試行錯誤しました。

よういするもの

  • jq: jq-1.5-1-a5b5cbe
  • awscli: aws-cli/1.11.58 Python/3.6.0 Linux/4.8.0-41-generic botocore/1.5.21

jqでの検索

 jqでは入力のJSONに対して、以下のようなことができます:

  • 入力JSONの整形 ({"id": .InstanceId, "tags": .Tags})
  • 入力JSONからのデータ抽出 (select)

 今回はこれらを組み合わせて、aws ec2 describe-instancesの結果から探しているインスタンスのインスタンスID(InstanceId)を取得してみます。

入力の整形

 たとえばこんなJSONオブジェクトがあって、

[
  {
    "name": "Lisp",
    "developer": "John McCarthy"
  },
  {
    "name": "Common Lisp",
    "developer": "ANSI X3J13 committee",
    "spec": "ANSI INCITS 226-1994 (R2004)"
  },
  {
    "name": "Arc",
    "developer": "Paul Graham",
    "books": [
      "On Lisp",
      "Hackers and Painters"
    ]
  },
  {
    "name": "Clojure",
    "developer": "Rich Hicky"
  }
]

言語名(lang)と開発者名(developer)だけがほしいとき、入力をprintf的に整形できます。

$ echo '...上のJSON...' \
   | jq '.[] | {"lang": .name, "developer": .developer}'
{
  "lang": "Lisp",
  "developer": "John McCarthy"
}
{
  "lang": "Common Lisp",
  "developer": "ANSI X3J13 committee"
}
{
  "lang": "Arc",
  "developer": "Paul Graham"
}
{
  "lang": "Clojure",
  "developer": "Rich Hicky"
}

要素の抽出のようにも思えますが、別にもっと抽出っぽいオペレータがあるので、こちらは整形と呼びました。

入力からのデータ抽出

 条件を満たすもののみをpass throughするというオペレータもあって、それがselectです。こちらを抽出とここでは呼んでいます。

 manにあることがすべてなのですが、

   select(boolean_expression)
       The function select(foo) produces its input unchanged if foo returns true for that input, and produces no output otherwise.

       It´s useful for filtering lists: [1,2,3] | map(select(. >= 2)) will give you [2,3].

正規表現を組み合わせたりできるので強力です;

$ echo '...上のJSON...' \
  | jq '.[] | .name |select( .| test("Lisp"))'
"Lisp"
"Common Lisp"

実際に抽出

Exactlyな名前でインスタンスの検索

 では実際に検索クエリをつくってみます。

 まずはインスタンスリストを得て、

$ aws ec2 describe-instances | jq '.Reservations[].Instances[]'
{
  "ImageId": "ami-commonlisp",
  "State": {
    "Code": 16,
    "Name": "running"
  }
}
{
...  # ずらずら出るので省略

そこにTagsKeyNameValueがExactlyapp.commonlispの要素だけ真になる条件を加えます(ルシのファルシがパージでコクーン感ある)。

$ aws ec2 describe-instances | jq '.Reservations[].Instances[]
  | select(.Tags[].Key == "Name" and .Tags[].Value == "app.commonlisp")
{
  "InstanceId": "i-commonlisp",
  ...  # ずらずら出るので省略
}

 ついでに、不要な要素は出ないようにしましょう。

$ aws ec2 describe-instances | jq '.Reservations[].Instances[]
  | select(.Tags[].Key == "Name" and .Tags[].Value == "app.commonlisp")
  | {"instance-id": .InstanceId, "tags": .Tags}'
{
  "instance-id": "i-commonlisp",
  "tags": [
    {
      "Key": "Name",
      "Value": "app.commonlisp"
    },
    {
      "Key": "aws:autoscaling:groupName",
      "Value": "commonlisp-ApplicationFleet"
    },
    ...  # タグが出るので省略
  ]
}

名前うろおぼえインスタンスの検索

 なんかappって名前をつけたような気がするけどなー、まったく思い出せない。俺たちは雰囲気でサーバを立てている。そんなアナタに捧ぐ。主にぼく自身に捧ぐ :angel:

$ aws ec2 describe-instances | jq '.Reservations[].Instances[]
  | select( .Tags[].Key == "Name" and (.Tags[].Value | test("^app")))
  | {"instance-id": .InstanceId, "tags": .Tags}'
{
  "instance-id": "i-commonlisp",
  "tags": [
    {
      "Key": "Name",
      "Value": "app.commonlisp"
    },
    {
      "Key": "aws:autoscaling:groupName",
      "Value": "commonlisp-ApplicationFleet"
    },
    ...  # タグが出るので省略
  ]
}
{
  "instance-id": "i-clojure",
  "tags": [
    {
      "Key": "Name",
      "Value": "app.clojure"
    },
    {
      "Key": "aws:autoscaling:groupName",
      "Value": "clojure-ApplicationFleet"
    },
    ...  # タグが出るので省略
  ]
}

おわりに

 jqでの検索について、以下のことを述べました:

  • 要素を検索する基本的な方法
  • 対象要素のさらに中の辞書も検索条件にする方法

 jqってよくできたDSLですね。jqはチューリング完全でもあるらしく、まさしくJSON時代のawk感があります。


 ところでjqで検索するだけなのになぜ記事が長くなるのか :sob:

続きを読む