読者です 読者をやめる 読者になる 読者になる

Data Migration Night に登壇してデータ移行の Tips を紹介してきた

今日は Data Migration Night に参加してきた & LT 枠で登壇してきた!

atnd.org

ChatWork がデータマイグレーションに使った技術の話

  • CQRS + Event Sourcing System
  • Aurora にある17億メッセージを HBase にマイグレーションする
  • 基本マイグレーションと差分マイグレーションに分類した
  • Aurora の binlog を直接取得して差分マイグレーションに使った
  • メトリクスを取得して,マイグレーションの成功をちゃんと確認するようにした
  • Aurora から Spark で HBase に書き込むときのパーティションを試行錯誤した

17億メッセージを管理する Aurora って,どんなインスタンスタイプを使ってるんだろう?質疑応答では Room ID でパーティショニングをしていると言っていた.あと資料に “巨大部屋” という表現があって笑った.Spark も HBase も使ったことがなくて,詳細はわからなかったけど,分散システムの特性を理解した上でマイグレーションを考えたという話は参考になった.

(資料公開待ち)

オンプレから AWS 移行で変えた3つの意識

  • コスト意識
    • RI をインスタンスの在庫確保として使う
  • AWS オペレーション意識
    • インフラ専任エンジニアの撤廃
    • IAM で細かく制御して,オペミスが起きないようにした
  • システムダウン意識
    • フルマネージドを使うようにした
    • サーバダウンを許容し,復旧できることを重要視した

インフラ専任を撤廃していくのは施策としては非常に興味深かった.もっと事例を聞いてみたかった.また RDS で適用必須なメンテナンスが入るとサービスダウンに繋がるので,前段で書き込みを止めて対応しているとのこと.メンテナンスを選択できれば良いんだろうけど,無停止前提だと厳しいよなぁ.

www.slideshare.net

2000万アカウントの無停止データ移行の裏側

大きな問題無く移行できたのは凄いと思う.個人的にはレプリケーション遅延も気になるし,循環更新も気になる.懇親会で話を聞いたら「今はもう役目を終えた」と言っていて,完全に移行専用のアーキテクチャとのこと.強い!あと open-replicator は全く知らなかったので,調べる.

speakerdeck.com

LT : どのように MySQL on EC2 から Aurora に移行したのか

僕は CyberAgent Developers Blog に投稿した記事の一部 + α を紹介した.Aurora に特化した内容はあまり話さず,データ移行の Tips を中心に話したけど,比較的反応もあって良かった!

developers.cyberagent.co.jp

資料も up した!

speakerdeck.com

LT : AWS のデータ移行ソリューション

  • AWS Direct Connect
  • AWS Snowball
  • AWS Snowmobile
  • などなど

(資料公開待ち)

関連記事

labotech.dmm.com

登壇写真

撮影してもらった!素晴らしい会場だったし雰囲気ある ( ゚д゚)ノシ

f:id:kakku22:20170523002440j:plain

Mackerel で ECS の動的ポートマッピングに対応したタスクのメトリクスを取得する

前回書いた記事に続き,Mackerel を使って ECS のメトリクスを取得する方法を検証していて,今回は「パターン2 : コンテナインスタンスに mackerel-agent をインストールする」の検証結果をまとめる.前提としては前回と同じで,ALB の動的ポートマッピングを使う.

  • パターン1 : mackerel-agent タスクをコンテナインスタンスごとに起動する
  • パターン2 : コンテナインスタンスに mackerel-agent をインストールする

kakakakakku.hatenablog.com

メリデメ

最初に「パターン2」のメリデメを整理しておく.「パターン1」では実現できなかった「タスク特有のメトリクスを取得できる」点が大きなメリットで,今回この「パターン2」を採用することにした.

  • メリット
    • mackerel-plugin-docker でコンテナインスタンスのメトリクスを取得できる
    • mackerel-plugin-docker が Docker API からメトリクスを取得するため,動的ポートマッピングでタスクが増えた場合も,基本的なメトリクスは取得できる
    • mackerel-plugin-gostats など,任意のプラグインを使って,動的ポートマッピングでタスクが増えた場合も,タスク特有のメトリクスを取得できる
      • 少し工夫が必要になる(本記事のポイント)
  • デメリット
    • mackerel-agent のインストールは cloud-init で頑張る(逆に cloud-init でプロビジョニングができるのはメリットと言えるかも?)
    • Mackerel のグラフ生成が難しい

構成図

「パターン2」の構成は以下のようになる.コンテナインスタンスに mackerel-agent をインストールしている点が「パターン1」とは異なる.

f:id:kakku22:20170518202609j:plain

前提

今回は mackerel-plugin-gostats を使って Golang のメトリクスを取得するため,前提として API に fukata/golang-stats-api-handler を導入しておく必要がある.実装は割愛するが,エンドポイントとしては /stats を用意した.

github.com

github.com

コンテナインスタンスの起動設定に指定する cloud-init 実装例

