Quantcast
Channel: コネヒト開発者ブログ
Viewing all 376 articles
Browse latest View live

ママリでAWSを使った動画配信をはじめました。

$
0
0

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

ママリではよりユーザーにとってわかりやすく情報を伝えるべく、4/22 からママリ内の人気記事などを動画にし配信する取り組みをはじめました。

news.connehito.com

今回、動画配信にあたって AWS 上で動画変換と配信を行ったので、使用したサービスやシステム構成などを解説しようと思います。

システムの全体像

AWSサービスを使って、以下のような構成で作りました。

f:id:hyirm:20200511084620p:plainCloudCraft

※この図は動画の配信のみに絞っています。動画の情報をアプリに配信するAPIなどは別にあります。

大まかに説明するとこんな感じです。

  • CloudFront + S3を使ってHTTP (HLS) で配信
  • MediaConvertを使ってMP4形式の動画をHLS 形式へ変換
  • API Gateway + Lambdaを使って動画変換のAPIを提供
    • DynamoDBを使って動画の情報を保存

以下、詳しく説明します。

動画のフォーマット

配信にあたり、まず動画をどのように配信するかを決めました。 今回配信する動画は、数十秒程度がメインなのでそのまま動画ファイルを配信する方法も検討しました。 ただ、1分を超える動画もありますし、全部ダウンロードしないと再生できないというのは体験的に良くないのでストリーミングでの配信に決めました。

動画のストリーミング配信は全く知見がなかったので色々調べたところ、以下の2つが主流のようです。

  • HLS (HTTP Live Streaming)
  • MPEG-DASH(ISO/IEC 23009)

あまり詳細な仕様までは調べきれていないですが、いろいろ調べた結果、HLSの方がシンプル、MPEG-DASHはDRMなどの複雑な要件にも対応できるがその分仕様も複雑、という印象でした。

今回の配信要件はシンプルで、iOS/Android でもネイティブのエンジニアの方から再生できることが可能であることを確認したのでHLSを採用することにしました。 ちなみに、HLSはAppleが提唱している規格なので、iOSは当然対応していますし、HLS形式の動画はSafariで直接開くことも可能です。(他のブラウザは対応していません)

CloudFront + S3

HLSは配信にあたって静的なHTTPサーバーにファイルを配置するだけで良いので、よくある CloudFront + S3で配信しています。

MediaConvert

S3に置いてある動画ファイルを変換して、変換した動画ファイルをS3に出力してくれるサービスです。 今回の動画配信で最も重要なサービスでした。

ジョブの作成

MediaConvert は「ジョブ」という単位で変換を実行します。 ジョブは、以下のような入力と出力を設定して、変換の詳細を設定します。

変換の全体像

f:id:hyirm:20200511084710p:plain:w320

入力が一つに対して、出力が複数あることが分かるかと思います。 例えば、1つの動画からHLSとMPEG-DASHの2つを同時に変換して出力することができます。

ちなみに入力も複数作れるようなのですが、今回複数入力を使う用途はなく私にも用途が分からないので、説明は割愛します。 (動画の結合とか、複数の動画を横に並べて結合したりできるんでしょうか?)

入力の設定

f:id:hyirm:20200511084801p:plain

入力は S3に配置した動画ファイルのパスを指定します。 他にもたくさん設定項目がありますが、今回はここの設定以外は特に使っていません。 動画は奥が深い・・・。

出力の設定(動画)

f:id:hyirm:20200511084926p:plain

出力先の S3プレフィックスを入力します。 (※プレフィックスであり、このままの名前で出力されるわけではありません。)

また、下の方の「出力」欄を見ると、2つの出力があるのが分かるかと思います。 例えばHLSであれば、複数のビットレートの動画を1つの配信に含め、クライアントがネットワークの状況に応じて切り替えることができます。 そういった場合に、1つの出力グループに対して、複数の出力を設定することができます。 (ちなみに、今回のママリの配信では諸事情により1つの出力にしています)

f:id:hyirm:20200511084953p:plain

各出力に対して、動画の詳細を設定できます。 こちらも大量の設定項目がありますが、主に使ったのは以下の2つです。

  • ビデオコーデックと解像度
  • ビットレート

他は特にいじらなくても、デフォルトのままで特に問題ありませんでした。

出力の設定(キャプチャ画像)

今回、動画に配信する際にサムネイル用の画像も一緒に作りたいということで、動画内から1秒おきにキャプチャ画像を作り、その中から適切な画像を選んで配信する、という方法を取りました。

例えば、30秒の動画なら、30枚のJPEG画像が出力され、その中からサムネイルとして良さそうなものを選ぶ、という感じです。

f:id:hyirm:20200511085029p:plain

こんな感じの出力設定をします。 画質にあまり拘らなければ、フレームレートを調整すれば大丈夫です。 1/1で1秒に1枚、という設定になります。

変換の実行

設定が完了したら、変換を実行します。 以下のようにジョブリストに表示され「COMPLETE」と表示されれば完了です。 ちなみに設定にエラーがある場合は「ERROR」と表示されます。

f:id:hyirm:20200511085044p:plain

変換が完了すると S3にはこのように出力されます。

f:id:hyirm:20200511085108p:plain

※ジョブ作成時に設定したプレフィックスなどによって出力先は変わります。

出力先を CloudFrontで配信している S3バケットにしておくと、変換完了後にすぐインターネットからアクセスできるので便利でした。

API Gateway + Lambda

AWS SDK でも実行できるのですが、curlやスクリプト、管理画面などから使いやすいように、シンプルなAPIを提供するようにしました。

  1. アップロードの情報を提供するAPI
    • S3 の Pre-Signed URLを使って、アップロードに必要な一時URLを生成して返却するAPIです。
    • アップロード自体は、アクセス元の責務にしてます。
  2. 変換をリクエストするAPI
    • MediaConvertにジョブを作るAPIです。
    • ジョブIDなどは DynamoDBで管理しています。
  3. 動画情報を取得するAPI
    • MediaConvertに問い合わせて、ジョブの情報を取得して返却します。
    • 変換が完了している場合は、インターネットからアクセスできるURLなども返却します。

なお、API Gatewayは API Key でアクセスを制限しています。

変換作業

今回は、1本辺り数十秒程度がメインの動画を200本以上、再生時間だと2時間以上の動画ファイルをアップロード&変換しました。 curlなどで手動アップロードも可能ですが、時間がかかる、ヒューマンエラーが発生しやすい、設定ミスなどで再変換したい場合でも再実行しやすくしたい、といった目的のため Node.jsでスクリプトを書きました。

詳細は省きますが Node.jsを使いアップロードを最大10並列で実行するスクリプトを書いて実行しました。 大体変換完了まで16分ほどで終わったので、かなり早くできました。 MediaConvertは複数実行すると並列に変換してくれるので、単に2時間の動画を変換するよりも早く終わります。便利ですね!

料金

CloudFront での配信料金

これは動画に限らないですが CloudFrontからインターネットへの転送は 1GB あたり 0.114 USD かかります。 $1 = 107円で、100GB の配信をしたとすると 0.114USD * 100GB * 107円 = 約1,220円かかります。

aws.amazon.com

MediaConvert での変換料金

今回の肝である MediaConvert での変換にはどのぐらいの料金がかかるのか紹介します。 最近のスマホは解像度が高くなっているので、MediaConvert の算定方法だと 4K に該当する画質で変換しました。

aws.amazon.com

フレームレートは <30fpsでやったので、 1分間の変換料金は 0.034USD になります。 $1 = 107円で、60分の動画を変換した場合、 0.034USD * 60分 * 107円 = 約218円になります。 めっちゃ安いですね!

VODやってる会社さんとかはわかりませんが、今回のように小規模な動画配信を行う場合、 動画変換の開発や設定、サーバー維持費など考慮すればめちゃくちゃコスパいいサービスだな、と感じました。

その他

S3API GatewayLambdaDynamoDBなどの料金などがありますが、CloudFrontでの配信料金に比べると微々たるもの、もしくは無料枠で収まる程度でしかなかったので、省略します。

おわりに

今回初めての動画配信でしたが、HLS で配信し、AWS のサービスを使うことでかなり容易に構築や配信準備を行うことができました。 動画配信は敷居が高いイメージでしたが、今回のようなシンプルな配信と AWSのサービスを組み合わせる場合は割と楽にできます。

未知の領域に踏み込んでみるのは楽しいですね。 コネヒトでは、新しいことにどんどんチャレンジしたいエンジニアを募集中です!

www.wantedly.comwww.wantedly.comwww.wantedly.com


カテゴリ類推の機械学習モデルをプロダクションに導入した話

$
0
0

こんにちは。インフラエンジニアの永井(shanagi)です。

今回は、昨年から取り組んでいる機械学習の分野で、自分の作ったモデルをサービスに本番リリースする機会があったので、PoCで終わりがちな機械学習をプロダクト導入にこぎつけるためにどのようなプロセスを歩んだのかとそこで得た自分なりの知見を中心にご紹介できればと思います。

機械学習のプロダクト導入に向けては歩むステップが非常に多く、個々の詳細な内容について細かく書くと非常にボリューミーな内容になってしまうので、詳細は割愛し機会があればまたどこかでご紹介出来ればと思います。

内容は、ざっくり下記2つに絞りました。

  • どんな機能をリリースしたのか?
  • 導入までの全体アプローチ

ちなみに、なぜインフラ畑の自分が機械学習をやっているのかについては、昨年末に下記ブログにまとめたのでもし興味がある方がいれば読んでみてもらえればと思います。

kobitosan.hatenablog.com

どんな機能をリリースしたのか?

まずは今回どのような機能をリリースしたのかを紹介します。 ママリはママ達の悩みや疑問を解決するQAプラットフォームなので、毎日ユーザであるママさん達から多くの質問投稿があります。

ママリ内のカテゴリとしては、「妊娠・出産」「育児・グッズ」「お金・保険」などがあり、質問文の内容に合わせて投稿時にユーザに選択してもらっています。 今回は、この質問投稿時のカテゴリ選択において、質問本文の内容から適切なカテゴリを類推してユーザに推薦する「カテゴリ類推」エンジンをリリースしました。

