AWS IoT Enterprise Buttonを使ってSlack通知ボタンを作る

こんにちは。スタディプラスでインフラ周りを担当している id:rmanzoku です。 先日、国内での発売が開始されたAWS IoT Enterprise Buttonを使ってオフィスの小さな改善を行ったのでご紹介します。

f:id:rmanzoku:20180522171742p:plain

弊社の課題

弊社のオフィスは、ビルの4Fと6Fに分かれており、6Fは会議室となっております。 6Fは「なんとなく自分が最後だ」、という人が施錠しSlackにて報告していました。

f:id:rmanzoku:20180522171736p:plain

(採用面接がある日は遅くなりがち)

この報告は忘れやすいですし、毎日行うことから非常に手間です。 今回、この報告をIoTボタンを使って簡略化することにしました。

AWS IoT Enterprise Buttonとは

先日、国内で発売されたIoTボタンです。 以前、Amazon Dashボタンを見て、ハックを試みたエンジニアの方は多いはずです。

このIoTボタンでは、なんと、リンクしたAWS Lambdaを呼び出すことができます!

実装

今回、解決したい課題は「施錠したときの報告を簡略化したい」でした。 ですので、シンプルにSlackへ通知するボタンを実装することにしました。

アクティベートの方法は公式ブログが詳しいです。

サンプルアプリケーションによると、次のようなJSONがLambda eventとして渡されます。

{
    "deviceInfo": {
        "deviceId": "GXXXXXXXXXXXXXXX",
        "type": "button",
        "remainingLife": 98.7,
        "attributes": {
            "projectName": "Sample-Project",
            "projectRegion": "us-west-2",
            "placementName": "Room-1",
            "deviceTemplateName": "lightButton"
        }
    },
    "deviceEvent": {
        "buttonClicked": {
            "clickType": "SINGLE",
            "reportedTime": 1521159287205
        }
    },
    "placementInfo": {
        "projectName": "Sample-Project",
        "placementName": "Room-1",
        "attributes": {
            "key1": "value1"
        },
        "devices": {
            "lightButton":"GXXXXXXXXXXXXXXX"
        }
    }
}

この値のうち、placementInfo内は、ある程度自由に入力できます。 また、IoT 1-Clickのコンソールを見ると、 placementNameにIoTボタンのある場所、attributesにカスタマイズ情報を入力する思想ということが推察できます。

f:id:rmanzoku:20180522171746p:plain

画像のようにAttributeを設定し、 次のLambdaスクリプトを実行するようにします。 (弊社はRubyの強い会社ですが、手慣れているPythonです)

import os
import json
import logging
import urllib.request
import urllib.parse


logger = logging.getLogger()
logger.setLevel(logging.INFO)


def lambda_handler(event, context):
    webhook_url = os.environ.get('WEBHOOK_URL')

    logger.info('Received event: ' + json.dumps(event))

    text = '''
    %s %s
    ''' % (
        event["placementInfo"]["placementName"],
        event["placementInfo"]["attributes"].get("msg")
    )

    body = {
        "link_names": 1,
        'username': event["placementInfo"]["attributes"].get("username", "AWS IoT"),
        'text': text,
        'icon_emoji': event["placementInfo"]["attributes"].get("icon_emoji", ":aws:")
    }

    encoded_post_data = urllib.parse.urlencode({"payload": body}).encode(encoding='ascii')
    urllib.request.urlopen(url=webhook_url, data=encoded_post_data)

内容は至ってシンプルなSlack Incoming webhookのスクリプトです。GitHub

WEBHOOK_URLはLambdaの環境変数から指定することにし、 SlackのusernameとiconはAttributeで渡すようにしました。

結果

IoTボタンを施錠記名簿のところに置くことで簡単にSlackへ施錠通知ができるようになりました。

f:id:rmanzoku:20180522171739p:plain

イケているオフィスや自宅などではある程度自動化が可能かもしれませんが、弊社オフィスのように自動化できない状況は多いと思います。 なにか1つだけでも改善することで、働きやすいオフィスに近づいていけると思います。

今回の例では、改善案を思いついて1時間くらいで実装が終わりました。 小さな改善でもすぐに実行して結果が出せるのはエンジニアの楽しみの1つだと思いました。

pt-query-digestによるパフォーマンス測定

筋トレっていいですよね。 💪

BIG3の総重量が280kg(5RM)になりました。サーバーサイドエンジニアの花井(id:hiroyuki-hanai)です。

弊チームではサービスのコアになるAPIをRailsとMySQLをベースに開発しております。 インフラにはAWSを利用しています。

スタディプラスを初期から支えているAPIなので、モノリシックな部分や性能のよろしくない部分が少なからず存在しております。

先日、突然DBサーバーのCPUがスパイクしてサービスにアクセスしづらくなる障害が発生しました。

今回はサービスに影響を与えてしまった原因をあぶり出すために実施した、pt-query-digestによる性能改善の取り組みを紹介します。

intro