あくまで実装例として紹介するが,コンテナインスタンスに最低限必要になるプロビジョニングを全て cloud-init で実行した.こうすると Chef や Ansible を実行する必要もなく,Auto Scaling で負荷に応じて自動的に増減させることができる.AMI にする案もあるが,ECS で使う ECS-Optimized AMI も定期的に更新するだろうし,今はこのままにしている.

cloud-init に実装した内容をザックリと箇条書きにすると,以下のようになる.

  • timezone
    • タイムゾーンを変更する
  • write_files
    • /etc/mackerel-agent/mackerel-agent.conf を生成する
    • 動的ポートマッピングに対応したメトリクスを取得するための /root/ecs_gostats.sh を生成する
  • runcmd
    • mackerel-agentmackerel-agent-plugins をインストールする
    • /etc/ecs/ecs.config にクラスタ名を書き込む

cloud-init の実装例としては,以下のようになる.ロール名 / コンテナ名 / クラスタ名などは,適宜読み替えてもらえればと!

#cloud-config
timezone: "Asia/Tokyo"

write_files:
 - path: /etc/mackerel-agent/mackerel-agent.conf
   permissions: 0644
   content: |
     apikey = "xxx"
     roles = [ "yyy:zzz" ]
     include = "/etc/mackerel-agent/conf.d/*.conf"
     [host_status]
     on_start = "working"
     [plugin.metrics.gostats]
     command = "/root/ecs_gostats.sh"
     [plugin.metrics.docker]
     command = "/usr/bin/mackerel-plugin-docker"

 - path: /root/ecs_gostats.sh
   permissions: 0755
   content: |
     #!/bin/sh
     if [ "${MACKEREL_AGENT_PLUGIN_META}" != "1" ];then
       for PORT in $(docker ps --format='{{.Ports}}' --filter name=api | cut -f1 -d- | cut -f2 -d:); do
         /usr/bin/mackerel-plugin-gostats -port ${PORT} -path=/stats -metric-key-prefix=gostats.${PORT}
       done
     else
       /usr/bin/mackerel-plugin-gostats -metric-key-prefix=gostats.#
     fi

runcmd:
 - curl -fsSL https://mackerel.io/file/script/amznlinux/setup-yum.sh | sh
 - sudo yum install -y mackerel-agent
 - sudo yum install -y mackerel-agent-plugins
 - echo AUTO_RETIREMENT=1 >> /etc/sysconfig/mackerel-agent
 - mkdir /etc/mackerel-agent/conf.d/
 - service mackerel-agent start
 - sh -c 'echo ECS_CLUSTER=api-cluster >> /etc/ecs/ecs.config'

cloud-init の詳細は以下に載っている.

docs.aws.amazon.com

Amazon Linux に mackerel-agent をインストールする手順は以下に載っている.

mackerel.io

mackerel-agent.conf の詳細は以下に載っている.

mackerel.io

ecs_gostats.sh

今回のポイントは,この ecs_gostats.sh だと思う.

#!/bin/sh
if [ "${MACKEREL_AGENT_PLUGIN_META}" != "1" ];then
  for PORT in $(docker ps --format='{{.Ports}}' --filter name=api | cut -f1 -d- | cut -f2 -d:); do
    /usr/bin/mackerel-plugin-gostats -port ${PORT} -path=/stats -metric-key-prefix=gostats.${PORT}
  done
else
  /usr/bin/mackerel-plugin-gostats -metric-key-prefix=gostats.#
fi

まず,動的ポートマッピングに対応するため docker ps の結果から,ポートを抽出する.以下の例で言うと 32768 / 32770 / 32771 となる.

PORTS
0.0.0.0:32768->8080/tcp
0.0.0.0:32770->8080/tcp
0.0.0.0:32771->8080/tcp

次にそのポートごとに -metric-key-prefix オプションを使って,メトリクスを送信する.具体的には -metric-key-prefix=gostats.${PORT} とする.ポート番号 32768 を例にすると,実際取得できるメトリクスのキーは以下のようになる.

gostats.32768.gc.gc_num
gostats.32768.gc.gc_pause_per_second
gostats.32768.gc.gc_per_second
gostats.32768.heap.heap_idle
gostats.32768.heap.heap_inuse
gostats.32768.heap.heap_released
gostats.32768.heap.heap_sys
gostats.32768.memory.memory_alloc
gostats.32768.memory.memory_stack
gostats.32768.memory.memory_sys
gostats.32768.operation.memory_frees
gostats.32768.operation.memory_lookups
gostats.32768.operation.memory_mallocs
gostats.32768.runtime.cgo_call_num
gostats.32768.runtime.goroutine_num

さらに,重要なのは MACKEREL_AGENT_PLUGIN_META と言う環境変数の存在で,実は今まで知らなかった.簡単に言うと MACKEREL_AGENT_PLUGIN_META=1 でプラグインを実行すると,グラフ定義を出力する仕様になっている.今回はメトリクス名にポート番号を含めているため,グラフ定義も変更する必要がある.具体的には -metric-key-prefix=gostats.# と書いて,ポートの部分をワイルドカードで表現することができる.

