コネヒト開発者ブログ

コネヒト開発者ブログ

コネヒトマルシェオンライン「事業を支えるWeb開発」vol.2 を開催しました!

こんにちは! @otukutun です。

先日 2020/09/25(金)に コネヒトマルシェオンライン「事業を支えるWeb開発」vol.2 をランサーズさんと共同開催で開催しました!コネヒトマルシェはみんなの「知りたい」「知ってる」をおすそ分け!をコンセプトにした参加者が主役の勉強会です。今回はオンライン開催になってから第二回目になります。前回はBASEさんとこんな内容でやっておりますのでそちらもよかったらご覧ください。

発表内容

今回はオンラインになってから第二回の開催となりますが、テーマは第一回に引き続き「事業を支えるWeb開発」というテーマでお送りしました。今回の発表内容は以下のようになっています。ではさっそく、これからLTの内容など簡単にご紹介できればと思います。

  • @奥津(コネヒト株式会社所属) Fabricate導入した話
  • @金澤さん(ランサーズ株式会社所属)ランサーズのCakePHP4移行について
  • @富田さん(コネヒト株式会社所属)コネヒトの健全性を支える GitHub Actions の事例紹介
  • @安達さん(ランサーズ株式会社所属)ECS/Fargateの活用事例 ( CakePHP編)
  • @山田さん(コネヒト株式会社所属)ページネーションから考えるSQLパフォーマンス

Fabricate導入した話 / @奥津(コネヒト株式会社所属)

こちら私が発表した内容になります。CakePHPにおけるテストデータ生成からFabricateというテストデータ生成ツールを導入した内容の発表でした。年数が経っているプロジェクトで既存のFixtureとFabricateを共存させて使っていく話をしました。

ランサーズのCakePHP4移行について / @金澤さん(ランサーズ株式会社所属)

既存のプロジェクトを分割しながらCakePHP4に移行していく発表でした。CakePHP4移行に当たってのレポジトリ戦略から始まり、アップデートしてくなかで多数のOSSにコントリビュータされていていて、とても刺激を受けた発表でした。弊社でメンテナンスしているcakephp-master-replicaにもコントリビュートしていただいてきました。

コネヒトの健全性を支える GitHub Actions の事例紹介 / @富田さん(コネヒト株式会社所属)

開発における健全性の定義から始まり、それをGitHub Actionsを使って観測・改善していこうという発表でした。ツールの導入して日が浅いようなので今後の経過も気になりますね!

ECS/Fargateの活用事例 ( CakePHP編) / @安達さん(ランサーズ株式会社所属)

こちらはCakePHPの開発環境を作るお話からはじまり、stg・本番環境をECS Fargateに移行する話を中心に発表していただきました。ログまわりの集約方法の話や作業用コンテナを設置している話とても共感しながら聞いておりました。Fargateはデバッグしづらいというのはその通りでうなづいちゃいました。

ページネーションから考えるSQLパフォーマンス / @山田さん(コネヒト株式会社所属)

先日業務で実装されたページネーションから、SQLパフォーマンスを考えてどんなindexを貼るべきかという発表内容でした。個人的にindexまわりの話は好物なので楽しく聞くことができました。MySQLのサンプルデータは気軽に試してみるのによさそうだな〜と思いました。

最後に

ランサーズさん、コネヒト社共にメインで使っているCakePHPの発表だけでなく、GitHub ActionsやSQLなどいろんなテーマの発表がありとても興味深い勉強会になったと思っています。

またまた第三回も開催したいと思いますので参加される方ははたまた共同開催していただける会社さんも募集しておりますのでぜひよろしくお願いします!

ということで、また会いましょー!

コピペでできるGitHub Actionsでreleaseブランチのマージ、リリースノートの作成の自動化【git-flow用】

こんにちは、コネヒトでiOSエンジニアをやっていますyanamuraです。

iOSやAndroidアプリの開発だとgit-flowを使って開発することも割と多いのではないかと思います。 git-flowの場合releaseブランチをマージする場合はdevelopとmain/masterにマージする必要があるのでちょっと手間がかかります。 また、main/masterにマージ後にタグをつけてpushし、さらにリリースノートを書いたりすると結構面倒です。

これをGitHub Actionsで自動化しました。

想定しているフロー

以下の手順は手作業で行います

  1. release-x.x.x という命名ルールでreleaseブランチをつくる
  2. 1のreleaseブランチでPull Requestを作成する。
  3. 2のPull Requestのbodyにリリースノートに書く内容を記載する
  4. releaseブランチをマージしたいときは、Pull Requestにshipit ラベルをつける

こうすると自動でマージが行われ、vx.x.x というタグがつけられ、Pull Requestのbodyに書いた内容がリリースノートに反映されます。

GitHub Actionsの作成

.github/workflows というフォルダを作成し、その中にymlファイルを作成し、以下の内容をコピペするだけです。

name: automerge
on:
  pull_request:
    types: [labeled]
jobs:
  automerge:
    if: github.event.label.name == 'shipit'
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Extract branch name
      uses: mdecoleman/pr-branch-name@1.0.0
      id: extract_branch
      with:
        repo-token: ${{ secrets.GITHUB_TOKEN }}
    - name: Extract tag name
      shell: bash
      run: |
        branch=${{ steps.extract_branch.outputs.branch }}
        echo "##[set-output name=tag;]$(echo ${branch#release-})"
      id: extract_tag
    - name: Merge
      if: startsWith(steps.extract_branch.outputs.branch, 'release')
      uses: yanamura/git-flow-merge-action@v1
      with:
        github_token: ${{ secrets.GITHUB_TOKEN }}
        branch: ${{ steps.extract_branch.outputs.branch }}
        tag: v${{ steps.extract_tag.outputs.tag }}
    - name: Get body from PullRequest
      id: pr-body
      uses: actions/github-script@v2
      with:
        github-token: ${{secrets.GITHUB_TOKEN}}
        result-encoding: string
        script: |
          const result = await github.pulls.get({
            owner: context.repo.owner,
            repo: context.repo.repo,
            pull_number: context.issue.number
          })
          return result.data.body
    - name: Create ReleaseNote
      if: startsWith(steps.extract_branch.outputs.branch, 'release')
      uses: actions/create-release@v1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        tag_name: v${{ steps.extract_tag.outputs.tag }}
        release_name: Release ${{ steps.extract_tag.outputs.tag }}
        body: |
          ${{ steps.pr-body.outputs.result }}
        draft: false
        prerelease: false