↓質問の本文からカテゴリを類推している様子です!カテゴリを考え中の時にカテゴリ類推のAPIが呼ばれています。 f:id:nagais:20200512144003p:plain

詳しくは、この後の章で触れるのですがこのリリースによって、下記の指標にプラスの影響を出すことが出来ました。

- 回答率の向上
- 質問投稿時のCVRの向上

機械学習の導入は、導入時はマイナス影響なしの状態で入れてそこからモデルアップデートを通して数値を上げていくものという期待値調整をするので、初期モデルである程度プラスの成果が出たのは運がよかった面もあるな思っています。

導入までの全体アプローチ

前提として昨年からコネヒトでは、機械学習を活用したプロダクトの改善を組織として推し進めていくという取り組みを行っています。組織から機械学習導入を期待されているというのは、説明コストが大幅に下がること(PoCまでのハードルが下がる)を意味しており今回のカテゴリ類推導入に向けても大きな後ろ盾になりました。

全体のステップ

今回カテゴリ類推を本番リリースするにあたり大まかに下記のステップをたどりました。

①仮説作りのためのデータ分析
↓
②仮説を裏付けるためのデータ分析
↓
③モデル作成
↓
④オフライン検証と品質チェック
↓
⑤デモ作成(AWSとAndroidアプリ)
↓
⑥A/Bテスト(PoC)と効果測定
↓
⑦本リリース

それぞれどんなことをしたのかとそこで得た知見ベースで振り返っていこうと思います。

進める上で気をつけた点として、ある程度の精度はオフライン検証で出来るが、実際のユーザの行動は出してみないとわからない部分が大きいのでできるだけ早くA/Bテストまで持っていくことを意識しました。

①仮説作りのためのデータ分析

機械学習導入を期待されているといっても闇雲に機械学習を入れればいいというわけではもちろんありません。 サービスが抱える課題を解くためのツールとして機械学習が適切であればそれを使います。

その課題を可視化して理解し、仮説(機械学習で解決すべき課題)に落とし込むのにはデータ分析が必要です。 解くべき課題に関しては、主にサービスのKPIやそれを細分化したものの達成を設定することが多いと思います。今回のカテゴリ類推においても、当時のサービスKPIの一つになっていた回答率を分析することからはじめました。

データ分析を進める上での工夫として、属人化しがちなデータ分析の知見を気軽に共有するために、GitHubのissueでデータ分析のログを残すようにしています。 仮説を裏付ける際や簡単なモデルの試作品を作る際に使うjupyter notebookのソースも同じリポジトリで管理するようにしており、後からでも過去のプロジェクトを気軽に参照出来るようにしています。

f:id:nagais:20200512143537p:plain

可視化したデータは下記のようなダッシュボードにまとめておくことで、以後のA/Bテスト時の結果計測にも使えるような形にしておきました。

f:id:nagais:20200511205800p:plain

②仮説を裏付けるためのデータ分析

①のデータ分析の結果、質問に対して適切なカテゴリが選択されると回答がつく確率が上がりそうなことがわかりました。 このカテゴリ選択を適切にすることで回答率を上げられそうという仮説を裏付けるためのデータ分析に入ります。 同時に、どのようにして投稿のカテゴリを適切に設定するかを検討しました。その中で、今回採用する機械学習を使った質問投稿時の「カテゴリ類推」を導入すれば、ユーザが入力する投稿内容を元に適切なカテゴリを設定出来てUXも上がりそうという仮説が最終的に出来上がりました。

この仮説を裏付けるために更にカテゴリと回答に絞った分析を行い下記のような事実が浮かび上がってきました。(一部抜粋)

これらは質問文の内容から適切なカテゴリが入ることで回答を増やしてくれそうという仮説の裏付けになりました。

  • 特定のカテゴリに偏りがある(質問内容とは関係なくカテゴリを選択している質問が多くある)
  • ユーザは好きだったり興味のあるカテゴリを回遊しながら回答してくれていることが多そう

③モデル作成

続いて、ローカルのjupyter notebookで「カテゴリ類推」のモデル作成を行いました。 ざっくりメモですが、どんなことをやったか羅列しておきます。

  • 学習データの作成
    • 過去の膨大な質問データを使えるので加工等は不要
    • 1ヶ月分のデータを取ってpandasで分布みたりしてデータ件数を揃える
  • 質問文を機械学習で使える形に前処理する
    • 小文字化とテキストから不要な文字を削除や置換
    • neologdnを使った正規化とUnicode正規化
    • MeCabを使ったトークナイズ(どの品詞を使うと一番精度高いか色々と試したり)
  • 学習データとテストデータの分割
    • scikit-learnのtrain_test_splitを使う
    • 最初層化抽出してなくて、層化抽出することで大きく精度が伸びたのはなつかしい
  • 特徴抽出
    • 文字列をベクトルに変換する手法
    • Bag of WordsとTF-IDFを試す
    • 最終的に、TF-IDF(min_df,max_dfでストップワード兼ねる)を使った
  • アルゴリズム選定
    • scikit-learnを使っていたので、SVM,ロジスティック回帰,ランダムフォレストを試す
    • GridSearchしながら同じ条件でどのアルゴリズム一番精度が出るかを繰り返した
    • 恥ずかしながら当時はニューラルネットワークはまだ手をつけてなかったので試してない(今後検討)

手元である程度の精度が出るモデルが出来たのでオフライン検証と品質チェックに進みます。

④オフライン検証と品質チェック

オフライン検証として、学習データに含まれないN日分のデータからの正答率を出して指標としました。

オフライン検証の結果、ある程度精度の出る(ユーザに違和感なく提供出来そうな)モデルが出来ました。 ただ、機械的に正答率が高くても、ユーザにとって不快な推薦をしてしまうとサービス価値の毀損につながります。ママリはコミュニティサービスなので、せっかく質問しようとしたユーザにカテゴリの推薦で不快な思いをさせてしまっては機会損失につながります。

ここの部分の不安を解消するために、CSチームと連携して、オフライン検証の結果間違ったデータに関して品質チェックを行ってもらいました。 結果、ユーザに致命的に不快感を与えるような予測はないという裏付けをもらってから次のステップに進みました。

⑤デモ作成(AWSとAndroidアプリ)

ディレクターや社内で説明するのに動いているものを見せるのが一番説得力が増すと考えて、Androidエンジニアに協力してもらいデモ用のAPIを叩くデモアプリを作ってもらいました。 今回このデモの果たしてくれた役割は大きく、やはり動くものをサクッと作るのは大事だなと思いました。

⑥A/Bテスト(PoC)と効果測定

デモやデータ分析と品質チェックの結果をディレクターに説明して、A/BテストへのGoサインを無事こぎつけました。

A/Bテストを行う上では、当たり前ですがトラッキングするためのダッシュボードを事前に作っておくことが重要でした。 今回は①で作ったダッシュボード+mixpanelでアプリver毎のイベントをトラッキングすることで効果測定を行いました。 結果指標として掲げていた項目で明らかな優位が見られました!

- 回答率の向上
- 質問投稿時のCVRの向上

裏話でもないですが、ユーザに直接届くものを作ってリリースする機会はこれまであまりなかったので、結果を毎時ドキドキ見ていたのを思い出します。 興奮してこんなツイートもしてました。

⑦本リリース

効果測定の結果、明らかな優位が見られたので簡単なレポートをまとめ関係者に共有し本リリースが決まりました。 ここでのレポーティングは事前にデータ分析する時に効果測定時のレポートを意識しながら可視化したのが役立ちサクッと作りました。

その後、A/Bテスト時は実現までのスピードを重視し手動作成したモデルを使っていたので、前処理とモデル作成のバッチ処理を作成し自動化しました。バッチはAWS上でECS+Fargateベースで動かしています。

最後にどんなアーキテクチャで動いているのかを載せておきます。

f:id:nagais:20200512101636p:plain

今回は技術的な内容には踏み込まずにデータ分析から機械学習のプロダクト導入に至るまでの道筋についてご紹介しました。 コネヒトでは、これからも今回の事例のようにテクノロジーの力でプロダクトを伸ばしていってくれる仲間を絶賛募集しています。ご興味ある方は是非一度お話だけでもさせてもらえるとうれしいです。

www.wantedly.com

ECS×Fargate ターゲット追跡ServiceAutoScallingを使ったスパイク対策と費用削減

$
0
0

こんにちは。インフラエンジニアの永井(shnagai)です。

今回は、ターゲット追跡ServiceAutoScallingを使い、ECS×fargateで運用しているサービスのスパイク対策と費用削減に取り組んだのでその内容をまとめています。

内容はざっくり下記4項目について書いています。

  • 抱えていた課題
  • キャパシティプランニングに対する考え方
  • ECS ターゲット追跡ServiceAutoScallingとは何か?
  • どんな結果になったか?

抱えていた課題

コネヒトでのWebのアーキテクチャはほとんどがECS×Fargateの基盤で動かしています。 ECSのバックエンドをEC2からFargateに移行したタイミングで、大きく下記2点のメリットは享受していました。

  • EC2を意識しないことでの運用コスト削減
  • オートスケールの容易さ

ですが、サービス運用にあたりまだ下記のような課題がありました。

  • ①FargateのコストがEC2リザーブドインスタンスに比べて費用が高い
  • ②オートスケールは容易になったが、平均50sくらい起動にバッファが必要なので、瞬間的なスパイクに対する瞬発力が弱い

今回この2つの課題に対して、ターゲット追跡ServiceAutoScallingを本格導入することで解決することに成功したのでどのようなアプローチをしていったかについて紹介します。

キャパシティプランニングに対する考え方の変化

アプリケーションの実行環境としてクラウド利用やコンテナ化が進んできたことで、スケールアウトやスケールアップが容易になったことを背景として、昔のようなピークトラフィックに耐えうるキャパシティを事前に用意しておくようなアーキテクチャは、私達のような小・中規模のウェブサービスでは選択しなくてもよい時代になりました。

もちろんサービスの可用性を高く保つのはインフラエンジニアとしての本分なのでそこは保ちつつ、クラウドは従量課金なので、ユーザが多いときには多くのリソースを構え少なくなったらリソースを最小化するというより動的なキャパシティコントロールをすることがそのまま費用削減につながります。同じ品質のサービスを提供するのであれば費用は少ない方がいいに決まっており、費用削減もサービス運用に大きな意味を持つと思っています。