すると,以下のようなグラフ定義を出力することができる.

  • gostats.#.gc
  • gostats.#.heap
  • gostats.#.memory
  • gostats.#.operation
  • gostats.#.runtime

JSON の部分は可読性のために整形しているが,実際はこんなグラフ定義を出力することができる.

$ MACKEREL_AGENT_PLUGIN_META=1 /usr/bin/mackerel-plugin-gostats -metric-key-prefix=gostats.#
# mackerel-agent-plugin
{
    "graphs": {
        "gostats.#.gc": {
            "label": "Gostats.# GC",
            "unit": "float",
            "metrics": [
                {
                    "name": "gc_num",
                    "label": "GC Num",
                    "stacked": false
                },
                {
                    "name": "gc_per_second",
                    "label": "GC Per Second",
                    "stacked": false
                },
                {
                    "name": "gc_pause_per_second",
                    "label": "GC Pause Per Second",
                    "stacked": false
                }
            ]
        },
        "gostats.#.heap": {
            "label": "Gostats.# Heap",
            "unit": "bytes",
            "metrics": [
                {
                    "name": "heap_sys",
                    "label": "Sys",
                    "stacked": false
                },
                {
                    "name": "heap_idle",
                    "label": "Idle",
                    "stacked": false
                },
                {
                    "name": "heap_inuse",
                    "label": "In Use",
                    "stacked": false
                },
                {
                    "name": "heap_released",
                    "label": "Released",
                    "stacked": false
                }
            ]
        },
        "gostats.#.memory": {
            "label": "Gostats.# Memory",
            "unit": "bytes",
            "metrics": [
                {
                    "name": "memory_alloc",
                    "label": "Alloc",
                    "stacked": false
                },
                {
                    "name": "memory_sys",
                    "label": "Sys",
                    "stacked": false
                },
                {
                    "name": "memory_stack",
                    "label": "Stack In Use",
                    "stacked": false
                }
            ]
        },
        "gostats.#.operation": {
            "label": "Gostats.# Operation",
            "unit": "integer",
            "metrics": [
                {
                    "name": "memory_lookups",
                    "label": "Pointer Lookups",
                    "stacked": false
                },
                {
                    "name": "memory_mallocs",
                    "label": "Mallocs",
                    "stacked": false
                },
                {
                    "name": "memory_frees",
                    "label": "Frees",
                    "stacked": false
                }
            ]
        },
        "gostats.#.runtime": {
            "label": "Gostats.# Runtime",
            "unit": "integer",
            "metrics": [
                {
                    "name": "goroutine_num",
                    "label": "Goroutine Num",
                    "stacked": false
                },
                {
                    "name": "cgo_call_num",
                    "label": "CGO Call Num",
                    "stacked": false
                }
            ]
        }
    }
}

ワイルドカードの仕様はよく理解できていなくて,難しいなと感じた.#* の違いはなんだろう?とか,個数制限があるのは # だけ?とか.ドキュメントとしては以下に載っている.

mackerel.io

mackerel.io

ホストグラフ : カスタムメトリック(成功 ○)

この時点で,既に動的ポートマッピングに対応したメトリクスが取得できているため,まずは「ホストグラフ : カスタムメトリック」を確認する.以下のように,ちゃんとポート別にメトリクスが確認できているし,タスク数の増加にも対応できている.ただし,これはまだコンテナインスタンスごとのメトリクスなので,実際にはあまり使わなそう.

f:id:kakku22:20170518203032p:plain

ロールグラフ : カスタムメトリック(失敗 ×)

ロールグラフは完全に厳しくて,ワイルドカード指定で表示することができなかった.改善要望出したいなぁ.

f:id:kakku22:20170518202545p:plain

ロールグラフ : カスタマイズグラフ(微妙 △)

こうなったらもうカスタマイズグラフで頑張るしかなさそうだ!ということになり,カッとなって作った.role 関数を使って,ポートの部分を * で表現したらうまくグラフ化できた.けど,シンプルとは言えないし,ツライ.

https://mackerel.io/embed/orgs/xxx/advanced-graph?query=role('yyy:zzz', 'custom.gostats.*.memory.memory_sys')&title=custom.gostats.*.memory.memory_sys&period=1h
https://mackerel.io/embed/orgs/xxx/advanced-graph?query=role('yyy:zzz', 'custom.gostats.*.memory.memory_sys')&title=custom.gostats.*.memory.memory_sys&period=1d
https://mackerel.io/embed/orgs/xxx/advanced-graph?query=role('yyy:zzz', 'custom.gostats.*.memory.memory_sys')&title=custom.gostats.*.memory.memory_sys&period=1w

カスタマイズグラフを大量に作ってダッシュボードを用意した.