RDSの一般クエリーログやスロークエリログでもよかったのですが、本番のRDSに負荷をかけず、サーバーの設定を変更しなくても実行できるような方法が求められました。 また、本番のサーバーはオートスケーリングによっていつのまにかいなくなってしまうことがあるので、ローカルにログを残すわけにはいきませんでした。

上記の事情から、EC2上でtcpdumpを取得してS3へ結果を流すようにしました。


注: long_query_time に01.以上の値を指定することで、1秒未満のスロークエリについても記録ができるようになるそうです。 参考 - Amazon RDS における MySQL の既知の問題と制限


what is pt-query-digest?

pt-query-digestはpercona toolkitの一つで、slow logなどからMySQLのクエリを解析して、見やすい統計情報を表示してくれます。

what we did?

tcpdumpをいったんS3へ送り、サービスとは切り離されたサーバー上でpt-query-digestによる解析を実行します。


Step1

こちらを参考に、ログを取りたいサーバーでtcp dumpを取得し、S3へ流します。

# tcpdump  -G60 -w - -W1 port 3306 | aws s3 cp - s3://example.com/slow-query/dump.cap

各オプションについては以下のような意図で指定しました。

tcpdump

  • -G60 : 本番環境で実行するので、他の通信へ影響がでないように/出ても軽微で済むように、60秒で様子見したい。
  • -w - : S3へ送るために、キャプチャ結果を標準出力に書き出します。
  • -W1 : サンプリング目的なので、とりあえず1ファイルに収まる範囲で見たい。

aws s3 cp

  • - : パイプの前半部分から流れてきた標準出力をそのまま受けます。

Step2

こちらを参考に、取得したtcpdumpをtxtファイルにして、pt-query-digestにかけます。

まず、S3から解析を行うサーバーへファイルを持ってきます。

$ aws s3 cp s3://example.com/slow-query/dump.cap ~/

pt-query-digestにかけます。

$ tcpdump -r dump.cap -s 65535 -x -n -q -tttt | pt-query-digest --type tcpdump >> dump.txt

オプションについては tcpdump

  • -x : パケットを16進数で表示します。
  • -n : アドレスやポート番号をそのまま出します。
  • -q : 限定したプロトコルだけの簡易的な表示にします。
  • -tttt : タイムスタンプを見やすい形(年-月-日 時:分:秒.ミリ秒)にします。指定しないと時間だけ出ます。

(このオプションはマニュアルの方で、tcpdumpを扱う時のおまじない的に指定して欲しい旨が書かれています。)


  • -r dump.cap : 読み込むファイルを指定します。
  • -s 65535 : CentOS 6のデフォルトサイズに従っておきます。0を指定すると全部になります。

output

こうして以下のような解析結果が得られます。

# Query 2: 55.40 QPS, 0.15x concurrency, ID 0xXXXX at byte 305728800
# This item is included in the report because it matches --limit.
# Scores: V/M = 0.00
# Time range: 2018-04-17 04:52:29.618996 to 04:53:29.005168
# Attribute    pct   total     min     max     avg     95%  stddev  median
# ============ === ======= ======= ======= ======= ======= ======= =======
# Count          6    3290
# Exec time      6      9s     3ms    16ms     3ms     3ms   426us     3ms
# Rows affecte   0       0       0       0       0       0       0       0
# Query size     8 776.48k     235     243  241.68  234.30    0.00  234.30
# Warning coun   0       0       0       0       0       0       0       0
# String:
# Hosts        XX.XX.XX.XX
# Query_time distribution
#   1us
#  10us
# 100us
#   1ms  ################################################################
#  10ms  #
# 100ms
#    1s
#  10s+
# Tables
#    SHOW TABLE STATUS LIKE 'xx_xxxs'\G
#    SHOW CREATE TABLE `xx_xxxs`\G
# EXPLAIN /*!50100 PARTITIONS*/
SELECT  `xx_xxxs`.* FROM `xx_xxxs` WHERE `xx_xxxs`.`user_id` = xxx AND `xx_xxxs`.`xxxxx_id` = xxx ORDER BY created_at desc LIMIT 1\G

# Query 3: 51.35 QPS, 0.13x concurrency, ID 0x9DXX at byte 71527245
# This item is included in the report because it matches --limit.
# Scores: V/M = 0.00
# Time range: 2018-04-17 04:52:29.616751 to 04:53:28.978729
# Attribute    pct   total     min     max     avg     95%  stddev  median
# ============ === ======= ======= ======= ======= ======= ======= =======
# Count          5    3048
# Exec time      5      7s     2ms     5ms     2ms     2ms    91us     2ms
# Rows affecte   0       0       0       0       0       0       0       0
# Query size     3 315.27k     104     106  105.92  102.22       0  102.22
# Warning coun   0       0       0       0       0       0       0       0
# String:
# Hosts        10.3.2.218
# Query_time distribution
#   1us
#  10us
# 100us
#   1ms  ################################################################
#  10ms
# 100ms
#    1s
#  10s+
# Tables
#    SHOW TABLE STATUS LIKE 'xxxs'\G
#    SHOW CREATE TABLE `xxxs`\G
# EXPLAIN /*!50100 PARTITIONS*/
SELECT  1 AS one FROM `xxxs` WHERE `xxxs`.`xxxxx_id` = xxxxx AND `xxxs`.`user_id` = xxxxx LIMIT 1\G