コネヒトの最近の話を少し振り返ると、下記のような流れをたどっています。

アーキテクチャポイント費用
ECS×EC2時代EC2バックエンド時はオートスケールが複雑だったので採用せずにピークトラフィックに*数倍に耐えうるEC2を用意ReservedInstanceを利用し費用は圧縮
ECS×Fargate時代EC2(RI)と比べると費用が高いので、ピークトラフィックに耐えうるタスク数までFargateの必要数をチューニングして減らす+想定外のスパイクはオートスケール利用(ちょっと遅いという課題感あり)SavingsPlansを利用し30%弱の費用を圧縮するが、タスク数減らしてEC2時代と同じくらいの費用感
ECS×Fargate(ターゲット追跡ServiceAutoScalling)時代負荷に応じてタスク数を動的にコントロール。最小数を小さくして夜間とピーク時で3倍程のタスク数の差が出る。平均的なタスク数の削減に成功従量課金を生かして2のパターンと比べて25%程の費用削減に成功

ECS ターゲット追跡ServiceAutoScallingとは何か?

ここからは、今回活用したターゲット追跡ServiceAutoScallingについて紹介します。

簡単に説明すると、CPU使用率/メモリ使用率/リクエスト数の3つから追跡するメトリクスと値を選択することで、ECS側でタスク必要数 (起動するコンテナ数)を動的にコントロールしてくれる機能です。

実はこれまでもスパイク時のオートスケール用には使っていたのですが、今回スケールインもこの機能に任せる設定を入れて、負荷が低い状態の時に費用削減するようなアプローチを取りました。

具体的にどのような動きをするか

現在は、ECSサービスの平均CPU使用率を使っているのですがこの挙動が少し理解出来なかったので検証時に調べてみました。

端的に言うと、指定した値に収束するようにタスク数をコントロールするような挙動になります。 例えば、CPU 40%という値を設定したとすると具体的に下記のような挙動になります。(これが中々わかりにくかった)

  • 平常時に10タスク起動で、CPU40%
  • リクエスト数が増え負荷が上がり、CPU55%に
  • タスク数が10→13にスケールアウトしCPU40%に収束
  • 深夜になり、リクエスト数が減りCPU20%に
  • タスク数が13→5にスケールインしCPU40%に収束

裏側では、CloudWatchAlarmがセットされて下記のような設定がされていました。(このアラームの設定は変更負荷)

  • 【スケールアウト判定】 3分間連続しきい値違反
  • 【スケールイン判定】15分間連続しきい値-3%に収束

設定のポイント

ここから設定のポイントを書いていきます。

スケールの数を設定

サービス内のタスクのスケールイン/アウトの幅をまず設定します。

項目内容
Minimum number of tasks (タスクの最小数) スケールインの最小値なのでこの数以下にはタスク数は減らない
タスクの必要数サービスで設定しているタスクの必要数が入ります
Maximum number of tasks (タスクの最大数) スケールアウトの最大値なのでこの数以上にはタスク数は増えない
ターゲット追跡するメトリクスを指定

スケーリングポリシーには、「ターゲット追跡」と「ステップスケーリング(任意のCWAを指定)」があり、今回は「ターゲット追跡」を使う場合の設定について書きます。

ターゲット追跡の対象と出来るのは下記3つです。

ECSサービスの平均CPU使用率ECSサービスの平均メモリ使用率ECSサービスに紐づくALBのリクエスト数

また、スケーリングの設定として

項目内容ポイント
ターゲット値 追跡したメトリクスでのしきい値サービスの性質により検証しながら適切な閾値を入れる
スケールアウトクールダウン期間サービスで設定しているタスクの必要数が入りますスケールアウトした後に待機時間(この間は連続でスケールアウトは発動しない)|コネヒトでは60sにしています
スケールインクールダウン期間スケールインした後に待機時間(この間は連続でスケールインは発動しない)コネヒトでは900sにしています(ゆっくり縮退してほしいので)
スケールインの無効化On/Offでスケールインを行うかどうか今回の目的だとOff

どんな結果になったか?

最後にこのターゲット追跡ServiceAutoScallingを導入してどのような結果が出たかを紹介します。

①FargateのコストがEC2リザーブドインスタンスに比べて費用が高いという課題に対しては、1日平均のタスク起動数が25%程削減出来ました!!

従量課金なので、Fargateの利用料金が25%削減しています。

f:id:nagais:20200610114745p:plain

②オートスケールは容易になったが、平均50sくらい起動にバッファが必要なので、瞬間的なスパイクに対する瞬発力が弱いという課題に対しても解決策を見つけることが出来ました。

どうゆうことかというと、ターゲット追跡のしきい値を緩めたことでリソースに余裕のある状態でスケールアウトが走るので、例えばpush通知で急激にリクエストスパイクする際もレイテンシ悪化なくスケールアウト出来るようになりました。

これまでは、CPU60%超えで発動というような強めの設定をしていたので、スケールアウトする際にはすでにリソースがかつかつで既存のタスクが死んでいくというような状況だったので、スケールアウトで収束するまでに時間が少しかかっていたということに改めて気づきました。

実は、スケールアウト時に間に合わずレスポンスタイム悪化すると使えないなと思っていたのですが、レイテンシに全く影響のでない抑えめのしきい値の最適値を見つけ、現在はこのような波形で動的にタスク数が制御されています。(設定後一度もアラートは鳴ってません)

ちなみに、導入時は、ピーク時間帯が毎日不安で、最適なタスク数と追跡するしきい値1週間は張り付いてリソース状況を観察していました。

f:id:nagais:20200610114809p:plain

最後に宣伝です! コネヒトでは一緒に成長中のサービスを支えるために働く仲間を探しています。 少しでも興味もたれた方は、是非気軽にオンラインでカジュアルにお話出来るとうれしいです。

人の生活になくてはならないサービスをつくる、インフラエンジニア募集! - コネヒト株式会社のインフラエンジニアの求人 - Wantedlywww.wantedly.com

CakePHP4.1.0がリリースされたので変更点を追ってみる

$
0
0

こんにちは。CTOの@itoshoです。 最近stand.fmをはじめたので、よかったら聴いてみてください。

今日は週末(7/4)にリリースされたCakePHP4.1.0の変更点をBakeryやGitHubを読んでみて、個人的に「おっ」と思ったことを中心にまとめてみたいと思います。*1

なお、全ての変更点は取り上げられないので網羅的に知りたい方は公式のリリースノートをご参照ください。また、内容についてはきちんとチェックしているつもりですが、もし間違いや分かりづらい点がありましたらご指摘いただければ幸いです。

新たに追加された機能

最初に新機能についていくつか紹介したいと思います。

共通テーブル式が利用出来るようになった

個人的には今回これが一番大きな機能かなと思うのですが、ORMで共通テーブル式(CTE)が使えるようになりました。MySQL8系や他のRDBMSを使っている方には朗報ですね。*2

WITH句は以下のようにwith()メソッドとCommonTableExpressionクラスを利用することで実現出来ます。

<?php$con= ConnectionManager::get('default');
$query=$con->newQuery()->with(new CommonTableExpression('cte', function(ConnectionInterface $con){return$con->newQuery()->select(['col'=>1]);
    }))->select('col')->from('cte');

生成されるSQLはWITH cte AS SELECT 1 AS col SELECT col FROM cte;といった感じになります。

Window関数が利用出来るようになった

こちらもMySQL8系ユーザー向けの機能ですが、Window関数も利用出来るようになりました。例えば、ROW_NUMBERを利用する場合は以下のようになります。

<?php$function=new FunctionsBuilder()->rowNumber();

OrderAsc()メソッドとOrderDesc()メソッドにクロージャを渡せるようになった

クロージャを渡すことで、以下のようなCASE WHENを用いた複雑なソート条件を設定出来るようになります。

<?php$query= ConnectionManager::get('default')->newQuery();
$query->select(['id'])->from('children')->orderAsc(function(QueryExpression $exp, Query $query){return$exp->addCase([$query->newExpr()->add(['user_id'=>1])],
            [1, $query->identifier('id')],
            ['integer', null]);
    });

ちなみに、データベース周りは他にもAggregateExpressionというクラスが追加されており、表現力がかなり強力になっているように感じます。

ログメッセージに{foo}形式のプレースホルダーが利用出来るようになった

上記の形式を利用すると$contextパラメーターの値から置き換えてくれるようになったので、以下のような書き方が可能になりました。

<?php$context=['id'=>1,
    'foo'=>'bar',
];
$this->log('error: {id}', LogLevel::NOTICE, $context);

配列なんかも渡せます。地味に便利ですね。

非推奨となった機能

次に4.1系からdeprecatedとなった機能を紹介します。ここに挙げられている機能はCake5.0で廃止される予定ですので、マイグレーションガイドを読みながら早めに対応したいなと思っています。*3

or_()メソッドとand_()メソッドの廃止

今後はor()メソッドとand()メソッドを利用する必要があります。Cake3系だとor_()メソッドは利用頻度がそれなりにあるかなと思うので、変更が必要なシステムも多いかもしれません。

ServerRequest::input()メソッドの廃止

生データを文字列で取得する場合は今後(string)$request->getBody()を利用する必要があります。

whitelistというワーディングを使った機能の廃止

これは昨今のBLM運動の流れを汲んだものになり、具体的にはPaginatorComponentwhitelistという名称のオプションがallowedParametersという名称に置き換えられるなどの変更が行われています。なお、BLM運動については賛成の立場でありつつ、日本にずっと暮らしている身からすると頭では理解しながらも肌感覚としては分からないことも正直多いのですが、分からないで終わらせず歴史的背景をきちんと勉強したり、傍観者にならずきちんと当事者意識を持って今後も動向を注視したりしていく必要があると考えています。

その他気になったこと

最後にアプリケーションテンプレートや今後のロードマップを読んで、気になったことを紹介します。

デバッガーの設定でエディターを指定出来るようになった

app.php内で以下のような設定が出来るようになりました。

<?php'Debugger'=>['editor'=>'phpstorm',
 ]