f:id:kakku22:20170518202526p:plain

カスタマイズグラフに関しては以下に載っている.

mackerel.io

去年のアドベントカレンダーでカスタマイズグラフで試行錯誤したのを思い出してしまった.

kakakakakku.hatenablog.com

まとめ

今回実現した方法が正しいのかよくわからないし,もっと良い方法があるのかもしれないし,Mackerel が正式に ECS をサポートしてくれるかもしれないけど,現状この「パターン2」で考えていたことを実現することができた.とは言え,まだ検証環境で ECS を動かしている段階で,実環境に投入したらまた別の問題に気付くかもしれない.もっと良い方法があれば教えて欲しい.

  • コンテナインスタンスに mackerel-agent をインストールした
  • コンテナインスタンスのメトリクスを取得できた
  • タスクのポートをワイルドカードで表現することで,タスクのメトリクスも取得できるようになった
  • グラフは一筋縄では実現できず,カスタマイズグラフで頑張った

謝辞

困ったときに相談するとすぐに助けてくれる id:okzk に今回はアイデアをたくさん頂いた!感謝!

okzk.hatenablog.com

関連記事

kakakakakku.hatenablog.com

kakakakakku.hatenablog.com

mackerel-agent タスクをコンテナインスタンスごとに起動して ECS のメトリクスを取得する

最近 Mackerel を使って ECS のメトリクスを取得する方法を検証している.ポイントとしては,ALB の動的ポートマッピングを使うところで,コンテナインスタンスの中で複数のタスク(コンテナ)が起動されるため,全てのタスクのメトリクスを取得する必要がある.

パターン

Mackerel を使って ECS をモニタリングする場合,アーキテクチャとしては大きく2パターンが考えられる.

  • パターン1 : mackerel-agent タスクをコンテナインスタンスごとに起動する
  • パターン2 : コンテナインスタンスに mackerel-agent をインストールする

今回の記事では「パターン1 : mackerel-agent タスクをコンテナインスタンスごとに起動する」の検証結果をまとめようと思う.

メリデメ

最初に「パターン1」のメリデメを整理しておく.

  • メリット
    • コンテナインスタンスをプロビジョニングしなくても Mackerel が使える
    • mackerel-agent タスクからコンテナインスタンスのメトリクスを取得できる
    • mackerel-plugin-docker が Docker API からメトリクスを取得するため,動的ポートマッピングでタスクが増えた場合も,基本的なメトリクスは取得できる
  • デメリット
    • 動的ポートマッピングで起動されたタスク特有のメトリクス(mackerel-plugin を使って取得するようなもの)を取得できない

構成図

まず,今回は以下のような構成で検証をした.実際に動かすタスクは Golang で書いたサンプル API で,API Service に乗せた.

f:id:kakku22:20170516004413j:plain

ECS : タスク定義

今回 mackerel-agent という名前でタスク定義を作成した.タスク定義に必要な設定は,全てドキュメントに載っていたので,参考になった.ただし,ドキュメントには -role オプションの記載が無かったため,ここは別途追加している.

mackerel.io

タスク定義の画面としては,以下のようになり,ザッとポイントを挙げておく.

  • コンテナの定義
    • コンテナイメージ
      • mackerel/mackerel-agent を利用する
    • 環境変数
      • apikey = xxx
      • enable_docker_plugin = true
      • auto_retirement = false
      • opts = -v -role=yyy:zzz
    • マウントポイント
      • Docker ホストの /var/run/docker.sock を参照できるようにする
      • Docker ホストの /var/lib/mackerel-agent/ を参照できるようにする
  • ボリューム
    • /var/run/docker.sock
    • /var/lib/mackerel-agent/

f:id:kakku22:20170516004445p:plain

ECS : タスク定義 (JSON)

もし AWS CLI でタスク定義を作成する場合は,以下のように実行する.

$ aws ecs register-task-definition --cli-input-json file://register-task-definition.json

register-task-definition.json はこんな感じに定義した.

{
  "family": "mackerel-agent",
  "containerDefinitions": [
    {
      "name": "mackerel-agent",
      "image": "mackerel/mackerel-agent",
      "cpu": 0,
      "memory": 128,
      "essential": true,
      "mountPoints": [
        {
          "containerPath": "/var/run/docker.sock",
          "sourceVolume": "docker-sock"
        },
        {
          "containerPath": "/var/lib/mackerel-agent/",
          "sourceVolume": "mackerel-agent"
        }
      ],
      "environment": [
        {
          "name": "apikey",
          "value": "xxx"
        },
        {
          "name": "auto_retirement",
          "value": "false"
        },
        {
          "name": "enable_docker_plugin",
          "value": "true"
        },
        {
          "name": "opts",
          "value": "-v -role=yyy:zzz"
        }
      ]
    }
  ],
  "volumes": [
    {
      "host": {
        "sourcePath": "/var/run/docker.sock"
      },
      "name": "docker-sock"
    },
    {
      "host": {
        "sourcePath": "/var/lib/mackerel-agent/"
      },
      "name": "mackerel-agent"
    }
  ]
}