マージする際のトリガーとなるラベル名やリリースブランチの命名、タグの命名など変えたい場合はカスタマイズしてみてください。(masterではなくmainブランチの場合は、こちらを見てmainを指定してください)

かんたんにできると思いますので、ぜひお試しください!

今回ご紹介したところ以外にもGitHub Actionsを活用して自動化を行っているのでまた別の機会にご紹介できたらなと思います。

最後に

コネヒトではエンジニアを募集しています。少しでもご興味のあるかたはぜひ一度話を聞きに来てください! hrmos.co

hrmos.co

iOSDC Japan 2020 に協賛、参加しました!

こんばんは!コネヒトでアプリケーションエンジニアをしているaboです。

先日開催された iOSDC Japan 2020 は、前夜祭含め3日間におよび、大盛況のうちに幕を閉じました。今回はオンライン開催ということもあり、例年とは違った準備も必要で大変だったと思いますが、今年も素敵なコミュニティだったと思います。ニコ生での開催だったので、リアルタイムに視聴者のコメントが流れるのが楽しかったですし、事前録画の発表に対して発表者による補足コメントが流れるのも新鮮でした。また、Discord をつかった Ask the Speaker や雑談部屋などのチャレンジも印象的でした。

iOSDC Japan 2020 に関わった皆様に感謝致します! 🤝

f:id:aboy_perry:20200921183938j:plain

コネヒトは iOSDC Japan 2020 に協賛させていただきました

今年もシルバースポンサー、Tシャツスポンサーの2つで協賛させていただきました!

iOSDC は昨年も協賛させていただきましたが、根底にあるのはコネヒトが掲げる「人の生活になくてはならないものをつくる」というミッションです。エンジニア文脈においても、コネヒトが技術コミュニティになくてはならないような会社になるべく、様々なアウトプットを支援・促進するような制度*1も存在します。自分たちが普段お世話になっている技術コミュニティに、こうした形でサポートさせていただくことで、一緒に盛り上げていくことができたらと思っています。

f:id:aboy_perry:20200921173643j:plainf:id:aboy_perry:20200921173716j:plain

機械学習まわりの発表もいくつかありました

ぼくがちょうど機械学習をサービスに組み込んでいく動きをするチームに所属している*2のもあり、機械学習に関連する発表にも興味がありました。

とくに『機械学習のブルーオーシャン、CoreML』という発表に関しては、個人的にも Core ML を少し調べていたのもあり、実案件レベルをこなしている方の目線から見る Core ML を知る貴重な機会でした。

発表を聞いてみると、実務レベルでは Core ML モデルをつくる部分も含めて全体を触る必要があり、そのなかで機械学習やグラフ可視化ツール、Core ML Tools、Swift、Python などの知識が求められるため、ブルーオーシャンといえど幅広い知識が必要になりそうだなーと感じました。このあたりは全て独学だと骨が折れそうな感じですが、コネヒトには機械学習エンジニアがいるので、チームで取り組めればラクそう...!

Core ML はコネヒトでも可能性がなくはない技術なので、今回の発表が聞けたのはとても有意義でした!

最後まで大盛り上がりで終了!

2日目の LT タイムには視聴数が2000人を突破し、その後のクロージングでは2500人ほどになっていました!LT は事前録画ではなくリアルタイムで行われたので、オフラインで開催された昨年までと同様ライブ感のある盛り上がりでした。

f:id:aboy_perry:20200921182021p:plain
クロージングの様子

というわけで簡単ではありますが参加ブログとさせていただきます!今年も最高のコミュニティでした!あらためて、iOSDC Japan 2020 に関わった皆様ありがとうございました!

コネヒトでネイティブアプリをグロースさせるために使っているSaaS

こんにちは!コネヒトでアプリケーションエンジニアをしているaboです。

今回はコネヒトで特にネイティブアプリをグロースさせるために使っているSaaSを、それぞれどう使っているかも合わせて紹介します。

今回紹介するのはこの4つで、ざっくりとした使い方は以下の通りです。

  • Firebase:クラッシュ分析や、A/B テストなど
  • KARTE:ポップアップ配信
  • Repro:プッシュ通知配信
  • Mixpanel:フローやファネル、リテンションなど多様なデータ分析

それぞれについてもう少し詳しく紹介していきます。

f:id:aboy_perry:20200911173902p:plain

Firebase のつかいかた

Firebase は多くの機能を持つアプリ開発プラットフォームですが、コネヒトでは以下の機能をつかっています。

  • Firebase Analytics:BigQuery とリンクさせて多様なデータ分析
  • Firebase Crashlytics:クラッシュ/非重大なイベント分析
  • Firebase App Distribution:テストアプリ配信
  • Firebase A/B Testing:A/B テストの実施
  • Firebase Remote Config:A/B テストなどのパラメータ配信
  • Firebase Performance Monitoring:パフォーマンス分析

Firebase Analytics

ネイティブアプリから Analytics へアプリの各種イベントを送っていますが、Analytics をそのまま使うというよりは、BigQuery 経由で活用されることが多いです。Firebase を BigQuery にリンクすることでサンプリングされていない元のイベントデータを見ることができ、溜まったデータは機械学習や、サービスの分析等につかわれます。コネヒトでは BigQuery をはじめとする各種データソースを Redash から参照可能にしており、かつ社員だれでも Redash にアクセスできます。これらは Query Results を含むデータソースとクエリを駆使して比較的難易度の高いデータの分析にも活用されています。

Firebase Crashlytics

アプリのクラッシュ情報がわかる重要な機能です。Fabric の deprecated に伴い移行しました。コネヒトではクラッシュ情報を流す Slack チャンネルを用意して、新規、または頻発しているクラッシュを検知できるようにしています。

f:id:aboy_perry:20200908173002p:plain
アプリのクラッシュログが流れるSlackチャンネル

Firebase App Distribution