ここでエディターを設定しておくと(デバッグモード時に)エラー画面で表示されるエラー箇所のリンクから各エディターをURLスキームで起動出来るようになったようです。ここではPHPStromを指定していますが、VSCodeやEmacsなども指定可能です。

ちなみにデフォルトの設定をPHPStormにしたときのPRのコメントが面白かったです。

CsrfProtectionMiddlewareの設定が移動した

新規のユーザーがこのミドルウェアがどこで設定しているか分かりづらいとの理由routes.phpから Application.php移動しています。ですので、我々も今後はApplication.phpに設定するようにしましょう。

DIコンテナの実装は持ち越しになった

以前、DIコンテナが4.1に導入されるかもしれないという記事を書いたのですが、ロードマップを読むと4.2へ持ち越しになったようです。個人的には期待していたのですが、やはりそれだけ影響範囲が多いということでしょうか。引き続き、状況をウォッチしていきたいなと思います。

まとめ

変更点をいくつか紹介しましたが、リリースノートをざっとみてもマイナーバージョンということもあり、細かなアップデートが中心になったかなと思います。もちろん、それが悪いことではなく、フレームワークは漸進的に確実に進んでいくことがDX(Developer Experience)の向上に繋がると思うので、CakePHPの本体のアップデートに負けないように自分たちのシステムも継続的にアップデートしていければと考えています。

というわけで最後に宣伝です!コネヒトでは新しいシステムをCakePHP4で実装するなど、積極的にCakePHPを利用しています。CakePHPを利用したアプリケーション開発や日本の家族を取り巻く社会課題の解決に興味があるエンジニアの方はオンラインで構いませんので、是非お気軽にカジュアルにお話出来ると嬉しいです!

www.wantedly.com

*1:なお現時点(7/6)で"composer create project"を実行すると既にに4.1.1がインストールされるようになっています。

*2:コネヒトではMySQL5系互換のAuroraを利用しているので、恩恵を受けられるのはもう少し先になりそうです。

*3:公式ドキュメントではRectorを使ったマイグレーション方法が紹介されています。

AWS CodeBuild を使った検証環境へのデプロイ改善

$
0
0

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

コロナの影響でコネヒトも3月からフルリモート体制が始まり、早4ヶ月が過ぎました。 流行に乗り遅れがちな私は、今になって自宅のリモートワーク環境を整えようと動き始めています。 まずはローテーブルを卒業しよう・・・。

さて、今回はそんなフルリモート下で発生した課題の1つを CodeBuildを使って解決したので紹介させていただきます。

コネヒトにおける検証環境のデプロイ方法について

コネヒトでは、本番リリース前のチェックや開発時にAWS環境で動作確認に使う検証環境を用意しています。

この検証環境にデプロイするには以下の2つの方法がありました。

  1. masterブランチにPRをマージする(本番リリース前に動作確認するため)
  2. 開発PCから直接デプロイする(開発時に検証環境で動作確認したい時など)

2.の方法をコネヒトでは「ローカルデプロイ」と呼んでいます。 このローカルデプロイを今回 CodeBuildを使って改善しました。

ローカルデプロイの問題点

主に以下の2つが問題点としてあげていました。

  1. ローカル環境に依存しているので Dockerのバージョンなど、差異が出てしまう場合がある
  2. リモートワークの際に、各家庭のネット回線によって Docker プッシュに時間がかかる場合がある

特にコロナでフルリモート環境になって 2.が深刻な問題となりました。 自宅の通信速度が遅い場合、開発業務に影響が出るレベルの方もいました。

f:id:hyirm:20200729104832p:plain
嘆きのSlack投稿

Docker プッシュ中に Zoom のミーティングの時間が来たので泣く泣く中断して、ミーティング後に再実行・・・、なんて最悪ですね。

これらの問題を改善するために、CodeBuildを使って検証環境へのデプロイを安定して行えるようにしました。

CodeBuild の選定理由

以下のような理由で CodeBuildを選びました。

  1. ママリはAWSで動いているので、CodeBuildとの親和性も高い
  2. CLI/SDK などを使ってAPIですべての操作が可能(CodeBuildに限らずAWS全体に言える話ですが)
  3. コネヒトの一部のプロジェクトで使用実績があった
  4. 比較的安い従量課金で、並列度に制限がない

ちなみに無料枠で月100分(build.general1.smallのみ)はずっと無料で使えるので、試しに使ってみても良いかもしれません。

名前をつける

もともと「ローカルデプロイ」という名前でしたが、CodeBuildを使った場合は違和感のある名前なので、まずは名前をつけることにしました。

Slack で名前を募集したところ、たくさんの案が出てきました。 ありがたや~。

f:id:hyirm:20200729104914p:plain
たくさんの案を出してもらえました

色々案が出ましたが、最終的に「ブランチデプロイ」に決まりました。 主に開発時に使い、開発時はブランチを作りそこからデプロイするので、イメージに近くて分かりやすい名前ということで決めました。

ブランチデプロイの仕組み

コネヒトのサービスは主に ECSで動いており、 Docker イメージは ECRに保存しています。 このような流れで動いています。

f:id:hyirm:20200729171800p:plain
ブランチデプロイの仕組み

CodeBuild 用の設定ファイルを作成

まず CodeBuildが実行時に参照する YAML の設定ファイルを作成して、リポジトリ内にコミットします。 こんな感じの YAML ファイルを用意しました。

version:0.2env:variables:DOCKER_BUILDKIT:"1" # Ref: https://tech.connehito.com/entry/2019/06/17/180404parameter-store:GITHUB_ACCESS_TOKEN:"/xxxxxxxxxxxx/GITHUB_ACCESS_TOKEN"phases:build:commands:- /bin/bash .codebuild/dev_deploy.sh

実際のデプロイ処理は dev_deploy.shというスクリプトにまとめて、設定ファイルはシンプルになるようにしています。 dev_deploy.shの中身は省略しますが、主に Docker ビルド&プッシュと ecs-deployを使ってECSにデプロイする処理をしています。

ビルド時には GitHub のトークンが必要なのですが、今回は パラメータストアに設定して、parameter-store:でキー名を設定するだけで自動的に環境変数に設定してくれます。 この辺りは同じAWSサービスならではの便利さですね。

CodeBuild のプロジェクト設定

公式ドキュメントなどを参考にAWSコンソールで設定しました。

特筆すべき設定はありません。 強いて言えば、自動実行はしないのでウェブフックイベントの設定はしないことぐらいでしょうか。 (CIだとプッシュなどのタイミングで自動実行する場合が多いと思いますが、今回はユーザーからのリクエストがあって動く仕組みなので)

f:id:hyirm:20200729104953p:plain
ウェブフックは無効にしました

CodeBuild を起動するシェルスクリプトの作成

AWS CLI を使って簡単にブランチデプロイが実行できるように、以下のようなシェルスクリプトを作成しました。

#!/usr/bin/env bashset-ereadonlyGIT_ROOT="$(git rev-parse --show-toplevel)"readonlyREPOSITORY_NAME="(リポジトリ名)"readonlyPROJECT_NAME="(CodeBuildのプロジェクト名)"readonlyDEPLOY_TARGET="${1:-"HEAD"}"readonlyDEPLOY_COMMIT_HASH="$(git rev-parse "${DEPLOY_TARGET}")"readonlyDEPLOY_COMMIT_LOG="$(git log "${DEPLOY_COMMIT_HASH}" --max-count=1)"# GitHub API を使って GitHub にコミットがプッシュ済みかをチェックするif [["${GITHUB_ACCESS_TOKEN}"!=""]];thenHTTP_STATUS_CODE="$(curl -H "Authorization: token ${GITHUB_ACCESS_TOKEN}" -o /dev/null -w '%{http_code}\n' -s \"https://api.github.com/repos/Connehito/${REPOSITORY_NAME}/git/commits/${DEPLOY_COMMIT_HASH}")"if [["${HTTP_STATUS_CODE}"=="200"]];thenprintf"\e[34mINFO: GitHubにコミットが存在することが確認できました。\e[0m\n"elseprintf"\e[31mERROR: GitHubにコミットが存在するかをチェックしたところ、ステータスコード(${HTTP_STATUS_CODE})が返却されたためブランチデプロイを中止します。\e[0m\n">&2exit2fielseprintf"\e[31mWARNING: 環境変数 'GITHUB_ACCESS_TOKEN'が設定されていないため、GitHubにコミットが存在するかのチェックをスキップします。\e[0m\n"fiprintf"\nこのコミットでブランチデプロイを実行しますか?\n-----\n%s\n\n-----\n""${DEPLOY_COMMIT_LOG}"printf"実行する場合は \e[34m'yes'\e[0m と入力してください: "read ANSWER

if [["${ANSWER}"!="yes"]];thenecho"ブランチデプロイを中止します。"exit1fiecho"ブランチデプロイを開始します。"
aws codebuild start-build \--project-name"${PROJECT_NAME}"\--source-version"${DEPLOY_COMMIT_HASH}"\--output json
echo"ブランチデプロイを開始しました。実行結果はログを確認してください。"

ざっくりいうと、こんな流れで処理を進めています。

  1. 引数からコミットハッシュを取得
    • git rev-parseを使ってコミットハッシュを取得しています。
    • ブランチやタグ、省略されたコミットハッシュでも参照できます。
    • "${1:-"HEAD"}"で第1引数に指定がない場合は HEADからコミットハッシュを取得します。
  2. GitHub APIを使ってコミットハッシュが GitHubに存在しているか確認
    • これで CodeBuild実行後に「コミットをプッシュし忘れた〜」と気づくトラブルを防いでいます。
    • 開発時に必要になるので、コネヒトでサーバー開発に携わるエンジニアは GITHUB_ACCESS_TOKENという環境変数をセットしています。
    • 一応、環境変数が無くてもワーニングだけ出して実行はできるようにしています。
  3. ブランチデプロイを実行してよいかユーザーに確認
    • terraformに倣って、ユーザーが yesを入力しなければ実行されないようにしています。
    • コミットログも表示して、想定していたものに間違いないかを確認できるようにしています。
  4. AWS CLIを使って CodeBuildでのビルド&デプロイを実行。
    • ちなみに AWS CLIv1, v2がありますが、どちらのバージョンも同じコマンド体系なのでどちらでも実行できます。(下記参照)