JSON で1点注意するところがあり,管理コンソールでタスク定義 (JSON) を見ると,mountPoints の値が "readOnly": null となっている.このままだと以下のエラーが出たため,削除してから実行した.

$ aws ecs register-task-definition --cli-input-json file://register-task-definition.json

Parameter validation failed: Invalid type for parameter containerDefinitions[0].mountPoints[0].readOnly, value: None, type: <type 'NoneType'>, valid types: <type 'bool'>
Invalid type for parameter containerDefinitions[0].mountPoints[1].readOnly, value: None, type: <type 'NoneType'>, valid types: <type 'bool'>

ECS : サービス定義

サービス定義で意識するのは,タスク配置制約を One Task Per Host (distinctInstance) にするところで,この設定にすることで,コンテナインスタンスに対して必ず1タスクが配置されるようになる.ちなみに 4/26 時点だと,管理コンソールから distinctInstance でタスクを配置しても,コンテナインスタンスに複数のタスクが配置されるというバグがあり,AWS サポート側に報告した.現在は既に修正されている.

f:id:kakku22:20170516004510p:plain

docs.aws.amazon.com

mackerel-plugin-docker とは?

mackerel-agent タスクの環境変数で enable_docker_plugin = true を設定することで,mackerel-plugin-docker が使えるようになる.これは,簡単に言うと Docker のソケットファイル docker.sock に対して Docker API を叩くことによってメトリクスを取得していて,複数コンテナが起動している場合でも問題なく対応できる.メトリクス名としては -name-format name というオプションがデフォルトで設定されているため,コンテナ名で区別することができる.ただ,取得できるメトリクスは限られていて,CPU 関連,メモリ関連,IO 関連となる.

github.com

実際に Mackerel でメトリクスを確認した

上記のように mackerel-agent タスクを起動すると,すぐにメトリクスを確認できるようになる.Docker Memory のグラフを見るとわかりやすいが,途中でサービス定義を変更して,コンテナインスタンスに配置するタスク数を増やしても,ちゃんとメトリクスを取得できていた.

  • Docker CPU
  • Docker Memory
  • Docker BlkIO Bytes
  • Docker BlkIO IOPS

f:id:kakku22:20170516004539p:plain

mackerel-agent タスクだと難しいこと

上記のように,動的ポートマッピングを使う場合でも,タスクごとに CPU 関連などの基本的なメトリクスを取得することができるが,例えば gostats など,mackerel-plugin を使って取得するようなタスク特有のメトリクスを取得できないという課題がある.動的ポートマッピングを使わないのであれば,ポートが決まっているし,Docker の link オプションを使えば良いが,今回の要件を満たすのは難しそうだった.

ロールグラフも難しい

あくまでこれは,コンテナインスタンスのメトリクスなので,Auto Scaling のことを考えると,コンテナインスタンスをあまり意識したくなく,ロール単位でメトリクスを見たくなってくる.ただ,今の状態だとメトリクス名がコンテナごとに異なるため,ロールグラフを作れなかった.もしかしたら,拡張グラフ機能を使えばできるのかもしれないけど,うまく書けなかった.

mackerel.io

まとめ

  • Mackerel を使って ECS をモニタリングする場合,アーキテクチャとしては大きく2パターンある
  • 今回は「パターン1 : mackerel-agent タスクをコンテナインスタンスごとに起動する」を検証した
  • mackerel-agent 用のタスク定義はドキュメントを参照して環境変数やボリュームを設定する
  • 動的ポートマッピングを使う場合でも,タスクごとに基本的なメトリクスは取得できるが,タスク特有のメトリクスは取得できなかった
  • ロール単位でメトリクスを見る方法もうまく実現できなかった
  • 結果として「パターン1」の採用は見送ることにした

次回

実際に採用することにした「パターン2 : コンテナインスタンスに mackerel-agent をインストールする」の検証結果をまとめる予定!

最近書いた ECS 関連記事

kakakakakku.hatenablog.com

kakakakakku.hatenablog.com

kakakakakku.hatenablog.com

FCM (Firebase Cloud Messaging) で Instance ID API を使ってトピックをサブスクライブする

最近 Firebase を検証している.今回は mBaaS としての Firebase ではなく,プッシュ通知基盤としての Firebase を検証していて,学んだことをまとめておこうと思う.Firebase には他にも Analytics や Functions や Remote Config などなど,機能がたくさんあり,使っていて凄くワクワクする.

前提

前提として,動作検証をするアプリに Firebase を導入して,端末トークンを取得できるようにしておくことと,プッシュ通知を受け取れるようにしておく必要がある.この部分は割愛するが,詳しくは以下のドキュメントに載っている.

2種類ある Firebase のプッシュ通知