この結果から、上位20件のうち15%ほどが同じテーブルに対するアクセスであることが判明しました。 詳しく調査してみたところ、n+1問題が発見され、問題の解消をすることができました。

conclusion

今回は問題が起きてからその原因を特定するために利用しましたが、後日こうした情報をきちんと監視して、改善につなげていく必要があるという話になりました。 上記コマンドをcronで定期的に実行するなどしていく予定です。

スタディプラス開発者ブログ始めました

はじめに

はじめまして、学習管理プラットフォーム「Studyplus」を運営していますスタディプラス株式会社のCTOの島田です。

www.studyplus.jp

この度、弊社でも開発者ブログを始めることになりました。
最初の記事として、このブログを始めるにあたって考えた事を書きたいと思います。

目的

ブログの大きな目的としては採用活動への貢献があります。
その他にも以下のような事が目的として上げられると思っています。

  • スタディプラスという会社の認知度・理解度のUP
  • 求職者への期待値調整。実際に入社したがギャップがありガッカリするといったことがないようにする
  • 採用活動中以外での定期的な外部への発信
  • メンバーが発信を意識した業務への取り組み
    • 社内でのナレッジ共有
    • 技術的チャレンジの促進と技術力の向上

方向性

どんな内容の記事を投稿し、どういったブログにしていきたいかを考えました。 いきなりハードルの高いところを求めておらず、どれくらいの時間軸でどうなりたいかをメンバーと共有しました。

  • スタディプラスに関連することが望ましい。会社でのナレッジを社内外に共有・発信する
  • 他社ブログを指標にして投稿内容のイメージを持ち、段階的な成長を目指す。

例)

  • ブリ:C社、M社
  • ワラサ:S社、M社
  • イナダ:E社、S社
  • ワカシ:K社、A社
  • 魚卵:弊社

懸念・問題

弊社でもご多分に漏れず、以下のようなブログ運用の懸念事項がありました。

  • 記事執筆にリソース(人、時間)を割けないのではないか?
  • 記事執筆・公開のサイクルが回らなくなり、ブログが放置されるのでは?
  • 品質チェックは?
  • 書くネタがない場合は?

解決方法

懸念事項に対して解決を考えてから始めないと、ブログ公開自体が逆効果になるので、そこを解決する運用方法を決めました。

リソース問題

これは、言い換えると優先度の問題とも言えると思います。
多くのエンジニアの最優先事項はプロダクト開発です。そこに会社のためとはいえブログ執筆をなんの考課もなく実施することは負担を生むだけとなってしまいます。

ブログで会社のブランディングを向上させる取り組みは、エンジニア個人にとってもブランディグをあげる事にも繋がり、更にそこから優秀なメンバーの採用に繋がればエンジニアのスキル、生産性を向上させることに繋がるのではないかと思いました。

なので、ブログ投稿を各エンジニアの目標に設定し評価の対象にしました。
評価は段階的な評価軸を決めて、以下のような運用を導入しました。(以下は簡略化した概要)

  • A: 投稿が話題となる(n件シェアされる。ただし炎上はダメ)
  • B: 投稿する
  • C: 投稿しない

公開サイクル問題

投稿数をコントロールしやすくするため、各チームでのローテーションとしました。 スタディプラスでは、以下のようにチームが分かれています。

  • CTO室
  • サーバーグループ
  • アプリグループ
  • ForSchoolチーム

担当をチームとすることで個人が担当するよりも負担が軽減されるのと、各チーム間での情報共有に繋がると考えました。 そして、隔週でエンジニア全員が集まる、Engineering All Hands MTGで発表する事にしました。

品質問題

投稿前のレビューについては以下のような最低限のチェックをして、カジュアルに投稿ができるようにしようと思いました。

  • 著しく会社を毀損することがないか
  • 技術的に誤りがないか

ブログを始めたばかりという事もあり、まずは投稿する事とそれを継続する事に重点を置こうと思いました。
やってみなければ課題や問題がわからないので、適切に振り返りをして、失敗を恐れずにチャレンジして行こうと考えています。

ネタがない問題

  • たまにであれば緩い内容のものでも良い。開発に直接関係なくても中にいるエンジニアの人柄が分かる内容でもOK。
  • 弊社では情報共有にesaを利用しているのですが、そこに「ネタ帳」ページを作成しストックする事にしました。

最後に

ブログについては若干の見切り発車感は否めないですが、何事もやってみなければ分からず、かつ採用に通じる事は全てトライしていこうと思っています。

これから、本ブログでスタディプラスの魅力を発信していこうと思いますので、よろしくお願いします。
また、話を直接聞きたいという方もお待ちしております。

www.wantedly.com