AWS CLI v1の場合

$ aws --version
aws-cli/1.18.90 Python/3.8.4 Darwin/19.5.0 botocore/1.17.13

$ aws codebuild start-build help

NAME
       start-build -

DESCRIPTION
       Starts running a build.

       See also: AWS API Documentation

       See 'aws help'for descriptions of global parameters.

SYNOPSIS
            start-build
          --project-name<value>[--secondary-sources-override <value>][--secondary-sources-version-override <value>][--source-version <value>]# (以下略)

AWS CLI v2の場合

$ aws --version
aws-cli/2.0.24 Python/3.7.3 Linux/4.19.76-linuxkit botocore/2.0.0dev28

$ aws codebuild start-build help

NAME
       start-build -

DESCRIPTION
       Starts running a build.

       See also: AWS API Documentation

       See 'aws help'for descriptions of global parameters.

SYNOPSIS
            start-build
          --project-name<value>[--secondary-sources-override <value>][--secondary-sources-version-override <value>][--source-version <value>]# (以下略)

少なくとも今回使っている範囲では同じコマンドで実行できることがわかります。

動かしてみる

先程作ったシェルスクリプトを実行するとこんな感じになります。 AWSへの認証情報がセットされた状態で以下のコマンドを実行します。

$ ./branch-deploy.sh "(ブランチ名、コミットハッシュなど)"

こんな感じで表示されます。

f:id:hyirm:20200729105046p:plain
Terminal での実行例

後は CodeBuildの実行を待つだけです。

ちなみにコネヒトでは CodeBuildを実行すると、自動で Slack 通知が来る共通の仕組みがあるので、通知関連は今回実装しませんでした。

f:id:hyirm:20200729105151p:plain
Slack で通知されます
(ちなみに社内では検証環境を「dev」とか「dev 環境」と呼んでいます)

CloudWatch EventsCodeBuildのイベントを Lambdaに送って Slackに通知しています。

このような感じで実行すれば非同期で動くので、ネット回線やPCの負荷を気にせず検証環境へのデプロイが安定して行えるようになりました。

おわりに

今回は好きなタイミングで任意のコミットを CodeBuildを使ってデプロイする、ということをやりました。 IAM の権限周りで少しハマりましたが、設定は難しくなくサクッとできるので、こういった活用もありだな〜、と思いました。

最後に、コネヒトでは開発環境の改善にもチャレンジしたいエンジニアを募集中です!

www.wantedly.com

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

$
0
0

こんにちは、コネヒトでアプリケーションエンジニアをしている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 がブログにしてくれると思うので、気になった方はもう少しお待ちください。


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

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

$
0
0

こんにちは。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つはそれぞれ時間が計測され、画面トレースは画面が存在する間のパフォーマンスがトレースされます。それぞれを簡単に解説すると以下の通りです。

アプリ起動トレース

アプリを起動するまでにかかった時間が計測されます。 FirebasePerfProviderContentProviderが 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

バウンスメールと AWS SES

$
0
0

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

先日エンジニアチーム内で 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 を使う際には、最低でもバウンスや苦情を検知しておく必要があります。

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

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 をご覧ください。

おわりに

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

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

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

PR

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

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

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


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

$
0
0

こんにちは!コネヒトでアプリケーションエンジニアをしている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

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

$
0
0

こんばんは!コネヒトでアプリケーションエンジニアをしている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 に関わった皆様ありがとうございました!

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

$
0
0

こんにちは、コネヒトで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:falseprerelease:false

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

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

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

最後に

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

hrmos.co

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

$
0
0

こんにちは! @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などいろんなテーマの発表がありとても興味深い勉強会になったと思っています。

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

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

potatotips #71 で Mixpanel について LT してきたのでその補足や失敗談の共有

$
0
0

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

先日開催された potatotips #71 iOS/Android 開発 Tips 共有会で、コネヒトでも活用しているプロダクト分析ツールの Mixpanel について話してきました。コネヒトでは、エンジニアもプロダクトをより良くする開発チームの一員として仮説検証や施策を考えるために Mixpanel や Redash などの分析ツールを触ります。Mixpanel は GUI で比較的かんたんに分析できたり、レポートのビジュアライズが強力で、コネヒトでも分析ツールのひとつとして役立っています。

potatotips.connpass.com

今回は、発表内容の補足や失敗談の共有をします。当日の発表資料はこちらです。

GUI でかんたんに分析できる

発表で出さなかった例として、「Compare to previous year」を選択すると、前年の値と比べられたり(点線が前年、実線が今年)

f:id:aboy_perry:20201028160221p:plain
前年との比較

Annotation という、レポートの日付に注釈をつけられる機能があったりします。下の画像の例だと、機械学習のモデルを更新したんだな、ということがレポート上でわかりやすくなります。

f:id:aboy_perry:20201028184839p:plain
Annotation をつけてやった施策をわかりやすくする

ユーザーの Activity Feed をエラー調査に活用する

ユーザーの行動を時系列で確認できる Activity Feed も便利です。実際にアプリケーションのエラーを調査するときに、Activity Feed で該当のエラーが発生した時刻の前後の行動をみたりします。そして「通常のユースケースで起きてそう」「いや何か変だぞ」みたいなコミュニケーションをしたりしています。

f:id:aboy_perry:20201028181426p:plain
ユーザーの行動を時系列で確認できる Activity Feed

Cohorts も便利

発表では触れられませんでしたが、Cohorts というユーザーをグループ化できる機能があります。つくった Cohorts は分析の条件に入れたり、ユーザーリストを CSV としてエクスポートできたりして便利です。例えば「過去7日間に10回以上起動した Android ユーザー」などです。

f:id:aboy_perry:20201028170603p:plain
Cohorts の作成画面

エンタープライズプランであれば Cohorts はファネル分析やリテンション分析のレポートからも作成できます。ファネルであれば「施策で追加したバナーから購読画面を開いたが購読せず離脱したユーザー」や「ディープリンクから投稿画面を開いてそのまま投稿してくれたユーザー」など。リテンションであれば「投稿した次の日に起動しなかったユーザー」のようなユーザーのリストを、レポートからポチポチするだけで作成できます。

f:id:aboy_perry:20201028164022p:plain
ファネル分析のレポート上で選択するだけで Cohorts がつくれる

コネヒトでは使っていませんが、Cohorts に対してメッセージを送ることもできます。

help.mixpanel.com

MTU が想定以上に増えた

最後に失敗談です。Mixpanel は MTU (Monthly Tracked User) という方法で費用が計算されます。 MTU は対象の月にイベントを実行したユーザー数のことで、Mixpanel では distinct_id という識別子でユーザーが一意に決定されます。

コネヒトではアプリ (Native と WebView) からのみ Mixpanel にイベントを送信しており、distinct_id にはママリのユーザー ID が設定されるようになっていました。なので MTU はアプリ版ママリの月間ユニークユーザー数と近しい値になっていました。

しかし新たに Mixpanel へのトラッキングを追加した際、ママリにログインしていなくても見ることができるブラウザも対象にしました。すると distinct_id には Mixpanel で生成されたユニークな ID が設定されたことによってそれぞれが別のユーザーとしてカウントされてしまい MTU が想定以上に増えてしまいました。

f:id:aboy_perry:20201027231719p:plain
MTU が爆増した2週間

このときは、(1)コスト増のインパクトが大きいこと(2)トラッキングの目的であったデータ分析が終わっていたため消しても問題ない、という理由から該当のコミットをリバートする対応をしました。

公式ドキュメントには、集計対象外のイベントや MTU の計算方法の例外、MTU に大きな影響を与えているイベントの調べ方なども書かれてあります。

help.mixpanel.com

おわりに

LT で話せなかったことや、失敗談を共有しました。プロダクト分析ツール選びの際に参考のひとつになれば幸いです!


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

hrmos.co

TravisCIの料金体系の変更によるmacOSでのビルドへの影響(有料ユーザー向け)

$
0
0

2020/11/02からTravis CIの料金体系が変わりました。 blog.travis-ci.com

影響があるのは以下の4つ場合です。

  • Building on the macOS environment
  • Building with more than 10 concurrent builds
  • Building on a Trial Plan
  • Building on a public repositories only

コネヒトでは月額$249(5 concurrent job)のplanでweb, iOS, Androidで使っています。

このエントリーでは、コネヒトで影響のあった Building on the macOS environmentについてのみふれていきます。

(注:TravisCIを無料でpublic repositoryでのみmacOSでビルドするのに使われている方には参考にならないです🙇🏻‍♂️)

macOS環境でのビルドへの影響

Travis CIではこれまではOSに関係なく同じプラン内で使えていましたが、今後はmacOSの場合はビルド時間に応じて追加料金が必要となります。 料金は以下の通りです。

  • $15で25000 creditを購入
  • macOSだと1分で50 credit

CircleCIとそろえているようでした。

いつから?

現在のプランの有効期限が切れると次回から新しいプランに移行するようでした。

Beginning on Monday the 2nd of November 2020, customers impacted by pricing changes listed above will be gradually upgraded to the new pricing scheme upon their plan expiration date.

他のCIとのコストの比較

追加料金必要になるなら他のCIに乗り換えることも検討するかと思いますのでコストを比較してみました。

月にどれくらい使うか、チームの規模、iOS以外の開発での利用などによって最適なものは異なってくるのでどれが一番良いとは一概に言えません。

ビルド時間 Travis CI Circle CI bitrise GitHub Actions
2000min $60 + $69/$129/$249 ※1 $60+$15 ※2 $100/$300 ※3 $136
2500min $75 + $69/$129/$249 ※1 $75+$15 ※2 $100/$300 ※3 $176
3000min $90 + $69/$129/$249 ※1 $90+$15 ※2 $100/$300 ※3 $216
3500min $105 + $69/$129/$249 ※1 $105+$15 ※2 $100/$300 ※3 $256

※1 Travis CIはconcurrent planによってベースの料金が異なります

※2 Circle CIはユーザー数によって料金が異なります

※3 bitriseはプランによって料金が異なります

2020/11/11時点の情報です。

コネヒトでの今後の方針

コネヒトのiOS開発でのCIの利用状況は月に3000分くらいで、コスト的には他のCIと遜色なさそうでした。