Firebase のプッシュ通知は,大きく2種類ある.このあたりの前提知識がないと,ドキュメントを読んでも混乱してしまうと思う.Firebase Notifications というのは,Firebase の管理画面からプッシュ通知を送る方式で,FCM というのは,HTTP 経由でプッシュ通知を送る方式のことを意味する.

  • Firebase Notifications
  • FCM (Firebase Cloud Messaging)
    • Notification Message
    • Data Message

その他の細かな差異に関しては,Firebase の FAQ に詳しく載っている.

Cloud Messaging: What’s the difference between the Notifications composer and Cloud Messaging?
https://firebase.google.com/support/faq/

また,FCM には Notification Message と Data Message という,2種類のメッセージタイプがある.簡易的なプッシュ通知を実現する場合は Notification Message を使えば良いが,Notification Message と Data Message を組み合わせて使うこともできる.また,アプリの状態(フォアグラウンド/バックグラウンド)によっても挙動が異なるため,詳細はドキュメントを参照で.

ターゲット

プッシュ通知を送るターゲットは大きく3種類ある.

  • ユーザーセグメント … 言語 / バージョン / ユーザープロパティなどを AND / OR 演算した集合体
  • トピック … 任意のグループ
  • 端末 … 特定の端末(登録トークン指定)

トピック

今回はトピックを検証した.トピックとは任意のグループのようなもので,トピックの中に端末(登録トークン)を紐付けていく.これを「サブスクライブ」と呼ぶ.ドキュメントでは「天気予報アプリで,荒天警報の通知を受けたい人」というトピックが紹介されていた.このように任意のグループを作成することができる.また,個数上限のあるユーザーセグメントとは異なり,トピックは無制限なので,大量に作っても問題が無さそう.

サブスクライブをする場合,以下のドキュメントに載っているような実装をする.具体的には subscribeToTopicunsubscribeFromTopic を呼び出す.なお,実行時にトピックが存在しない場合は,自動的にトピックも作成される.ただし,トピックが作成されるまでに最大1日待つ必要がある.今回トピックを数個作成してみて,大体4時間程度で作成されたが,それでも結構な時間を待つ必要があることには変わりがないと思う.

f:id:kakku22:20170512123010p:plain

Firebase の管理画面からプッシュ通知を送る

Firebase の管理画面で,Notifications のメニューを選択して送信するだけなので,割愛する.

curl で FCM (Firebase Cloud Messaging) のプッシュ通知を送る

curl で FCM のプッシュ通知を送る場合,以下のように実現できる.FIREBASE_SERVER_KEY には管理画面の設定ページから取得したサーバーキーを設定しておく必要がある.

$ FIREBASE_SERVER_KEY=''
$ curl --header "Authorization: key=${FIREBASE_SERVER_KEY}" \
  --header "Content-type: application/json" \
  https://fcm.googleapis.com/fcm/send \
  -d @firebase.json

リクエストとして送るパラメータ ( firebase.json ) は以下のように書く.今回はテスト用に test トピックを事前に用意した.表記としては /topics/test のように書く.プッシュ通知のタイトルと本文もそれぞれ定義する.

{
    "to": "/topics/test",
    "priority": "high",
    "notification": {
        "title": "title",
        "body": "body"
    }
}

Instance ID API で登録トークンを操作する

Google が提供している Instance ID API を使うと,HTTP 経由で登録トークンの情報を取得したり,トピックをサブスクライブすることができる.最初はネイティブじゃないとできないと思っていたので,この API を発見したときは驚いた.

ちなみに Instance ID API の文脈で言うと「インスタンス ID」という名称に変わるが,結局のところ,今まで紹介してきた「登録トークン」と同じもので,正確に言えば,「インスタンス ID」では iOS と Android 以外に Chrome も識別することができるという点が違うと思われる.当然ながら,プライバシーの観点で「インスタンス ID」も「登録トークン」もリフレッシュもできる.今回は「登録トークン」の表記で統一する.

登録トークンの情報を取得する API

IID_TOKEN には nKctODamlM4:CKrh_PC8kIb7O...clJONHoA などの登録トークン(正確には152文字?)を指定する.以下のエンドポイントを叩くと,登録トークンがサブスクライブしているトピックの一覧や,ネット接続タイプ WIFI or MOBILE,プラットフォームなどを取得することができる.

$ FIREBASE_SERVER_KEY=''
$ IID_TOKEN=''
$ curl --header "Authorization: key=${FIREBASE_SERVER_KEY}" \
  "https://iid.googleapis.com/iid/info/${IID_TOKEN}?details=true" | jq .
{
  "applicationVersion": "0.0.1",
  "connectDate": "2017-05-09",
  "application": "com.example",
  "scope": "*",
  "authorizedEntity": "111111111111",
  "rel": {
    "topics": {
      "test": {
        "addDate": "2017-05-09"
      }
    }
  },
  "connectionType": "WIFI",
  "platform": "IOS"
}