iOS のテストアプリの配布に使っています(Android は DeployGate です)。こちらも Fabric から移行しました。コネヒトでは fastlane を使って App Distribution による配布を自動化しています。GitHub 上で develop ブランチが更新されたら自動で配布されるようにしています。

Firebase A/B Testing

ネイティブアプリ側のコード修正だけで完結する A/B テストを実施するときに使っています。A/B のグルーピングに関係するパラメータやイベントは Firebase だけでなく他のサービスにも送っているため、A/B テストの結果は他のサービスでも分析できます。コネヒトでは Firebase A/B Testing が出す結果だけを見て判断せず、他のサービスも併用することが多いです。

Firebase Remote Config

何かしらのデータをネイティブ側にハードコードしてしまうと、変更するためにはアプリのバージョンアップや審査が必要になり手続きや考えることが増えます。そのため PDCA をより早く回したい施策などは Remote Config を使うことがあります。定義したデータは iOS/Android で共通して使え、専用の API を用意する必要もないので便利です。

Firebase Performance Monitoring

アプリのパフォーマンスを分析できる機能ですが、コネヒトではまだ実験段階です。詳しくは Performance Monitoring を深掘りしたこちらの記事をご覧ください。

tech.connehito.com

KARTE と Repro のつかいわけ

KARTE は CX(顧客体験)プラットフォーム、Repro は CE(カスタマーエンゲージメント)プラットフォームです。両者とも多機能ですが、コネヒトではそれぞれの長所を生かしてポップアップとプッシュ通知の配信に使っています。

まず KARTE はポップアップの配信に使っています。特定の記事やページを見たユーザーを配信対象に設定できたり、配信しない曜日や時間帯を設定できる点が魅力です。

一方 Repro はプッシュ通知の配信に使っています。KARTE もプッシュ通知配信が可能ですが、負荷分散設定がなかったり、秒間や月の送信上限が現状のママリのプッシュ通知配信の要件に一部応えられないためです。

Mixpanel のつかいかた

最後に紹介する Mixpanel はグロースハックツールで、フローやファネル、リテンションなど様々な角度からの分析が可能です。また、GUI による分析作業のしやすさや、分析結果のビジュアライズによってエンジニア以外の職種も比較的簡単に扱える点が魅力です。

コネヒトではネイティブアプリ上で起こるイベントのほとんどは Mixpanel に送っていて、施策の効果検証や仮説探索のためのデータ分析などに役立っています。

f:id:aboy_perry:20200914225121p:plain
Mixpanel でつくれるダッシュボード

また、細かいですが今年の6月頃には Mixpanel のレポート URL が Slack 上でプレビュー表示されるようになり、ますます使いやすくなっています。この辺りの詳細は Mixpanel 公式の Slack / Mixpanel Integration で確認できます。

f:id:aboy_perry:20200911011240p:plain
Mixpanel のレポートが Slack でプレビューされるようになった

Profile Properties と Event Properties

Mixpanel を例にして、プロパティのつかいわけを紹介します。Mixpanel のプロパティのうち Profile Properties はユーザーに紐づくため、ユーザーの状態が変わるとプロパティの値も変わり、Mixpanel 上では常に最新の値を確認できます。一方 Event Properties はイベントに紐づくため、イベント発生時点での状態が記録され、あとから確認できます。

例えば検索イベントにおける検索クエリは、イベント固有なため Event Properties として設定しています。一方で、ユーザーの課金状態は Profile Properties と Event Properties 両方に設定しています。これは、あるイベント時点での値と、ユーザーの最新の値両方を確認したいニーズがあるからです。

また、課金状態のような、全てのイベントに対して共通して付与したいプロパティに関しては Super Properties が使えます。一度 Super Properties に追加するコードを書いてしまえば、イベントごとに個別の Event Properties として追加する必要がなく、忘れる心配もないのでとても便利です。

おわりに

コネヒトで特にネイティブアプリをグロースさせるために使っているSaaSを4つ紹介しました。このあたりの話は、何が正解ということもないので、この記事を読んでくださった皆さんの「ウチはこんな風にしてます!」というのをぜひ聞いてみたいです!


コネヒトではネイティブアプリを一緒に成長させられるエンジニアを絶賛募集中ですので、まずはお話だけでも聞きにきてください! hrmos.co

バウンスメールと AWS SES

こんにちは! フロントエンドエンジニアのもりやです。

先日エンジニアチーム内で AWS SES のバウンスレートについて話題になったのですが、その時に「バウンスって何?」という声がちらほら聞こえてきました。

Webサービスではユーザー登録や問い合わせなどメールが必要になる場面が多いです。 バウンスなどはメールを安定して送信するために必要な知識なのですが、なかなか担当する人以外は知られていないのかな、と思いました。 そこで今回は、バウンスメールなどのメール運用で知っておきたいエンジニア向けの知識を紹介しようと思います。 (執筆時に調べた資料などは最後に記載しておりますので、より詳しく知りたい方はそちらもあわせて読んでみてください)

また、コネヒトでも使用している AWS SES の場合、バウンスをどう扱っていく必要があるかについても紹介します。

(※なお SMTP などのメールの仕組みや仕様などは本記事では解説しません)

Webサービスとメール

LINE や Slack など様々なコミュニケーションサービスが普及している今でも、メールは欠かせないツールです。 理由としては、メールアドレスを知るだけで送信でき、安価に使えることだと思います。 そのため、アカウント登録やお知らせなど様々な場所で使われています。 (SMS も電話番号だけで送信できますが、送信単価がメールに比べると高いですね)

ところが、その便利さ故に困った事が出てきます。 ご存知スパムメールですね。 メールアドレスが知られてしまうだけで、スパムメールも送信されてしまいます。

日常的に大量のスパムメールが送信されてくれば、メールは使い物にならなくなってしまうでしょう。 しかしながら様々な対策が取られているおかげで、私達は日頃ちゃんとメールを使うことができています。

ところがこの「様々な対策」が時に我々サービス提供者にとってネックになってしまう場合があります。 ここからが本題です。

メールは送信元が評価されている