ただTravis CIを使っている上で ログが最大4MBまでしか保存されないというところが結構不便に感じていたので、同じくらいのコストで定額で使えるbitriseに乗り換えようかと検討しています。

Lambdaのコンテナサポートに関する考察

$
0
0

こんにちは。インフラエンジニアの永井(shnagai)です。

AWS re:Invent今年も大豊作ですごいですね。まだ全部は追えてないんですが、良さそうなものがあればサービスに取り入れていこうと思いわくわくしています。

この記事はコネヒト Advent Calendar 2020 - Qiita 3日目の記事です。

今回は、試してみてる方は結構いそうなので、ざっとLambdaのコンテナサポートを触ってみた感じの所感を中心に書いていきます。

うれしいポイント

  • 今想像してる一番うれしいポイントは、lambdaがサポートしてる数多のAWSインテグレーションをトリガに好きな処理が動かせるところ(lambdaRuntimeAPIの存在を知りそう甘くないことを理解した)
  • ローカルの開発がやりやすくなるなー
    • SAMとか使って出来るけど。個人的にはlambdaの管理は煩雑
    • dockerで検証出来た方が楽
  • lambdaの設定とアプリケーションコード(コンテナ)を分離して管理出来るようになるので、コード化しやすいなとか

vs 既存のLambda

  • Lambdaの一番かゆいところは、チーム開発しようとするとデプロイフローがやや面倒という点があると思っている(AWS SAMとかをうまく使っていけば出来なくはないが、チーム全員で回すにはやや複雑と感じている)

pros

  • 今回、アプリケーションコードをDockerコンテナ内に閉じ込めることで、既存の開発フローに乗せてLambdaを開発出来るようになる未来が見えた
  • ハンドラがDockerのCMD句に置き換わるので、共通のイメージで開発して、Function毎にCMD句だけ上書きすることで管理コストが大幅に下げれそうな気がしている

cons

  • しいて言うなら、Lambdaのメリットにマネコン上からコード閲覧出来たりサクッと変えてお試しみたいなのが出来るのがあると思うが、それは出来ない(手軽さは少し落ちるかなというところ)

vs Fargate

  • サーバレスでコンテナを動かせると考えると、Fargateの代替になりえるなと考えるのは当然の流れだと思う

pros:

  • AWSのインテグレーションを簡単に使えるというLambdaの強みを享受
    • 例えば、下のサンプルでやったSQSトリガのワーカーの仕組みなんかは、Fargateでやる場合は、ポーリングの処理を書かないといけないがそれが不要になるのはうれしい
  • Fargateでコンテナ動かすには、ECSなりEKSのオーケストレーターも必要なわけなので、スポットで単発の処理動かしたいみたいな時はLambdaはより簡単で良い

cons:

  • LambdaRuntimeAPIをコンテナ側で実装しなければいけないので、どんなイメージでも動くわけではない。
    • AWSである程度の言語については、すでに用意されてはいる
    • インターフェースを統一しなきゃいけないのはそれはそうだな
    • 既存のアプリケーションの一部をLambda+コンテナで動かすのはちょっとハードルあるなというのが正直な印象ではある

ちょっとイメージと違ったところ

これは触ってみて感じたことなので、実は違う可能性もある。

  • コンテナイメージは都度pullではなく、イメージ設定時に固定されそう
    • 同一タグでECR更新して、起動時に最新のイメージ取得みたいな構成は出来なさそう(何回か試したけど、lambdaを更新した時にイメージがずっと使われてそうだった)
    • 「新しいイメージをデプロイ」する時にpullしてるように見える
    • 起動速度維持するために、キャッシュしてるのかなと想像
    • コンテナビルドとLambdaの更新をセットでやらないとダメそう

で試してみる

今回は、SQSをイベントソースとして、SQSキューが入ったら中身を出力するという簡単な内容(疑似的なワーカー)

こちらのチュートリアルをベースに実行

docs.aws.amazon.com

コードはこちら

github.com

コンテナの準備