登録トークンを指定してトピックをサブスクライブする API

以下のエンドポイントを叩くと,登録トークンを指定してトピックをサブスクライブしたり,アンサブスクライブできる.ポイントは HTTP メソッドで POST と DELETE を使う点と,ヘッダーに Content-type: application/jsonContent-Length: 0 を乗せる点がある.この条件が満たされないと,エラーが返ってくる.

$ FIREBASE_SERVER_KEY=''
$ IID_TOKEN=''
$ TOPIC_NAME='test'
$ curl -XPOST --header "Authorization: key=${FIREBASE_SERVER_KEY}" \
  --header "Content-type: application/json" \
  --header "Content-Length: 0" \
  "https://iid.googleapis.com/iid/v1/${IID_TOKEN}/rel/topics/${TOPIC_NAME}"
$ FIREBASE_SERVER_KEY=''
$ IID_TOKEN=''
$ TOPIC_NAME='test'
$ curl -XDELETE --header "Authorization: key=${FIREBASE_SERVER_KEY}" \
  --header "Content-type: application/json" \
  --header "Content-Length: 0" \
  "https://iid.googleapis.com/iid/v1/${IID_TOKEN}/rel/topics/${TOPIC_NAME}"

その他

複数の登録トークンをバルクでサブスクライブするエンドポイントなど,他にもいろいろと提供されている.詳細はドキュメントを参照で.

  • https://iid.googleapis.com/iid/v1:batchAdd
  • https://iid.googleapis.com/iid/v1:batchRemove

まとめ

  • Firebase のプッシュ通知を検証した
  • FCM を使うと,HTTP 経由でプッシュ通知を送ることができる
  • トピックを使うことで,任意のグループにプッシュ通知を送ることができる
  • ただし,トピックの作成には最大1日かかる
  • Instance ID API を使うと,HTTP 経由で登録トークンを操作することができる

関連記事

inside.pixiv.net

プレゼンをするときに意識していること

僕自身,プレゼンが得意とまでは言わないけど,プレゼンをすることは大好きで,勉強会だったり,会社のイベントだったり,様々なところで話す機会がある.「プレゼンのコツ」をネットで調べたら既に大量の記事が出てくるし,何番煎じだよという声もありそうだけど,僕がプレゼンをするときに意識していることをまとめてみようと思う.最近「プレゼンのコツを教えて欲しい」と聞かれることが増えてきたという背景もある.

よく聞く誤解

本題に入る前に,プレゼンの話をするときによく聞く誤解があるので,最初にまとめておく.

プレゼンが得意な人はリハーサルをしなくても上手く話せる?という誤解

僕が「最低5回はリハーサルをするし,多いときは10回を超えることもある」という話をすると驚かれる.確かに本当にプレゼンが得意な人はアドリブでも上手く話せるのかもしれないけど,多くの人は努力でカバーしている.特に僕は心配性なので,不安が無くなるまで繰り返しリハーサルをしている.これは多くの人が感じている誤解だと思う.

プレゼンをする機会がない?という誤解

「勉強会に登壇する予定もないし」と思っていたり,プレゼンには縁がないと考えている人が多いように思う.プレゼンというのは「情報をわかりやすく伝えること」なので,勉強会だけじゃなく,チームの定例ミーティングで進捗報告をするのもプレゼンだし,マネージャーとの面談もプレゼンだし,極端な話をすれば,飲み会で話すこともプレゼンだと思う.僕たちは日々プレゼンをしているので,プレゼンをする機会がないというのは誤解だと思う.

プレゼンをするときに意識していること

明日からすぐに使える具体的な話を書く.分類としては大きく3個にした.

  1. プレゼン前に
  2. プレゼン中に
  3. 日常的に

1. プレゼン前に

1-1. リハーサルを最低5回はして,資料を修正する

とにかくも,プレゼンの成功はリハーサルが全てだと思う.定量的に言うならば,最低5回はする必要がある.さらにリハーサルをするときに重要なポイントが2点ある.

  • ストップウォッチで時間を測ること
  • 資料を修正すること

まず,ストップウォッチで時間を測ることが大切で,プレゼン時間を見積もることができる.またリハーサルを繰り返せば繰り返すほど,プレゼン時間のブレは収束してくるため,プレゼン全体がまとまってくる.プレゼン時間が長くなるのは運営側も困るし,聞いてる側も飽きるので,必ず計画された時間内に終わるようにする.

次に,リハーサルをすると,口頭の言い回しと資料の表現がズレていることに気付いたり,説明の順番が悪かったり,資料の誤りに気付いたり,単純に情報量が多すぎたり,様々なことに気付く.リハーサルが終わったらすぐに資料を修正して,もう一度リハーサルをする.これを繰り返すことによって,資料も正確になり,プレゼン時間も収束し,プレゼンを体で覚える感覚に近付く.

1-2. 想定される質問を考えて,資料を修正する