今回お伝えしたいことの本題はこれです。 メールは送信元が正しくメールを送信しているかを評価されています。 (ちなみに本ブログでは「評価」で記載を統一しますが「レピュテーション」とも呼ぶ場合も多いです)

評価しているのは特定の組織というわけではなく、メールサービスを提供しているISPなどが個別に評価しています。 なので、統一された評価基準が存在するわけではありませんが、評価を下げてしまうよく知られた要因はあります。 それを説明していきます。

評価が下がる主な要因

送信元の評価が下がる主な要因を紹介します。

SPF/DKIM が設定されていない

SPF/DKIM とは、メールの送信ドメインを認証するための仕組みです。 なりすましメールを防ぐことができます。

自分で「私は〇〇の者です」というだけ言う人と、身分証明書を合わせて提示してくれる人のどちらが信用できるか、といったイメージですかね。

SPFはIPアドレスを使って、認証をしています。 メールの送信元のIPアドレスと、DNS に設定されている SPF レコードが一致するかを検証します。

DKIM は電子署名を使って認証をしています。 秘密鍵で署名した情報をメッセージと共に送信し、DNS に設定されている公開鍵の情報を元に正当性を判断します。

どちらも DNS が絡んできます。

バウンスメールが多い

冒頭にも出てきたバウンスメールです。

バウンスメールとは、何らかの原因でメールが相手まで到達できなかったメールのことです。 バウンスメールには以下の2種類があります。

  • 一時的なエラーによるソフトバウンス(例:メールボックスの容量がいっぱいだったなど)
  • 恒久的なエラーによるハードバウンス(例:メールアドレスが存在しないなど)

本ブログに出てくるバウンスとは、後者のハードバウンスを指します。

なぜハードバウンスが多いと評価が下がるのかと言うと、適当なリストやランダムに生成したメールアドレスに送信している疑いがあるためです。 要はスパムを送る業者などに出る特徴ということなのでしょうね。 バウンスを検知した場合は、再送しない対策が必要になります。

AWS SES では、バウンス率を2%未満に保つことを推奨しています。 また、5%を超えると対応を求められ、最悪送信できなくなる場合があるようです。

最良の結果を得るには、バウンス率を 2% 未満に維持する必要があります。

バウンス率が 5% 以上になると、アカウントはレビュー対象になります。バウンス率が 10% 以上の場合は、高いバウンス率の原因となった問題が解決するまで、以後の E メール送信を一時停止することがあります。

Amazon SES Sending review process FAQs - Amazon Simple Email Service

苦情が多い

苦情とは、受信ユーザーが「このメールはスパムだ」と報告するやつです。 例えば Gmail には「迷惑メール」というボタンが設置されていますね。

AWS SES では、苦情率を0.1%未満に保つことを推奨しています。 また、0.1%を超えると対応を求められ、最悪送信できなくなる場合があるようです。

最良の結果を得るには、苦情率を 0.1% 未満に維持する必要があります。

苦情率が 0.1% 以上になると、アカウントはレビュー対象になります。苦情率が 0.5% 以上の場合は、高い苦情率の原因となった問題が解決するまで、以後の E メール送信を一時停止することがあります。

Amazon SES Sending review process FAQs - Amazon Simple Email Service

受信ユーザーが明示的に送るものなので、スパム判定の重要な指標になっていそうですね。

メールに「unsubscribe」リンクが設置されて、ワンクリックで購読解除できるようになっているサービスも多いですね。(特に海外のサービスに多い印象です) これはユーザビリティ以外に、購読解除を素早く行わせることで「迷惑メール」ボタンを押されないようにする、という理由もあるのではないかと思います。

AWS SES を使う場合の注意点

AWS SES は上記のようなバウンスや苦情を自動で管理・対応してくれません。 これらを監視・対応するのはAWS利用者の責任となっています。 そのため AWS SES を使う際には、最低でもバウンスや苦情を検知しておく必要があります。

(2020/09/20 追記)
アカウントレベルのサプレッションリストを使用すると対応もできるようです。 詳しくは「アカウントレベルのサプレッションリストを使用する」の章をご覧ください。

バウンスや苦情の検知方法

AWS SES でバウンスが起きた場合は、SNS Topic に送信することができるようになっています。 とりあえずバウンスと苦情に SNS Topic を登録し、メールでサブスクリプションを作成すればバウンスや苦情が発生した際に気づくことができます。

ses_sns.png

メールの場合はこのように配信されます。

sns_mail.png

配信件数が多くない場合は、メールで通知して対応するだけでも十分かもしれません。 ただし内容が JSON で送られてくるので読みにくくはありますが・・・。

たくさんのメールを配信するようになると、利用者のメールアドレスの打ち間違いなど、一定数のバウンスも発生してくるようになると思います。 そういった場合はシステム的に対応できるようにしておくほうが良さそうです。

なお CloudTrail では SendEmail などの API コールは記録されないようです。残念。

ses.png

Logging Amazon SES API calls with AWS CloudTrail - Amazon Simple Email Service

また AWS SES 以外のメールサービスにはバウンスに対応してくれるものもあるので、そちらも検討しても良いかも知れません。 例えば SendGrid にはバウンスに対応できる機能があるようです。 (私は使ったことがないので、具体的にどのようなものかは分かりません)

SendGridでは、ハードバウンスが発生した場合は、そのアドレスをサプレッションリストに登録します。サプレッションリストに登録されている宛先への送信リクエストがあった場合、SendGridはそのメッセージを破棄(Drop)し、受信者のサーバーへの送信処理を行いません。無効なアドレスに対し送信し続けることはレピュテーションを下げる原因となってしまうからです。

バウンスメールとその対策 | SendGridブログ

Tips: バウンスや苦情をテストする場合

バウンスなどのテストをする場合は、AWS から提示されているテスト用のアドレスを使いましょう。 これらのアドレスはバウンス率や苦情率にカウントされません。

Testing email sending in Amazon SES - Amazon Simple Email Service

AWS SES でバウンスなどをシステム的に対応する場合

基本的にやり方はAWS利用者に委ねられています。 バウンスや苦情は SNS Topic に対して通知することしか設定できないようなので、SNS から HTTP や Lambda のスブスクリプションを作ってその先で何らかの対応をする、という感じになるでしょう。