$ docker build -t [アカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/tst-shnagai:test .

## 手元で動作確認
$ docker run --rm [アカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/tst-shnagai:test

hogehoge

## ECRへのpush(ログイン込)
$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin [アカウントID].dkr.ecr.ap-northeast-1.amazonaws.com && docker push [アカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/tst-shnagai:test


Lambdaの設定

作成(コンテナイメージを選ぶ)

f:id:nagais:20201203100953p:plain

  • 手元にあった適当なpython動くコンテナで最初試したけど、それをただ実行させるとかは出来ないみたい(lambdaRuntimeAPIが必要で、呼び出しI/FもLambdaにそる)

f:id:nagais:20201203100356p:plain

  • IAM Roleとして AWSLambdaSQSQueueExecutionRole を追加するのを忘れずに。

SQSの準備

aws-cliでサッと作る

$ aws sqs create-queue --queue-name lambda-test
{
    "QueueUrl": "https://ap-northeast-1.queue.amazonaws.com/[アカウントID]/lambda-test"
}

先程作ったLambdaのイベントソースに上記のSQSを登録

テスト

$ aws sqs send-message --queue-url https://ap-northeast-1.queue.amazonaws.com/[アカウントID]/lambda-test --message-body "lambda-containerのテスト"

無事に動いた。

f:id:nagais:20201203100305p:plain

クラメソさんの記事が概要理解するのにめちゃ役立ちました!!

dev.classmethod.jp


iOS14からはaddTargetじゃなくてaddAction

$
0
0

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

これは iOS Advent Calendar 2020の 4日目の記事です。

TL;DR

UIKitのUIControl系のView(UIButtonなど)ではタップ時のアクションをコードで実装するときは、標準のAPIだとaddTargetを用いる必要がありました。addTargetだとclosureが使えずいちいち関数を定義しなければならなかったり、@objcをつける必要があったりと面倒でした。

// addTargetでやるパターン@IBOutlet weak varbutton:UIButton!overridefuncviewDidLoad() {
    super.viewDidLoad()

    button.addTarget(self, action: #selector(doSomething), for: .touchUpInside)
}

@objcfuncdoSomething() {
    print("do something")
}

iOS14でついにUIControlにaddActionという新しいAPIが追加されこの面倒くささが解決しました!(SwiftUIがでているこの状況では今更感がありますが・・)

このようにシュッとかけるようになっています。

button.addAction(.init { _ in print("do something") }, for: .touchUpInside)

(ちなみにコネヒトでは現在RxSwift(RxCocoa)を使っているので正直addActionの恩恵にあずかることはなさそうです

詳細

UIControlの新しいAPIのaddActionではactionをselectorではなくUIActionを受け取るようになっています。

// UIControlfuncaddAction(_ action:UIAction, for controlEvents:UIControl.Event)

UIActionはinitializerで多くの引数を指定できますが、必須なのはhandlerだけです。

// UIActionconvenienceinit(
    title:String="",
    image:UIImage? =nil,
    identifier:UIAction.Identifier? =nil,
    discoverabilityTitle:String? =nil,
    attributes:UIMenuElement.Attributes= [],
    state:UIMenuElement.State= .off,
    handler:@escaping UIActionHandler)

UIActionHandlerは単なるaliasです。

// UIActionHandlertypealiasUIActionHandler= (UIAction) ->Void

UIControlにはaddAction以外にもinitializerでactionを設定することもできるようになっています。https://developer.apple.com/documentation/uikit/uicontrol/3600494-init

// UIControlconvenienceinit(frame:CGRect, primaryAction:UIAction?)

使い方

わかりやすく書くとこうなります。

letaction= UIAction(handler: { _ in print("do something")})
button.addAction(action, for: .touchUpInside)

省略して書くとこのようになります。

button.addAction(.init { _ in print("do something") }, for: .touchUpInside)

addActionが使えるView

addActionはUIControlのAPIなのでUIControlとUIControlを継承しているクラスでは使えます。

ですので、UIButtonをはじめ、UIDatePicker, UIPageControl, UISegmentedControl, UISwitch, UIStepper, UISlider, そして新たに追加されたUIColorWellでaddActionが使えるはずです。

参考

WWDC2020: Build with iOS pickers, menus and actionshttps://developer.apple.com/videos/play/wwdc2020/10052/

CakePHPを使った実装で悩んだときに見ている情報源

$
0
0

こんにちは! @fortkleです。
この記事は コネヒト Advent Calendar 2020 8日目 の記事です。

f:id:fortkle:20201208110038p:plain
https://cakephp.org/jp

はじめに

コネヒトではサーバーサイドの言語として主にPHP、フレームワークでいうとCakePHPが採用されています。 公式のドキュメントにもある通り、CakePHPは「設定より規約」的な思想が随所に感じられるフレームワークです。

私たちは「設定より規約」(convention over configuration) という考え方に賛成です。 CakePHP の規約を習得するには少し時間がかかりますが、長い目で見ると時間を節約していることになります。 規約に従うと自由に使える機能が増えますし、設定ファイルを調べまわってメンテナンスするという悪夢からも 開放されます。 規約によって開発が統一感を持つため、開発者が加わってすぐに手伝うということがやりやすく なります。
CakePHP の規約 - 4.x

日々の実装の中で「どうやって書けばスマートかな?」や「Cakeだとどうやって書くのがCakeWayっぽいかな?」だったりを考えながら手を動かすことが多いため、今日はそういった際に役に立つ情報源をまとめてみます。

ちなみに私が探しに行く順番で、上から順に紹介していきます。

情報源

公式ドキュメント(Cookbook)

最初というか、実装に悩む以前にCakePHPを触るのであれば一通り公式ドキュメントを流し見しておくことをおすすめします。 「設定より規約」という思想のフレームワークにおいて、「知っていること」が何よりも重要になるからです。

CakePHPの公式ドキュメントは内容の充実度もさることながら、ボランティアベースで行われている日本語への翻訳作業も継続的に実施されていて大変素晴らしい・・というかお世話になっております。

ドキュメントはcakephp/docsで管理されています。翻訳に興味がある人は以下の記事を参考にしてみてください。

公式APIリファレンス

CakePHPは公式のドキュメントであるCookbookの他に、APIリファレンスをHTMLの形で公開しています。これも実装に悩む以前に読んでいてほしいものですね。英語で言えばAPIは英単語、英会話を楽しむ前に最低限の語彙力を鍛えましょう!

エンジニアなら全員読んだことがありそうな『リーダブルコード』にも以下のような記述があります。

 プログラマというのは、既存のライブラリで問題を解決できることを知らないことが多い。あるいは、ライブラリで可能なことを忘れていることが多い。ライブラリの機能を熟知して、実際に活用することが大切だ。
 ここでささやかな提案だ。たまには標準ライブラリのすべての関数・モジュール・型の名前を15分かけて読んでみよう。標準ライブラリというのは、C++標準テンプレートライブラリ(STL)やJavaのAPIやPythonの組み込みモジュールなどのことだ。
13.4 身近なライブラリに親しむ 『リーダブルコード』 P.172

厳密にはフレームワークのAPIリファレンスよりは低いレイヤーの話だと思いますが、伝えたいことは同じです。全てを覚えるのではなく、頭の中にインデックスを作り、実装で悩んだときに参照できるようにしておきましょう、ということです。

社内の類似技術スタックのリポジトリ

(全員が参考にできないのでサクッといきますが)社内に同じような技術スタックのリポジトリがある場合はそちらの実装も参考にしてみましょう。 大抵の悩みは先行しているリポジトリで解決されていたりします。

cakephp/cakephpリポジトリ

特におすすめなのは「テストコードを読むこと」です。
公式ドキュメントを読んでも使い方が分からなかったり、自分たちがやりたいカスタマイズ方法が分からなかったりする場合はあると思います。 そんなときにテストコードを読むと、テスト対象であるSUTが「一体何を目的に実装されたコードなのか?」を明示されながら使い方まで理解することができるからです。

個人的には、テストコードのスマートな書き方を求めてコアコードを見に行くことが多いです。

(コラム) 思想を理解する

フレームワークの思想を理解するにはより詳細にissueなど実装当時の情報を深ぼるのもおすすめです。特にlabel:RFCだけでも見ておくとよいかもしれません。
例えば、CakePHP3でガラッと変わったバリデーション周りの変更はこのissueに背景が書いてありますし、Traitの使い方の見直しについてもこのissueで議論を垣間見ることができます。

他にもwikiにあるロードマップを見たり、コアコントリビューターのmark storyのwikiには実装の草案・アイデアが公開されていたりします。

FriendsOfCake/awesome-cakephpリポジトリ

CakePHPの良さそうなライブラリやシステムのリンク集です。
用途別に分類されているので、関係がありそうなセクションのリポジトリを眺めにいくことが多いですね。 弊社に関係するライブラリもいくつか記載されています💪

ちなみに、 FriendsOfCakeはCakePHPの中の人達が中心となっている開発者グループで、品質の比較的高いCakePHPプラグインやリソースが提供されているため、比較的安心して実装を参考にできるかなと思います。

CakePHP Slackグループ

これまで紹介した情報源を調べても分からなかったり、数時間悩んだりするなら「人に聞く」というのもおすすめです。
周りに詳しい人がいない場合は、CakePHPコミュニティのSlackグループがあるのでそちらを活用してみるのはどうでしょうか。

#japaneseチャンネルがあるので日本語で相談もできますね!
CakePHP Slackから誰でもjoinできます。

コアデベロッパーの関連サイト(2020/12/08 追記)

CakePHPのコアデベロッパーが個人でCakePHPに関する情報発信をしているケースが多々あるのでそちらも参考になります。いくつか例をあげます。

おわりに

ここまで参考になりそうな情報源を紹介してきました。
実際にはもちろんQiitaやZenn、個人ブログなども参考に見ることが多いですが、参考に値する確かな情報源を持っておくことは重要です。 いろいろなコードを読み、理解して、よりスマートな実装ができるように鍛錬していきましょう!

宣伝! イベントやります!

コネヒトでは12/17にオンラインイベント開催予定です。 サービス開発が大好きなエンジニアはもちろん、事業会社で働いてみたいエンジニアやtoC向けサービスに興味がある方はぜひご参加ください〜!

connehito.connpass.com

Bitrise を触ってみた所感

$
0
0

こんにちは!アプリケーションエンジニアのあぼです。

コネヒトでは iOS アプリの CI ツールとして Travis CI を使っていましたが、先日プランが変わったこともあり、定額の Bitrise に移行しました。元々 fastlane で自動化していたので、Bitrise ではキャッシングなどのワークフローを組み立ててメインの処理は fastlane のコマンドに任せるという感じで動かしています。ですので移行自体もそこまで苦ではなかったなという印象です。

今回ははじめて Bitrise を触ったので自分の所感を簡単に書いていこうと思います。コネヒト Advent Calendar 2020の記事です。

導入初期の色々試したいときに GUI でガリガリいじれる

Bitrise はワークフローの設定などが書かれた bitrise.yml を Bitrise.io で管理する方法とリポジトリ管理する方法が選べますし、後からでも変更可能です。個人的に導入初期は yml のこと考えずに一気にガリガリ弄って試したいので、その点でラクでした。そしてある程度動くことが確認できて正式にチームに展開する際に GitHub のリポジトリ管理に切り替えています。GUI で弄った bitrise.yml をそのままダウンロードしてリポジトリに追加すれば良いので切り替えも簡単にできました。

f:id:aboy_perry:20201214162452p:plain
bitrise.ymlの管理方法

bitrise.yml を自分で書く場合にドキュメントが必要なのですが、探してみた感じだと Bitrise CLI のドキュメントを参照すればだいたい網羅されてそうな気がします。CLI 自体 OSS なのでソースコードを読んで調べることもできますね。Go で書かれているようです。

devcenter.bitrise.io

スタック(イメージ)の更新・削除が早い

スタックの更新と削除ポリシーを見る限り、他の CI と比べて古いスタックが deprecated になるタイミングや、削除されるタイミングが早そうです。例えば直近だと Xcode11.x は 11.7 以外のマイナーバージョンが順次 deprecated になり、削除されていきます。Circle CI の場合 Xcode11.x は Xcode13 のリリースが始まってから順次 deprecated になっていきます。一方で、Bitrise は新しいバージョンのスタックが使えるようになるのも早い印象*1なので、そこは嬉しいですね。

Travis CI に関しては更新と削除ポリシーについてそれらしい記述が見つけられなかったのですが、現状用意されている環境一覧を見る限り Xcode7.3 があり、かなり古いバージョンまで保持しているようです。(ちなみに前述のリンク先のページでは Xcode11.7 が無い…のですが、チェンジログには対応したと書いてありました)

会社や個人の状況によって、スタックの更新・削除ポリシーが要件と合わず使いたくても使えないということも起こりそうですが、コネヒトの場合は運用していけそうと判断しました。

ビルドの結果がステップごとに表示されて見やすい

ステップごとに折り畳まれてたり、かかった時間が一覧で見れるのでパッと見で見やすいと感じます。ビルド時間を削減しようと思った時にステップを細かくわけてボトルネックを探ったりするのに使えそうですね。

f:id:aboy_perry:20201214165209p:plain
ビルドの結果がステップごとに表示される

キャッシュが便利

Bitrise のキャッシュはブランチごとに特定のディレクトリを保存しておけるもので、Bitrise.io Cache:PullステップとBitrise.io Cache:Pushステップを組み込んで簡単に使うことができました。該当のブランチに有効なキャッシュが無ければ Bitrise 上でデフォルトブランチに指定しているブランチのキャッシュを取りに行く仕様です。Travis CI ではプルリクエストトリガーの際にターゲットブランチのキャッシュを取りにいく点が違っていますが、ほとんど同じ感覚で使うことができそうですね。

コネヒトでは現在 ruby (rbenv)、Gem、CocoaPods で管理しているライブラリ、Swift Package Manager 管理しているライブラリをキャッシュしています。

devcenter.bitrise.io

おわりに

というわけで今回は Bitrise を触ってみて感じたことを書きました!まだ細かい課題ややりたいこともあるので色々試していきたいなと思います!


そして宣伝です!12/12 (火) に potatotips (iOS/Android のオンラインLT会) を開催予定です。空いている枠は先着順で入れるのでご予定合う方はぜひご参加ください〜!

potatotips.connpass.com

*1:Bitrise の Twitterをウォッチしているとそんな印象があります、あくまで印象

サービスとシステムの健全性に保つためのAndroidチームの取り組み色々

$
0
0

こんにちは!2017年11月にAndroidエンジニアとしてjoinした関根です。気づけば入社4年目に突入しました。 さて今回は、弊社サービスのママリ改善を担当するAndroidチームでやっていることや始めていることを、年末の棚卸しを兼ねて紹介してみようと思います。

この記事はコネヒト Advent Calendar 2020 16日目の記事です。

やっていること

まずは今年までに既にやっていることの中から2つの動作チェックサポート対象のアプリバージョンやminSdkVersionの運用について紹介をさせていただきます。

2つ動作チェック

言わずもがなですが、サービスやシステムを健全に保つためには、意図した挙動が守られていることを事前に確認しておく必要があります。 Androidチームでは大きく分けて2つ動作チェックのタイミングがあります。

プルリクエストでの確認

ひとつめはプルリクエストを送るタイミングです。 下記のようなテンプレートを用意して、レビュアー/レビュイーの間の共通認識をとり、認識の齟齬を減らしミスを減らす工夫をしています。

## 目的
今回のPRの目的を書く。関連issueを貼ることが多い

## やったこと
このPRの作業内容を書く

## テスト項目
動作確認する内容や手順を書く

## チェック項目
- [ ] File Changedのセルフレビュー
- [ ] 6系で動作が問題ないこと
- [ ] 7系で動作が問題ないこと
- [ ] 8系で動作が問題ないこと
- [ ] 9系で動作が問題ないこと
- [ ] 10系で動作が問題ないこと
- [ ] デザインが崩れていないこと
- [ ] Activity破棄設定にして動作が問題ないこと
- [ ] 不要なリソース削除。`$ ./gradlew removeUnusedResources`

## その他
特に見てほしいポイントや、その他レビュワーに伝えたいことなどがあれば記載

テンプレートのチェック項目を全て確認をするのはレビュイーが多いですが、レビュアーも必要に応じて動作確認をしています。

リリース前チェック

もう一つはリリース前のリグレッションテストです。 こちらはプルリクエストの場合と違い、既にある機能を守れているかを確認するテストとなります。

確認する項目

サービスの重要な機能にしぼり、約70の項目を確認しています。 プロダクトマネジメントを担当するメンバーと相談し、ユーザーやクライアントに与える価値の大きいものを抜粋してテストをしています。

確認するタイミング

アプリリリース前に実施しています。かかる時間は一時間弱です。

機能が増えるタイミングで項目が追加されることも多いため不定期で棚卸しを実施しています。今は人力での動作確認をしていますが、今後は自動化検討して、効率化をしていきたいと考えています。

サポート対象のアプリバージョンやminSdkVersionの運用

Android開発ではminSdkVersionとの向き合い方に悩まされることが多いと思います。コネヒトでも長い間明確な運用ルールは決めていなかったのですが、2019年度のシステム安定化という部署の目標をきっかけにルールの整備が始まりました。

導入の背景

それ以前は、ルールがないことで全ての環境の全ての問題を同じように対応することが多く、サービス改善で起こるシステムの問題に対して「過度な萎縮」が起こり始めていました。 しかし、ユーザーに価値を提供し続けるにはシステム改修に萎縮せず気軽にリリースをし続けることが大事だと考えています。他方、ビジネス数値からみると、サポート範囲を狭めることはネガティブに捉えられがちでなので、「守るべき範囲」を明確に定義し過度な萎縮を防ぐことが「ユーザーに価値を提供し続ける」ことに繋がるという前提で議論を進め、導入に至っています。現在は以下のルールで運用されています。

基準

ルール名 対応内容 基準値
非推奨環境 設計や実装時の考慮から外してOK
不具合やエラー発生時に通常issueとして扱い対応可否を決める
DAU500人以下の環境
レガシーアプリver. バージョンアップを誘導する リリースから2年経過したアプリVer.
レガシーOS minSdkVersionの対象外にする
※2~3週間前にお知らせでアナウンスをする
OS毎MAUが2%以下のOS

見直し時期

  • クオータ毎
    • 3月、6月、9月、12月

見直し事項

  • サポートバージョンの基準値の見直し
  • サポートバージョンの環境の更新

実施ステップ

  1. 開発部でサポート対象外となる環境を計測する
    • 特定期間のMajorバージョンシェア
    • 特定期間でDAU1000以下のMajorバージョン
    • 2年前のバージョン番号
  2. 社内に共有してスケジュールを組む
    • ex). レガシーOS/レガシーアプリver対応:事前に対象ユーザーのお知らせに案内を掲出
  3. スケジュールに沿って遂行する

あくまでも一定の基準であるため、閾値を下回ったから直ぐに対応を実施するわけではなく、開発効率のバランスやサービスに与える影響をプロダクトマネジメントを担当するメンバーと相談しながら進められるように工夫をしています。

はじめている事

最後に、これからはじめていくことの中からrenovateの導入について紹介します。今月に入ってから着手し始めたもので、試行錯誤の真っ最中です。

依存ライブラリのアップデートを効率化

renovateを一言で紹介すると、依存ライブラリのアップデートを検知してPRを投げてくれるOSSです。 Androidの依存ライブラリは更新も早く、人力でアップデートしていくのは、至難の技だと感じており、 この課題を解決するために、renovateで自動化をし、運用を楽にすることを考えています。 余談ですが、以前は gradle-use-latest-versions-pluginをGithub Actionsで実行し、PRを出す運用の準備をしていましたが、別のプロジェクトで仕様していたrenovateでも同じことが実現できることがわかり乗り換えをしています。 この取り組みの中から1つの工夫を紹介します。

PRをまとめる

renovateではデフォルト設定だとパッケージごとにPRが送られてきます。 依存ライブラリが多いとPRが多くなりすぎることが懸念されたので、minorとpatchアップデートは1つにまとめるようにルールを決めています。 renovateの設定ファイルに下記の設定を追加することで実現ができます。

"packageRules": [
        {
            "packagePatterns": [
                "*"
        ],
            "updateTypes": [
                "minor",
                "patch"
        ],
            "groupName": "all non-major dependencies",
            "groupSlug": "all-minor-patch"
        }
]

なお、majorバージョンは変更が大きく、プロダクトへの影響も大きいため、個別のPRでしっかりと検証する必要があると判断し、対象をminor、patchのみにしぼっています。

最後に

いかがでしたでしょうか?コネヒトでは今回紹介したように、各々のメンバーが工夫をしながら業務改善を楽しんでいます。そんなコネヒトでは積極的にエンジニアを募集しているので、是非一度話を聞きにきてください!

hrmos.co

RxSwift 6.0の主な変更点

$
0
0

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

RxSwiftの6.0が公開されました。

公式のWhat's new in RxSwift6はこちらです。

What's new in RxSwift 6 ? - DEV Community

細かいdiffはこちらをご覧ください。 https://github.com/ReactiveX/RxSwift/compare/5.1.1...6.0.0

------------------------------✂--------------------------------------

ここでは、RxSwift6.0にアップデートすると影響受けそうなところから見ていきたいと思います。

Breaking Changes

破壊的な変更はなさそうでした。

Deprecated

以下のものの命名が変更されました。コンパイルは通りますがwarningになります。

  • Rename catchError(:) to catch(:).
  • Rename catchErrorJustReturn(:) to catchAndReturn(:).
  • Rename elementAt(_:) to element(at:).
  • Rename retryWhen(_:) to retry(when:).
  • Rename takeUntil(:) to take(until:) and takeUntil(behavior::) to take(until:behavior:).
  • Rename takeWhile(:) to take(while:) and takeWhile(behavior::) to take(while:behavior:).
  • Rename take(_:) duration overload to take(for:) (e.g. take(for: .seconds(3))).
  • Rename skipWhile(_:) to skip(while:).
  • Rename takeUntil(_:) to take(until:).
  • Rename observeOn and subscribeOn to observe(on:) and subscribe(on:).

Single

RxSwift5まではSingleはsubscribeするとSingleEventという独自のResultみたいなものを返していましたが、これがResultに変わりました。

// RxSwift 5publicenumSingleEvent<Element> {   
    /// One and only sequence element is produced. (underlying observable sequence emits: `.next(Element)`, `.completed`)  case success(Element) 

    /// Sequence terminated with an error. (underlying observable sequence emits: `.error(Error)`) case error(Swift.Error)   
}
// RxSwift 6publictypealiasSingleEvent<Element>= Result<Element, Swift.Error>

これまでは .error, onErrorとしていたところを .failure, onFailureに変更しないとwarningになります。

変更のコミットはこちら

New

全てのReactiveCompatibleなオブジェクトのプロパティでBinderの実装が不要に

これまではclassのプロパティを

classMyView:UIView { 
    vartitle:String
}

このようにbind(to:)できるようにするには

viewModel.title.bind(to:myView.rx.title)

下記のような記述を書く必要がありました。

// RxSwift 5extensionReactivewhere Base:MyView {
    vartitle:Binder<String> {
       Binder(base) { base, title in 
           base.title = title
       }
    }

RxSwift6ではReactiveCompatibleなオブジェクト(.rxの使えるオブジェクト)ではこの記述は不要になります。

変更のコミットはこちら

withUnretained

ObservableTypeに withUnretainedが追加されました。

これまでは[weak self]をいちいち書く必要がありましたが

viewModel.importantInfo
    .subscribe(onNext: { [weak self] info inguardletself=selfelse { return }
        self.doImportantTask(with:info)
    })
    .disposed(on:disposeBag)

withUnretainedを使うことでoptional bindingが不要になります。

viewModel.importantInfo
  .withUnretained(self)
  .subscribe(onNext: { owner, info in 
    owner.doImportantTask(with:info)
  })
  .disposed(by:disposeBag)

変更のコミットはこちら

decode(type:decoder:)

Observabledecode(type:decoder:)が追加されました。

decodeがメソッドチェーンですっきりとかけるようになりました。

functest() {
    letrawJSON="""        [          {"id": 1, "name": "Foo", "country": "Foo"},          {"id": 2, "name": "Bar"}        ]""".data(using: .utf8)!

    Observable
              .just(rawJSON)
              .decode(type:[FakeObject].self, decoder:JSONDecoder())
}

privatestructFakeObject:Equatable, Decodable {
  letid:Intletname:Stringletcountry:String?
}

変更のコミットはこちら

driveとemitで複数のobserverにbinding

driveとemitでは一つにしかbindingできませんでしたが、複数できるようになりました。

viewModel.string.drive(input1, input2, input3)
viewModel.number.emit(input4, input5)

変更はこちらこちら

Infallible

RxSwiftにInfallibleが追加されました。

errorを流さないObservableです。似たようなものにRxCocoaのDriverやSignalがありますが、これらと違うのはメインスレッドは保証されないのとresourceをshareしたりreplayしない点です。

実装はこちら

ReplayRelay

ReplayRelayが追加されました。

PublishRelay, BehaviorRelayと同様にReplaySubjectのwrapperでerrorやcompleteを流しません。

変更のコミットはこちら

distinctUntilChanged(at:)

distinctUntilChanged(at:)が追加されdistinctUntilChangedにkeyPathを使うことができるようになりました。

structTestObject:Equatable {
            letvalue:Intletother=""
        }

        Observable<TestObject>
            .create { observer in
                observer.onNext(TestObject(value:1))
                observer.onNext(TestObject(value:1))
                observer.onNext(TestObject(value:2))
                return Disposables.create()
            }
            .distinctUntilChanged(at: \.value)
            .subscribe(onNext: {
                print($0.value)
            })

変更のコミットはこちら

UIApplication.rx

UIApplicationのLife cycle Eventなどが追加されました。

追加されたものはこちらです。

  • didEnterBackground
  • willEnterForeground
  • didFinishLaunching
  • didBecomeActive
  • willResignActive
  • didReceiveMemoryWarning
  • willTerminate
  • significantTimeChange
  • backgroundRefreshStatusDidChange
  • protectedDataWillBecomeUnavailable
  • protectedDataDidBecomeAvailable -userDidTakeScreenshot

変更のコミットはこちら

Viewing all 376 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>