資料がある程度できた段階で,このプレゼンを聞いた人はどんな質問をするだろうか?という,客観的な視点で想定される質問を考える.すると,資料で説明を飛ばしてしまっている部分に気付いたり,回答用の追加資料を事前に用意できたりする.この場合も同様に,資料を修正することで,よりまとまった資料になる.

2. プレゼン中に

2-1. 壇上の真ん中に立つ

僕が最も徹底的に実践しているのは,この「壇上の真ん中に立つ」ことで,とにかくこれは重要だと思う.一般的に多いのは,PC を接続する場所(何て呼ぶの?)に立ったままプレゼンをするスタイルだけど,これは避けた方が良いと思う.理由としては単純で,注目して欲しいのは資料ではなく,プレゼンだからで,そのプレゼンをするのは発表者なので,発表者に全ての視線を集める必要がある.目の前に PC があると,発表者は絶対にその PC を見てしまって目線が下がってしまうし,聞いてる側はスクリーンの文字を読んでしまう.

さらに「壇上の真ん中に立つ」ためには,プレゼンターが必須になる.僕は Logicool の R800 を2011年に購入して,もう7年間もずっと愛用している.R800 は今は売ってなさそうだけど,R400t と全く同じなので,最新のモデルはコッチなのかも?このプレゼンターを持っていれば,壇上に立てるし,柔軟なタイミングでスライドを送ることができる.

ロジクール ワイヤレス プレゼンター R400t

ロジクール ワイヤレス プレゼンター R400t

あまり自分がプレゼンをしているときの写真を持っていないけど,以下は最近 dots で話したときの写真で,壇上の真ん中に立ってプレゼンをしている良い例かなと思う(社内イベントの写真なので資料は隠した).右手にプレゼンターを持っている.

f:id:kakku22:20170508033934p:plain

2-2. ノドを潤す

もう1点意識していることは,ノドを潤すことで,プレゼンをしているときに声がガラガラにならないように,直前に水を飲むようにしている.たまにプレゼンをしながら咳払いを繰り返す人がいるけど,咳払いは「あー,えーっと」よりも悪だと思っていて,プレゼンをしている側も,聞いている側も,リズムが崩れてしまう.できる限り,ストレス無くプレゼンを聞いて欲しいと思っている.

3. 日常的に

3-1. TED を観る(特にスーパープレゼンテーション)

TED を観るという話はよく聞くと思うし,僕も TED が好きで観ることが多いけど,個人的には NHK で放送されている「スーパープレゼンテーション」で TED を観るのをオススメする.解説も入っていて楽しく観れる.僕は毎週録画予約をしている.特に TED を観ると「プレゼン中のジェスチャー」や「ストーリーに沿った声の強弱」や「笑いの取り方(アイスブレイク)」を学ぶことができる.是非プレゼンの技術を学ぶというモチベーションで TED を観てみて欲しい.

www4.nhk.or.jp

例えば “How to start a movement” は僕の好きな TED 動画の1つ.

www.ted.com

3-2. 絵本を読む

絵本を読むことは,プレゼンの練習にもなる.絵本は比較的文字が少なく,擬音語なども多いため,絵に負けないように読むには,アクセントの強弱を付けたり,顔で喜怒哀楽を表現したりする必要がある.僕は子供に絵本を読み聞かせることが多いので,そのときに面白く読むように意識していて,これは多少なりともプレゼンの練習になっていると思う.好きな絵本はたくさんあるけど,特に “From Head to Toe” がお気に入り.

From Head to Toe

From Head to Toe

3-3. ミーティングのときは指示棒をポケットに入れておく

僕と働いたことがある人なら知っているだろうし,最初はよく笑われていたけど,ミーティングのときに必ず指示棒をポケットに入れておく癖がある.ディスプレイを使って議論するときに,サッと指示棒を出して「ココは○○でー」と話すと,今どこのことを話してるんだろう?と議論の置いてきぼりになる人が減るし,参加者の視線が集まるというメリットもある.ペアプロをするときも指示棒を用意しておくと便利で,ペアが「ココのメソッドはこのままで良いの?」と教えてくれるので,どこどこ?と探す手間が減って効率的になる.ちなみに僕はコクヨの指示棒を使っている.

コクヨ 指示棒 サシ-1

コクヨ 指示棒 サシ-1

写真集

過去に書いたブログ記事を探したら少し写真が見つかったので,参考までに載せておく.全て壇上の真ん中に立っている.

2016年02月

f:id:kakku22:20160215212247j:plain

kakakakakku.hatenablog.com

2016年06月

f:id:kakku22:20160629163444p:plain

kakakakakku.hatenablog.com

2016年10月

f:id:kakku22:20161020224532j:plain

kakakakakku.hatenablog.com

まとめ

  • プレゼンの成功はリハーサルが全て
  • プレゼン中は壇上の真ん中に立つ
  • 必ずプレゼンターを使う
  • TED を観たり,絵本を読んだりして,日常的にプレゼンを練習する