私自身、ここまで対応したことはなかったので、バウンスメールを捕捉してメール送信を制御するサンプル実装を作ってみました。

GitHub: hyiromori/example-ses-bounce

構成はこんな感じです。

example-ses-bounce.png

バウンスメールを検知した場合に DynamoDB に保存して、送信前にバウンスの履歴がないかをチェックするような仕組みになっています。 詳しく見たい方は README をご覧ください。

アカウントレベルのサプレッションリストを使用する(2020/09/20 追記)

ブコメで「アカウントレベルのサプレッションリストを使えばもっとシンプルになる」というコメントがありました。 情報ありがとうございます。

この機能は2019年11月にリリースされたようですね。

Amazon SES がアカウントレベルの抑制リストを発表

試してみましたが、上記のサンプル実装ような仕組みを構築しなくてもこれを使えば良さそうでした。 以下、調査や試した結果などを記載します。

アカウントレベルのサプレッションリストの有効化

2019年以前から使用している場合は、明示的に有効にしないといけないようです。

2019 年 11 月 25 日以降に Amazon SES の使用を開始した場合、アカウントはバウンスと苦情の両方に対してアカウントレベルのサプレッションリストをデフォルトで使用します。この日付より前に Amazon SES の使用を開始した場合は、Amazon SES API の PutAccountSuppressionAttributes オペレーションを使用してこの機能を有効にする必要があります。

アカウントレベルのサプレッションリストの使用 - Amazon Simple Email Service

バウンスと苦情の両方を有効にする場合は、以下のコマンドで有効にできるようです。

$ aws sesv2 put-account-suppression-attributes \
      --suppressed-reasons "BOUNCE" "COMPLAINT"

アカウントレベルのサプレッションリストの動作検証

挙動を試したかったので、適当なアドレスにメールを送信して確かめました。

※注意:今回は検証のためにやりましたが、適当なメールアドレスに送信すると当然バウンスレートが上がってしまうので、可能な限りやらないでください。ちなみに、私が個人で使っているAWSアカウントでやっています。

上記の有効化以外は、特に設定をしなくてもサプレッションリストに自動的に追加されました。

$ aws sesv2 list-suppressed-destinations 
{
    "SuppressedDestinationSummaries": [
        {
            "EmailAddress": "b33a624b-e250-4976-80f7-4fb6eec55165@gmail.com",
            "Reason": "BOUNCE",
            "LastUpdateTime": "2020-09-20T08:02:05.934000+00:00"
        }
    ]
}

この後同じメールアドレスに再送信してみましたが、実際に送信されませんでした。 またバウンスにもカウントされませんでした。 上記のサンプル実装のように自前で色々やらなくても、これだけで大体のケースをカバーできそうです!

なおテスト用の bounce@simulator.amazonses.com に送信してもサプレッションリストには追加はされないようです。

Gmail の苦情データについて

ドキュメントを読んでいると気になる記述がありました。

Gmail では、Amazon SES に苦情データが提供されません。受取人が Gmail ウェブクライアントの [迷惑メール] ボタンを使用して、受信したメールを迷惑メールとして報告した場合、アカウントレベルのサプレッションリストには追加されません。

アカウントレベルのサプレッションリストの使用 - Amazon Simple Email Service

Gmail の苦情(「迷惑メール」ボタン)に関しては自動で追加されないようです。 恐る恐る Gmail で「迷惑メール」ボタンをクリックしてみると・・・、苦情率も上がらず、設定している SNS での通知も来ませんでした。

どうしてなのか気になって調べてみると、苦情のデータはフィードバックループという仕組みで提供されているようです。 しかしながら Gmail はこのフィードバックループに対応していないようでした。

Gmailはいまや世界で最も有名なメールサービスとなりました。その中身はGoogle独自の技術でブラックボックス化されており、ホワイトリストやフィードバックループなど多くのISPで利用されている技術は利用されていません。

Gmail宛にメールを届けるための5つのポイント | SendGridブログ

正確には対応しているけれども、条件を満たした場合のみしか利用できないようです。

(※) Gmailもフィードバックループに対応していますが、利用可能なのは以下の条件を満たしているESPのみです。
 ・MAAWG(Messaging Anti-Abuse Working Group)のメンバーである
 ・Googleに「良い送信者」であると認められている

フィードバックループ【入門】 | SendGridブログ

Google は Postmaster Tools というツールを提供しているようです。 Gmail に関しては、こちらで迷惑メールを確認する必要があるようですね。

Postmaster Tools を使ってみる - Gmail ヘルプ

Gmail で「迷惑メール」ボタンを押されても、苦情率が上がるわけではないので AWS SES から送信できなくなることはなさそうです。 しかし多くのユーザーをもつ Gmail に迷惑メールと判定されるようになると、送信しても迷惑メールに振り分けられてしまうためこちらも注意する必要がありそうです。

おわりに

メールは歴史が長く、いろいろな場面で使われているため様々な対策が講じられています。

「うちのサービスはあんまりメールを送信していないから大丈夫」と思っていても、認証情報がスパム業者に漏れたりして大量のスパムメールを送られてしまい、サービスで必要なメールが送れなくなることもありえます。 (もちろん認証情報が漏れないようにすることが一番重要ですが)

バウンスや苦情が発生しないように、また発生しても素早く検知して対応し、安定してメールを使っていきたいですね。

PR

コネヒトではエンジニアを募集しています!

執筆時に参照した資料など

SendGrid さんのブログは、メールに関する様々な情報がまとまっていて、とても参考になります。

Firebase Performance Monitoringを活用したパフォーマンス可視化

こんにちは。2017年11月にAndroidエンジニアとしてjoinした@katsutomu です。 Android愛を表現するために緑髪にしました。(既に金髪に戻ってしまいました。

さて、今回はFirebaseの機能の一つである、Firebase Performance Monitoringについて書かせていただきます。 Firebase Performance MonitoringはiOS アプリ、Android アプリ、ウェブアプリのパフォーマンス上の問題を分析できる機能で、コネヒトで実験的に一部の機能を導入し始めています。今回はツールの導入やコンソールで分析できる内容について紹介します。

Androidアプリへ導入する

基本的にPerformance Monitoringの導入ガイドを読んでいただければ問題なく導入ができるかと思います。サマリーを紹介すると以下の手順です。

ステップ 1:アプリに Performance Monitoring SDK を追加する
ステップ 2:アプリに Performance Monitoring プラグインを追加する

またFirebaseコンソールで計測をするには、この作業の前にAndroid プロジェクトに Firebase を追加する必要がありますが、今回はその説明は割愛します。こちらの記事を参照ください:https://firebase.google.com/docs/android/setup?hl=ja

ステップ 1:アプリに Performance Monitoring SDK を追加する

このステップではアプリの自動トレースやカスタムトレースの機能を有効にします。 作業内容はmoduleレベルのgradleファイルにfirebase-perfの参照を追加するのみです。

dependencies {
    // Performance Monitoring SDKを追加
    implementation "com.google.firebase:firebase-perf:19.0.8"
}

ステップ 2:アプリに Performance Monitoring プラグインを追加する

このステップでは@AddTrace アノテーション処理とネットワーク リクエスト モニタリングを実現する機能が有効になります。

1. ルートレベルのGradle ファイルにPerformance Monitoring プラグインを追加
buildscript {
    repositories {
        google()
        // binary追加のために必要
        jcenter()
    }
    dependencies {
        // 最新機能がv3.4.0と依存している
        classpath 'com.android.tools.build:gradle:3.4.0'
        // プラグインの追加 
        classpath 'com.google.firebase:perf-plugin:1.3.1'
    }
}
2. モジュールの Gradle ファイルにプラグインを含めるためのルールを追加
// プラグインを有効化
apply plugin: 'com.google.firebase.firebase-perf'

android {
  // ...
}

最新の機能を利用するためにはAndroid Gradle Pluginの依存関係を少なくともv3.4.0に更新する必要があります。 このステップを行うとtransformClassesWithFirebasePerformancePluginForDebugというビルドステップが追加されbuildの時間が大幅に増えますが、この対策は後述します。

Before(7秒程度)

f:id:katsutomu0124:20200908132551p:plain

After(22秒程度)

f:id:katsutomu0124:20200908132626p:plain

パフォーマンスをモニタリングする

ここまでの作業で、Firebaseコンソールでモニタリングをする準備が整いました。 モニタリングできるものには、FirebaseSDKが自動で解析するものと、開発者が追加するカスタームトレースがあり、いずれも計測の始点と計測の終点の間のパフォーマンスのレポートになります。今回は自動で解析するものについて紹介します。

自動所要時間レポート

自動で解析されるものは、大きく分けて、アプリ内の所要時間レポートとネットワークレイテンシレポートになります。SDKが自動的に分析する対象は、以下の4つです。

  • アプリ起動トレース
  • フォアグラウンドトレース
  • バックグラウンドトレース
  • 画面トレース

アプリ起動トレース、フォアグラウンドトレース、バックグラウンドトレースの3つはそれぞれ時間が計測され、画面トレースは画面が存在する間のパフォーマンスがトレースされます。それぞれを簡単に解説すると以下の通りです。

アプリ起動トレース

アプリを起動するまでにかかった時間が計測されます。 FirebasePerfProvider ContentProvider が onCreate メソッドを完了してから最初のActivityのonResumeが呼ばれるまでの期間が対象となります。_app_startというトレース名でコンソールに表示されます。

フォアグラウンドトレース

アプリがフォアグラウンドで実行されている時間が計測されます。 最初にAcitivityがonResumeを呼んでから、最後にActivityがonStopを呼ぶまでの間が対象となります。トレース名は_app_in_foregroundです。

バックグラウンドトレース

アプリがバックグラウンドで実行されている時間が計測されます。最後にAcitivityがonStopを読んでから、次にフォアグランドに移行するときにActivityがonResumeを呼ぶまでの間が対象となります。 トレース名は_app_in_backgroundです。

この3つは以下のような傾向値やグラフがコンソールで表示されます。

f:id:katsutomu0124:20200908132751p:plain

中央値を主軸に95%タイルや5%タイルの値や、過去90日までの変化を見ることが可能です。 これらの情報が、可視化されたことで、特定のバージョンで数値が悪化していないかを把握できるようになります。

画面トレース

次に画面トレースですが、こちらは各画面毎のレンダリング遅延やフリーズがどの程度発生しているかが比率で表示されます。計測対象はonActivityStartedが呼ばれてからonActivityStopped呼ばれる間となります。トレースはActivityのクラス名毎に表示されます。

f:id:katsutomu0124:20200908132821p:plain

これらを利用することで画面毎のパフォーマンスが把握できるため、極端にパフォーマンスが悪い画面から原因調査や対策を考えることが可能になります。

ネットワークレイテンシ

次はネットワークに関わるモニタリングです。 AndroidやiOSアプリの開発はネットワーク外からデータや画像を取得することが多くあると思いますが、これらのパフォーマンスもアプリ開発する上での大きな関心事になると思います。 先述したようにFirebase Performance Monitoringを導入することで、これらも可視化が可能になります。 分析できる内容は以下の3つです。

  • 応答時間: リクエストが発行されてからレスポンスが完全に受信されるまでの時間
  • ペイロード サイズ: アプリによってダウンロードまたはアップロードされたネットワーク ペイロードのバイトサイズ
  • 成功率: 全レスポンス数に対する成功レスポンス数の割合

コンソールでは以下のように表示がされます。 f:id:katsutomu0124:20200908132846p:plain

ネットワークアクセスのレポートをexample.com/api/*/*.jsonexample.com/cdn/*/*.jpegのようなパターン毎に分析結果を表示することが可能で、特定のエンドポイントでのレスポンスや成功率が悪化していないかを把握することが可能になっています。

なお、これらは全てのネットワークアクセスを分析する訳ではなく以下の機能を利用しているアクセスが分析の対象になります。

  • OkHttp3, specifically HTTP client v3.x.x
  • Java's URLConnection, specifically HttpURLConnection and HttpsURLConnection
  • Apache HttpClient

コネヒトではAPIリクエストにOkHttp3を採用しているためAPIアクセスは全て可視化されています。

問題を検知する

さて、ここまでの内容でパフォーマンス上の計測値を表示する方法を紹介しましたが、実際にチームで改善活動をするためのちょっと便利な機能を紹介したいと思います。
チームで改善を進めるためには、パフォーマンス上の問題の判断基準を揃えると、改善を進めやすくなると思います。 具体的にはアプリの起動時間が1秒以上かかっている場合は問題が発生している可能性が高いと言ったものや特定のAPIレスポンスが500ミリ秒以上を超えている場合は問題が発生しているというような基準ですが、FirebasePerformanceでは所要時間やネットワークレイテンシに対して、コンソール上でしきい値を決めることができます。

これを設定することで、問題が発生している箇所、つまりしきい値を超えた箇所がコンソール上に表示することができるようになっています。

f:id:katsutomu0124:20200908133453p:plain

この機能を活用し、チームでパフォーマンス上のしきい値を決め、これらの基準を守るようなルールを整備することで、可視化をしながら、ユーザーに使いやすいアプリを提供することを目指せる希望が出てきます。 しかしながら、コネヒトではまだ実験的に可視化した段階なので、今後は基準やルール作りは、チームメンバーと議論しながら進めていこうと考えています。

デバッグビルドの時にPerfomance Monitorringを取り除く

最後に、Performance Monitoring SDKを導入することでビルドが遅くなることへの対処方法を紹介します。設定は簡単で、gradleでinstrumentationEnabledをfalseにすることで、SDKの機能をOFFにすることが出来ます。

android {
  // ...
  buildTypes {
    debug {
      FirebasePerformance {
        // Set this flag to 'false' to disable @AddTrace annotation processing and
        // automatic HTTP/S network request monitoring
        // for a specific build variant at compile time.
        instrumentationEnabled false
      }
    }
  }
}

instrumentationEnabled true (22秒程度)

f:id:katsutomu0124:20200908133553p:plain

instrumentationEnabled false (4秒程度)

f:id:katsutomu0124:20200908133626p:plain

コネヒトでは、デバッグ環境では特に用途がないため、Performance Monitoring SDKの機能をOFFにしています。この設定をすることで本番環境ではPerfoamance Monitoringを有効にし、デバッグ環境ではビルド時間の短縮をすることで円滑に開発を進められるようにしています。

最後に

今回はFirebase Performance MonitoringのAndroidアプリへの導入方法と、コンソール上で分析できる内容について紹介させていただきました。 紹介した内容はコネヒトではまだまだ実験段階のものですが、今後は基準やルールを整備して、より快適なアプリをユーザーに届けていきたいと考えているので、一緒に試行錯誤してくれる方を絶賛募集中です!まずはお話だけでも聞きにきてください〜!

hrmos.co

テクノロジー推進グループでプッシュ通知開封率予測モデリングコンペをしました

こんにちは、コネヒトでアプリケーションエンジニアをしているaboです。今年の夏はいかがお過ごしでしょうか。とても暑い日が続いていますが、桃なんかは今が食べ頃なので冷やして食べると美味しいです。

さて、先日私の所属するテクノロジー推進グループで、プッシュ通知開封率予測モデリングコンペを行いました!私は主催者ではありませんが、機械学習未経験として参加した立場から、ブログにまとめようと思います。

f:id:aboy_perry:20200827132140p:plain

テクノロジー推進グループとは?

元々コネヒトでは、機械学習エンジニアとインフラエンジニアとでタッグを組んで、サービスに機械学習を組み込んでいました。以下に2つの事例を紹介します。機械学習でサービスを良くする事例ができたことで、社内からの注目も高まっています。

tech.connehito.com

tech.connehito.com

ただ、機械学習をサービスに組み込むプロセスに課題もありました。機械学習のモデルを実装することはできても、サービスに組み込む際は他チームにいるアプリケーションエンジニアの手を借りないといけないため、リソース調整や詳細設計にコストがかかっていました。会社として機械学習のチャレンジをよりたくさん繰り返していくにあたって、よりスピード感のある組織体制にする必要がありました。

そこで今期から生まれたのがテクノロジー推進グループで、主に「機械学習を使ってサービスを成長させる」ことを目的としたチームです。

チーム構成は

  • @takapy(機械学習エンジニア)
  • @shnagai(インフラエンジニア)
  • @abo(アプリケーションエンジニア)

の計3人(コンペ後にアプリケーションエンジニアが1人増えて今は計4人)です。

機械学習の実装をメインで担当するのは機械学習エンジニアですが、インフラエンジニアにも機械学習の実装経験があるため、相互に技術的な相談やPRのレビューなどを行っています。また、アプリケーションエンジニアをチーム内に置くことによって、前述した課題の解決をはかっています。このアプリケーションエンジニアが私で、必要に応じてサーバーサイドやiOSのコードを書いたりしています。

コンペ形式に至った経緯

テクノロジー推進グループでは発足当初、データ分析をもとに仮説を立てて(あるいは仮説を裏付けるデータ分析を行い)、施策(A/Bテストなど)に昇華させて、実装していく、という動きをしていました。ただこのデータ分析に少し手詰まり感があったのと、なにより毎週毎週繰り返されるデータ分析に少しマンネリ感も感じていました。これはチームメンバー全員が感じていた課題でした。

そんなときに、施策の一つであった「プッシュ通知 × 機械学習」について、機械学習エンジニアとインフラエンジニアのメンバーがkaggleなどのコンペ参加経験があることから、「優勝者のモデルを採用するコンペ形式でやってみると楽しいんじゃないか!!!」ということになりました。せっかくONE TEAMになったので、機械学習未経験の私はもっと機械学習の理解を深めたかったですし、チームとしての一体感も上がりそうな予感がしました。とてもワクワクするようなコンペの開催が決まりました。

コンペの設計

コンペの設計は機械学習エンジニアである @takapy がやってくれました。

ママリでは1日に数回、人気の投稿(ユーザーによる質問)をプッシュ通知で配信していて、開封率が高い通知が事前に分かれば、ユーザーがママリを開いてくれる頻度が高くなることが期待でき、また手作業で行っている投稿の選定工数の削減にも繋がります。今回のコンペでは、投稿本文やカテゴリー、プッシュ日時、投稿者の属性、実際の開封率(学習データのみ)など15個の特徴量が用意され、期間は約2週間で行いました。

また、コンペ開催前に機械学習エンジニアがJupyter Notebookの基本的な使い方や、特徴量の追加やモデルの生成方法など一連の流れをレクチャーしてくれたため、未経験の私でもすんなりコンペに参加することができました!

コンペを盛り上げるために、途中で中間報告を挟んだのですが、その時点では優勝候補筆頭の機械学習エンジニアがビリだったので、予想以上に盛り上がりました ✊ この時点でそれぞれがどうアプローチしたかを共有しあって、今後の参考にし合いました。

f:id:aboy_perry:20200827031450p:plain
中間報告で機械学習エンジニアのお尻に火が付く

コンペの最終結果は...機械学習エンジニアが優勝!

中間報告でお尻に火がついた彼が追い上げ、機械学習エンジニアの優勝で幕を閉じました!

f:id:aboy_perry:20200827142608p:plain
最終結果発表の様子

参加者がコンペでやったこと

参加した3人がそれぞれやったことを紹介します。

takapy(機械学習エンジニア)がやったこと

  • 最初にベースラインのnotebookを作っていたので、EDA的な事はあまりしていない
  • 一度100以上の特徴量を作って、そこから数十個にselection
  • pseudo labeling
  • trainの外れ値を学習データから除外
  • テキストはTF-IDFで変換し、下記手法を試して一番よかったUMAPを採用(BERTも試したけど、TF-IDFの方がよかった)
    • NMF
    • UMAP
    • t-SNE
    • SVD
  • Optunaでハイパーパラメータチューニング

shnagai(インフラエンジニア)がやったこと

  • cluster以外は、質問に紐づく情報だからclusterをどう表現するかが鍵かなーと思ったりした
  • test,trainを色々と見比べる
  • cluster,category_id軸に平均、最大、最小開封率を特徴量として追加
  • cluster平均開封率*category_id(最大、最小開封率)を追加
  • 特徴量毎の開封率分布みて得量ありそうなものを特徴量にしていく
    • wednesdayフラグ、q_freshness(qの新鮮度)
  • GridSearchでパラメータチューニングを実施
  • FeatureImportanceをみながら寄与度低い特徴量は削っていくアプローチ
  • テキスト
    • テキストではうまくモデリング出来なかった
    • tfidf化して、次元圧縮を色々試す
      • 次元数 2,10,100,300
      • pca(TruncatedSVD),nmap

abo(アプリケーションエンジニア)がやったこと

  • 質問者の属性は不要そうなので全削除、そのほかは消してスコアが悪くなったら戻すの繰り返し
  • 質問についているそれぞれのタグを出現頻度に応じて数値化(TF/IDFのようなもの)
  • LightGBMのパラメータ、num_iterationsとnum_leavesの調整
  • 質問本文の数値化をいろいろ試したがスコアをあげることはできなかった
  • (モデリングとはズレますが)やったことのログを細かく残した

参加者の感想

参加した3人の感想を紹介します。

takapy(機械学習エンジニア)の感想

  • オンライン/オフライン含めていくつかのコンペに参加したことはあるが、設計側になったのは初めてで、かなり考えることが多く大変だった(今回はクローズドなコンペだったので、そこまでプレッシャーはなかったが、これはオープンなコンペとなると、主催者側の苦労は計り知れない・・・。主催されている方々の凄さを改めて思い知りました)
  • マルチモーダルデータだったので、タスクとして難しかった
  • データ量がそこまで多くなかったので、特徴量を足したり引いたりいろいろ実験できてたのはよかった(初めてモデリングするメンバーもいたので、この辺りが手軽にできたのは良かった)
  • ベースラインのnotebookを作って共有し、「まぁここからそんなにスコア伸びないだろ」と思って前半は優雅に過ごしていたところ、他のメンバーが試行錯誤してスコアを伸ばしきたので、焦った&楽しく参加できた!
  • 自身のスキルを他者に分配することで、チームのベースとなるスキル・知識の底上げもできて良かった。

shnagai(インフラエンジニア)の感想

  • 悔しすぎる
  • でもめちゃ楽しかったし、これ系好きだなーと思ったのでどっかのコンペチャレンジしてみようかな
  • テキストをうまく使えなかったので、たかぱいの解法知りたい
  • Jリーグの時はドメイン知識的なやつを使えてうまく結果に繋がったけど今回はうまく出来なかった。。。
  • 色々と知識が断片化してることに気づいたから、実際に手を動かすの大事だよな
  • またやりたい!!

abo(アプリケーションエンジニア)の感想

  • 予測結果と実際の開封率の差を説明変数でグルーピングして可視化することで何かしらインサイトを得て、アプローチに幅を出したかった
  • ログを細かく残したことによって、何をしてスコアが上がったのか下がったのかを忘れないし、あとから見直す材料になった
  • スコアが良くなった時も、なかなか良くならない時も、楽しかった!
  • 初見のことをたくさん学べた(お二人に分からないことを聞きまくった)
  • 同僚の @takapy と @shnagai すげー!って思った
  • Python/Pandasが手に馴染んでないのでそういう意味でのもどかしさはあって悔しい

また、コンペを開催していなければ、もっと言えばテクノロジー推進グループになっていなければ、絶対見ないであろう機械学習系の記事もたくさん読みました。領域の違う人が同じチームになったことで生まれた良い効果なのではないかと思います。

f:id:aboy_perry:20200827035727p:plain
チームメンバーから教えてもらった記事を読んでみようとしてる様子

おわりに

今回は、今期から誕生したテクノロジー推進グループでの、ユニークな取り組みを紹介しました。機械学習未経験の私が参加してみて、自チームのコアである機械学習がより身近に感じるようになりました。

また、優勝したモデルで実際にA/Bテストしたりサービス運用に組み込んだ話は @takapy がブログにしてくれると思うので、気になった方はもう少しお待ちください。


コネヒトではこんな感じでやり方を工夫しながら楽しく仕事をしています!現在全職種募集中ですので、まずはお話だけでも聞きに来てください〜。 hrmos.co