Medley Developer Blog

株式会社メドレーのエンジニア・デザイナーによるブログです

介護のほんねリニューアルの話

はじめまして。メドレーでデザイナーをしているおおのです。わたしはメドレーには昨年(2020年)の6月に入社し、現在老人ホーム・介護施設の検索サイト「介護のほんね」を担当しています。

介護のほんねは昨年リニューアルを行いました。今回は、そのリニューアルプロジェクトの中で自分が取り組んだこと(主にサイトトップのリニューアルについて)についてお話しようと思います。

目次

  1. 介護のほんねとは
  2. リニューアルの背景
  3. プロジェクトについて
  4. プロジェクトとの関わりについて
  5. サイトトップの制作
  6. プロジェクトを終えて

介護のほんねとは

介護のほんねは、納得できる老人ホーム・介護施設探しができる検索サイトです。介護のほんねには、全国にある多くの施設が掲載されています。予算やエリアなどお好みの条件で施設を検索したり、気になった施設へ見学予約や資料請求などお問い合わせができます。また社内の入居相談員による施設に関する資料送付や条件にあった施設紹介、施設見学の日程調整などのサポートにも対応しています。

リニューアルの背景

介護のほんねは2014年にローンチされました。しかし、長い間の運用の中で古いデザインと新しいデザインが入り混じっている部分があったり、SEOの観点での強化が必要だったり、今後の成長に向けて手直しする部分が積もり始めていました。また、2019年に「医療につよい老人ホーム検索サイト」にコンセプトを変更しましたが、より多くの方にご利用いただくためにも、医療につよいというコンセプトからさらに一歩進み、様々な状況のお客様に向き合い、お客様が介護に対して前向きに、そして後悔のない選択ができるよう寄り添うことのできるサービスにしていきたいという思いから今回のリニューアルが始まりました。

プロジェクトについて

リニューアルプロジェクトは昨年5月頃から始まり、外部のデザイン制作会社と連携して進められました。制作会社の方には、デザイン業務の支援をお願いしつつ、週1〜2回の定例で進捗報告や業務内容の確認を行っていました。

プロジェクトとの関わりについて

自分が入社したのが昨年6月後半だったので、デザインや開発も部分的に進んでいる状況でした。介護領域の勉強や、担当サービス、競合調査、リニューアルプロジェクトや介護のほんねのこれまでの歩みについてなどのキャッチアップと並行してリニューアルのデザインのことも考えていました。

そこで次のようなことを意識して動きました。

プロジェクトに対して

  • 社内のメンバーといつでもコミュニケーションがとれることを活かし、外注先とも相談して自分自身も手を動かしながらデザインをブラッシュアップすること

社内に対して

  • サービスに対するメンバーの思いを確認しつつ、これまでのサービスの歩みを尊重して動くこと
  • 社内のデザイナーの先輩など頼れる人には頼ること

サイトトップの制作

プロジェクトにジョイン後、主にサービスの顔であるサイトトップについて、アイデア出しやデザインをしました。

サイトトップにどのようなコンテンツを掲載するのか、また、お客様が介護に対して前向きに、そして後悔のない選択ができるよう寄り添いたいというサービスの思いをどのように表現すればよいか考えていきました。

掲載するコンテンツに関しては、サイトトップを誰に向けて作るのかという部分を介護施設探しが初めての人に設定し、このサイトでは施設が探せることを伝えてサービスのコア機能を全面に出しつつ、介護のほんねでの施設探しの魅力ポイントや、介護や施設探しに関するコラムやQ&Aなどお役立ち情報も掲載するようにしました。

▼ リニューアル前のサイトトップ

f:id:medley_inc:20210401103421p:plain

また、当時のサイトトップは上に貼ったキャプチャの通りで、落ち着いた青を基調としたクリーンで誠実そうな印象がありました。これまで築き上げてきたプロダクトのイメージや印象は、リニューアル後も受け継いで残していきたいなと思いました。

それをふまえた上で、サイトトップの新しいキャッチコピーやキービジュアルをどういうものにするのかプロジェクトのメンバーとアイデアを出しながら考えました。しかし、「どのアイデアも間違ってないけどもっといいのがありそうだな」という気持ちが拭えず、なかなか決まりませんでした。

そこで、改めて原点に帰って情報を整理するために次のことに取り組みました。

  • ユーザーを知る
  • 介護のほんねが提供する価値を整理する
  • 信頼できる情報から納得できる介護サービスと出会えることをキャッチコピーに落とす
  • 介護のほんねの世界観を視覚的に伝えられるようなメインビジュアルの選定

ユーザーを知る

まずはじめに介護のほんねのユーザーデータを1件1件見ていきました。そこにはお客様の情報や施設を探している理由など様々な情報がまとまっています。それらのデータを見ていくと、おおよそ4つのパターンにわけられることがわかりました。

① 退院後の施設を探したい
転倒など何かしらの理由で入院している家族の退院期限が迫っており、退院後に在宅での介護ではなく施設にいれる必要があるケース

② 施設を移動しないといけなくなった
介護度があがったことで施設の受け入れ可能範囲から外れた場合や、施設の中でトラブルがあるなどの事情により施設を移る必要があるケース

③ 今後に備えて早めに動きたい
今すぐ介護施設に入れる必要があるわけではないものの、親が高齢でひとり暮らしをしていて不安なためまだ自分で動けるうちに施設にいれておきたいケース

④ 在宅介護では限界がきてしまった
自分自身も高齢になってきたため、仕事や家事に加えて介護の両立が難しくなってきた方が施設を探しているケース

このようなお客様のデータを見ていると、事情が事情だけになるべく急いで施設を探しているものの、離れても家族が幸せに暮らせるように慎重に施設を探したい、という思いが伝わってきました。

▼ ユーザーの大まかなパターン

f:id:medley_inc:20210401115623p:plain

f:id:medley_inc:20210401115632p:plain

f:id:medley_inc:20210401115641p:plain

f:id:medley_inc:20210401115649p:plain

介護のほんねが提供する価値を整理する

次に、ユーザーの方に対して介護のほんねができることはどういうことかを整理しました。

介護のほんねは「老人ホーム・介護施設の検索サイト」ということからもわかるように、施設を探すことができ、プロの入居相談員が施設探しから実際の入居までサポートするサービスです。また介護のほんねとしては、お客様が介護に対して前向きに、そして後悔のない施設探しができるよう寄り添いたいという思いがあります。そこで、介護のほんねの価値を機能的なものと情緒的なものにわけて整理してみました。

機能的価値=すぐに納得できる施設が見つかること
情緒的価値=家族のために、いろんな思いをもって施設探しをしているユーザーに寄り添うこと

ユーザーデータから見えてきた「事情が事情だけになるべく急いで施設を探しているものの離れても幸せに暮らせるように慎重に施設を探したい」、そのようなユーザーに介護のほんねは寄り添って、後悔することなく納得できる施設がすぐに見つかるようにサポートしていくことを伝えたいなと思いました。

納得できる介護サービスと出会えることをキャッチコピーに落とす

ユーザーやサービスの提供価値について整理をしたところで、新しいコンセプトをサイトトップのキャッチコピーにどう落とし込むのかを考えました。

そもそもサイトトップのキャッチコピーですので、前提として「サービス概要、コンセプトが端的に伝わること」は大事にしたいと思いました。サービス概要は、繰り返しになりますが老人ホーム・介護施設の検索サイトです。そして端的にサービス価値を伝えるためにも、先程述べたサービスの機能的価値である「すぐに納得できる施設がみつかること」をキャッチコピーに盛り込もうと考えました。

色々アイデアを出した結果、「老人ホームが見つかる」というサービスのコア機能に加え、介護のほんねならではの「すぐに」見つかるということや、後悔のない納得いく施設選びができるというエッセンスを入れて、最終的に「納得できる老人ホームがすぐ見つかる」というコピーになりました。

介護のほんねの世界観を視覚的に伝えられるようなメインビジュアルの選定

キャッチコピーはサービスの機能やできることをわかりやすく伝えるものにしたため、キービジュアルは先程述べたサービスの情緒的価値のエッセンスをいれたいと思いました。それを踏まえ、いろんな思いを持った相談者の方に寄り添い、ご家族が施設に入居されてから始まる新しい生活を前向きに捉えられるようなビジュアルにしようと思いました。

キービジュアルの選定にあたり、チーム内で意見を集めながら、色々アイデアが出ました。入居後のイメージやサービス概要が伝わりやすい老人ホームの屋内風景の写真、入居後の楽しい生活が期待できそうな施設のスタッフと入居者の笑顔の写真や、サービスの寄り添うスタンスを抽象的に伝える手を握り合うような写真など、たくさんの写真をあてはめて検討を繰り返しました。

▼ イメージ選定

f:id:medley_inc:20210401103430p:plain

  • 車椅子が写りこんでいると、自立度が高い状態で施設を探している方(※介護の必要がない元気な方向けの施設もあり、元気なうちから施設に入られる方もいらっしゃいます)にとって自分が使っていいサービスではないのかとマイナスなイメージにならないか?
  • 施設スタッフと入居者が笑顔で写っている人が写ったモデル風の綺麗な写真だと素材感が出すぎて嘘っぽくならないか?
  • 手を包みこむなどモチーフが抽象的すぎるとかえって何も伝わらないのではないか?

写真を選ぶ中でチーム内でも相談しながら、小さな違和感をひとつずつつぶしていきました。そして、最終的に下のようなキービジュアルになりました。

▼ リニューアル前とリニューアル後

f:id:medley_inc:20210401115327p:plain

f:id:medley_inc:20210401115319p:plain

これからの新しい生活がポジティブなものに捉えられるような、スタッフと笑顔で生活する入居者が写っており、背景に程よく雑多感が残る素材感を抑えた写真を選びました。写真に写っているのは入居者と入居者に寄り添うスタッフですが、介護のほんねも同じように、施設探しをしている方に寄り添う姿勢がこの写真から間接的に伝われば嬉しいなと思っています。

プロジェクトを終えて

プロジェクトは一段落しましたが、スタートラインにたったところなので、まだまだ追加したい機能や磨き込みたい部分も山積みだなと感じています。

今回、自分のデザインに納得感をだすためにサービスや介護の知識、チームのメンバーが考えていることへの理解を深めながら並行してリニューアルのデザインを手掛けたことは、とてもやりがいのあるものでした。

また、リニューアルをきっかけに改めてサービスの価値や目指したい世界を整理できたのはとても良かったです。これからも介護のほんねの目指したい姿を見据えながら、より多くの方につかってもらえるようなサービスにしていきたいと思っています。

www.medley.jp

開発チームと一体となったQA

みなさんこんにちは、メドレーの QAエンジニア かみむら です。 入社して間もなく1年になります。 CLINICS開発チームのQA活動を行っています。

私自身の経歴としては、テスト・品質関連業務に足を突っ込んでから早20年になろうとしています。 2020年はメドレーのQAエンジニアが一気に0名→2名になりました。 先日 Magic Pod導入の記事 を公開した米山とはかつての同僚でもあります。 現在は別々の部署に所属していますが、お互い得意分野を発揮しつつ時折情報交換や相談ごとなどをしているような関係性です。 自分とは違うタイプの同職種がいると、何かと捗ります(その辺りはまた別の機会に…)。

CLINICS開発チームでは、エンジニア・デザイナー・QAエンジニアがワンチームで開発を進めています。 これまで私が経験してきた現場では、QAは開発チームの外側にいる(ステークホルダーとして)ことが多く、新鮮な気持ちでいます。

この記事では私の入社以降取り組んできたQA活動の概要についてお話ししたいと思います。

「CLINICSのQA」とは?

さて、何しろこれまで「QAエンジニア」という職種のひとが存在しなかった開発チームのため、まずは「CLINICSに必要なQAってなんだろう?」というところから考えはじめました。 もちろん、数々のプロダクトを大きな障害なくリリース・運用してきているので、それなりにQA/テストの技術力はあるはずです。

入社前にも、何度も

  • なぜ(他の手段ではなく)QAエンジニアの採用が必要なのか?
  • QAエンジニアにどんな役割を期待しているのか?

といった点を開発チームの上長と話し合いました。

なぜ(他の手段ではなく)QAエンジニアの採用が必要なのか?

私は第三者検証会社に所属していた期間が長いこともあり、品質についての悩みがある開発チームにテスト支援だったりコンサルティング的役割で関わることが多かったので、「てっとり早く他社に相談するのではなく、採用したいのはなぜだろう?」という疑問が単純にありました。

現場の思いとして、以下の点が挙げられていました。

  • プロダクト開発エンジニアがリリース時のリグレッションテスト(シナリオテスト)をメンテ・実施しているが、CLINICS(電子カルテ)の複雑さに追いついていくのが難しくなってきた
  • ここに対して専門性をもって取り組むことで、複雑なドメイン知識を扱うプロダクトを安定して開発リリースできる仕組みを作りたい

QAエンジニアにどんな役割を期待しているのか?

描いている組織体制像としては以下のようなお話でした。

  • QAエンジニアにテストフェーズだけ縦割り的に関わってもらうのではなく、プロダクト開発チームとしてひとつになって、有るべき開発プロセスを一緒につくり上げていきたい
  • 開発エンジニアがテストに関してしっかりと理解をしていくことで、そもそも品質の高いプロダクトをつくることができる、といった世界を目指したい

これら課題に対してチャレンジ的に「やってみたい」と強く思ったのと、私はこれまでにいろいろな現場の開発体制の中でテストエンジニア/QAエンジニアとして活動してきていましたが、「プロダクト開発チームとしてひとつになって」というところがすごく「それ良いな!」と思ったのを覚えています。 専門職に任せるのではなく、「一緒に理想の世界をつくりあげたい」という気持ちがとても見える良い組織だと思いました。

「QAエンジニア」「テストエンジニア」「SET/SWET」

ここで少し職種名に関する補足説明をしますと、「QAエンジニア」という呼称は比較的新しい概念なんじゃないかと思います。

一般的には「テストエンジニア」と言い、文字通り「テスト業務に特化したエンジニア」を指していました。その後『テストから見えてくる グーグルのソフトウェア開発 』(2013年日本語版発行)から「SET/SWET(Software Engineer in Test)」が日本でも認知され、国内の導入事例が出てきたことで一気に広まった印象です。

私の解釈では、「QAエンジニア」と呼称する場合、「テストエンジニア」や「SET/SWET」の素養も含みつつテスト以外にも「品質向上のための活動全般を積極的に担う役割」という意味合いが濃くなるんじゃないかと捉えています。

CLINICSのありたい「QA」の姿

上長と話し合った結果、以下のような活動をメインに据えていきましょうということになりました。

  1. (現状行っている)テストの改善
  2. プロセス改善
  3. 知識の底上げ

それぞれのトピックについて、現在CLINICSのQA活動としてどのように取り組んでいるかを1つずつ詳しく説明していきます。

「テストの改善」

現状CLINICSの開発サイクルは週1回のリリースとしています。 毎回リリース用にコードフリーズした環境に対してリグレッションテストを開発チーム全員で手作業(マニュアルテスト)により行っています。 日々の機能追加や改修の際に手をいれてはいますが、リグレッションテストのシナリオもツギハギ感がみえるようになってきました。 そして増えてきたシナリオによってどこがどう品質担保されているのか見通しが悪くなっている点が大きな悩みでした。

そこでまず、「現状のシナリオテストを分析し、全体的に再設計する」という計画をたて、現在は開発チームの中でもカルテに造詣の深い一部メンバーで定期的にMTG(レビュー会)を行いながらテスト設計の方針を組み立てています。 設計図ではマインドマップツールやマトリクスを作成して方向性や粒度をすり合わせしています。

私自身も、今までの業務でこれだけじっくり丁寧にテスト設計をしてきたことがないため、厳しくもたいへんやりがいのあるタスクとなっています。

「プロセス改善」

こちらはテストそのものではなく、開発チームのルーチンワークや体制に関わる改善です。

種まき的にスモールチームで新しいふりかえり手法を試してみたり、開発定例会で共有している障害情報とテスト実施中に見つかったバグの情報を一元化して残す仕組みを導入したりなど行いました。 特に「ふりかえり」については常に改善を意識できるプロセスで、上手なふりかえりをすればするほど開発品質が向上すると考えられているため、勉強会の後にフィードバックコメントをもらう仕組みをつくったりなどちょっとした隙間にも「ふりかえり」を小さく回せるように腐心しています。

それとまだ着手できていませんが、記録した障害・バグ情報も近いうちに分析・分類していって今後の開発に役立てたいと考えています。 バグ分析は時間がかかるのでなかなかサクッとはいきませんが、長期的視点では有用な財産になります。

また「開発プロセス」「業務フロー」自体の現状の悩みごとを現場の声として私が直接探るためと、開発チーム内で「共通の目標」を認識するためのブレストをリード陣と行いました。 進め方は「SaPID 」という改善手法を参考にしています。

SaPIDとは、”Systems analysis/Systems approach based Process Improvement methoD”の略語で、当事者自らが(最終的には仲間と共に)解決すべき問題点を特定し、現実的に解決、改善、そして革新を実現しながら段階的・継続的に自律運営へのゴールを目指す手法です。

誰かにやらせる、やらされるのではなく、当事者自らの意思、チーム・組織の意思で自律的に運営を進めることを志向するのが特徴です。

f:id:medley_inc:20210305203657p:plain

コロナ禍の中、日によってリモートで参加のメンバーがいたりなどふせんをつかったワークにも工夫が必要でしたが、最終的には「共通の目標」として

  • 自分と身の周りに役に立つ状況をつくる
  • 世間に認知されるプロダクトをつくる

というような定義をつくることができました。

「知識の底上げ」

CLINICSはこれまで中途採用メンバーが多かったため、OJT中心で体系的な教育はまだまだ整備をしている段階です。 新卒入社も増えてきている昨今ではメンバー全体で知識レベルが合わないことによる弊害が出てきており、目線を合わせていくことが喫緊の課題でした。

普段の業務の中で断片的な情報を得ることはできてもなかなか体系的な知識を効率的に身につけることは難しいので(専門書は分厚くてハードルが高い)、上長からのたっての願いでもあり、QAに従事している者にとっては割と初歩的なテスト技法から教えることにしました。 私のようにずっとQA活動をしてきた者にとっては当たり前の技法でも、開発エンジニアにとっては意外と知る機会がなかったりするものです。 覚えておくとテストの段階だけではなく設計品質もあげることができるので、定着するように日々取り組んでいます。

まずは教科書的な内容をCLINICSチームのConfluenceに書いて、講義形式でCLINICS開発チームのメンバーに説明し、実践編として宿題を出して答え合わせと解説を行う、という流れで行っています。 学習者としては話を聞いているだけでは覚えにくく、実際に手を動かしたり、日々の業務で本当に困った経験をすると学びたい欲に火がつくと思っているので、実践を大切にしています。 演習問題は以下のような書籍を参考にして作問しています。ありがとうございます、著者の方々。

ソフトウェアテスト技法ドリル―テスト設計の考え方と実際
はじめて学ぶソフトウェアのテスト技法
ソフトウェアテスト技法練習帳 ~知識を経験に変える40問~

一度教わっただけではなかなか覚えるのも難しいので、大事なテスト技法(境界値分析とか…)は折に触れて何度も何度も口にするようにしています。

(再)「CLINICSのQA」とは?

冒頭で引用した Magic Pod導入の記事 では、「テストの自動化は(リリース後即座に修正できない)アプリから着手していく」方針としました。 現在は、CLINICSのWebページ側のテスト自動化も推進しています。

テスト自動化の目的は現場によっていろいろと思いがあるものです。なぜ「テスト自動化をやるのか?」については、機械にリグレッションテストを任せて手が空いた分、より高度な(経験則が必要な)探索的テストができるようになるから、と考えています。

最初の問いに戻ります。
「CLINICSのQA」とは何か?

品質向上する仕組みが自然にできている自律した組織で、私は開発チームメンバーと「おもしろいテスト」「楽しいテスト」をしていきたい、と思っています。 それによって顧客が出会う可能性のある不具合が減り、「そもそも品質の高いプロダクトをつくることができるという世界」に近づけるのではないかな、と考えています。

「おもしろいテスト」「楽しいテスト」とは発見であり、学習であり、フィードバックのサイクルによって生まれます。 そのためにも前述の「プロセス改善」と「知識の底上げ」は両輪で進めていく必要があります。

品質向上のための手段は、テストの他にも実に多岐に渡ります。 長年やってきた私もまだ全貌を掴み切れていない「QAのエンジニアリング」ってこんなに奥深く楽しい! ということが開発チームメンバーの共通認識になるとうれしいです。

f:id:medley_inc:20210305203713p:plain

最後までお読みいただきありがとうございました。

www.medley.jp

HTTPコンテンツ圧縮でパフォーマンス改善

事業本部 プロダクト開発室のエンジニアの中畑です。

オンライン診療・服薬指導・クラウド診療支援システム「CLINICS」の開発・基盤周りを担当しております。

今回は、HTTPのコンテンツ圧縮について調査・対応する機会があったので、本ブログにて紹介したいと思います。

HTTPコンテンツの圧縮とは

HTTPコンテンツの圧縮とは、HTTPの通信においてWebサーバー側が返すデータを、なんらかの形式で圧縮してクライアントに返すことです。圧縮されたレスポンスをクライアント側は解凍して利用します。

HTTPコンテンツの圧縮によって得られるメリット・デメリットは以下の通りです。

⤴メリット

  • 通信の帯域使用量を減らせる
  • それによって通信にかかる時間を削減し、ページ表示速度を向上できる

⤵デメリット

  • 圧縮・解凍コストがかかる
    • ただし、圧縮・解凍コストはほとんどの場合は小さいため、メリットを下回る
  • 大容量ファイルやもともと圧縮されているファイル(画像や動画、PDFファイルなど)を圧縮するのは、圧縮してもサイズがそれほど小さくならないため非効率である
    • サイズがあまり削減できない割に、圧縮・解凍にCPUリソースを使い、数百MBを超えるファイルになるとそれぞれ数秒かかることもある

HTTPコンテンツを圧縮するためには

HTTPコンテンツを圧縮するためには、クライアントが解凍可能な圧縮形式を指定する必要があります。解凍可能な圧縮形式を指定するには、リクエストヘッダにAccept-Encodingヘッダを指定します。

最近のブラウザでは、HTTPリクエスト時に自動的にAccept-Encodingヘッダを自動的に付加してアクセスしているので、ブラウザ経由の場合は特に明示的に指定する必要はありません。Chrome, Safari, Edgeなど、ほとんどのメジャーなブラウザではAccept-Encoding: gzip, deflate, brが指定されています(※2021-01-23時点)。

圧縮形式(gzip, deflate, br)

圧縮形式はいくつかありますが、ブラウザを利用する場合は以下のいずれかが選択肢になります。

  • gzip: LZ77と32ビットCRを用いた圧縮形式
  • deflate: zlib構造体とdeflate圧縮アルゴリズムを用いた圧縮形式
  • br: Brotliアルゴリズムを用いた圧縮形式。gzipに近いが大容量の言語辞書を用いて、頻出するパターンの単語を圧縮して効率化。そのため文章的なテキストではgzipよりも圧縮率が高いと言われる

Brotliは比較的新しい形式で、ほとんどのサーバー、ブラウザで対応しています。

サーバーでのHTTPコンテンツの圧縮方法(gzip)

サーバーはクライアントのAccept-Encodingリクエストヘッダを受け取り、その中から1つを選択して圧縮処理を行い、Content-Encodingレスポンスヘッダを付加してクライアントに結果を知らせます。

f:id:medley_inc:20210129123916p:plain

CLINICSが利用しているそれぞれのアプリケーション・ミドルウェアに絞って、どのようにHTTPコンテンツ圧縮を実現しているか解説したいと思います。いくつか圧縮形式はありますが、ここではgzip形式での圧縮方法について解説します。

NGINX

NGINXのngx_http_gzip_moduleを利用することでgzip圧縮することができます。

nginx.confのgzipディレクティブをonにすることで圧縮が有効になります。ただし、タイプを指定しないとContent-Type: text/htmlのときにしか圧縮されません。他のタイプでも圧縮したいときはgzip_typesディレクティブも合わせて指定する必要があります。gzip_types*を指定することで、すべてのコンテンツを圧縮することもできます。

gzip: on;
gzip_types: text/css application/javascript application/json

また、CloudFrontなどProxyを経由してのアクセスの場合はデフォルトでは行われません。Proxy経由のアクセスかどうかは、リクエストヘッダにViaヘッダがあるかどうかで判定します。

CloudFront経由でのアクセスの場合はVia: 1.1 xxxxx.cloudfront.net (CloudFront)のようにViaヘッダが付加されているため、NGINXにてProxy経由であると判定します。Proxy経由であっても何かしらの条件で圧縮したい場合はgzip_proxiedディレクティブを指定する必要があります。

ref. https://nginx.org/en/docs/http/ngx_http_gzip_module.html

CloudFront

CloudFrontのBehaviorの設定にて設定します。Compress Objects Automaticallyを有効化することで、gzip圧縮が有効になります。

f:id:medley_inc:20210129124017p:plain

上記を有効化すると、CloudFrontでは以下の条件で圧縮が行われます。

  • ファイルサイズが 1,000(≒1KB) 〜 10,000,000(≒10MB) バイトの間
    • よって、オリジンからファイルサイズを判定するためのContent-Lengthヘッダが付与されていない場合は、サイズ判別できないため圧縮されない
  • 特定のContent-Typeのコンテンツを圧縮する
    • テキスト系のコンテンツは圧縮するが、画像や動画、PDFなど、もともと圧縮されているものは対象外。詳しくはこちら
  • オリジン側(NGINXやRailsなど)から圧縮して返される場合は、再度圧縮は行わない
    • Content-Encodingヘッダの有無で判定している

ref. https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/ServingCompressedFiles.html

Rails

RailsはデフォルトではHTTPコンテンツの圧縮は行いません。Railsでコンテンツ圧縮を行いたい場合は、RailsのRack MiddlewareのRack::Deflaterを導入するのが簡単です。しかしながら、Rack::DeflaterはすべてのContent-Typeのコンテンツでも圧縮するので、画像や動画・PDFなど圧縮するべきでないコンテンツまで圧縮してしまいます。NGINXやCloudFrontなど、Rails外の他のサービスやミドルウェアに任せるのが良いと思います。

CLINICSでのHTTPコンテンツ圧縮のシーケンス

前章で解説したアプリケーション・ミドルウェアは、CLINICSでは以下のように連携しています。

f:id:medley_inc:20210129132442p:plain

AWS上にRailsアプリケーションをデプロイしており、通常のアクセスはロードバランサーからNGINXを経由してRailsにアクセスし、静的ファイルなどキャッシュコンテンツはCloudFront経由でアクセスしています。

CLINICSでは用途に合わせた圧縮を行っています。3つのケースを紹介します。

1. NGINX経由でRailsにアクセスした時

f:id:medley_inc:20210129124126p:plain

APIアクセスなどは上記シーケンスでアクセスしています。ほとんどがtext/htmlapplication/json形式のコンテンツとなり、NGINXにてgzip圧縮処理を行っています。Railsはアプリケーションの処理のみを行い、圧縮は行わないようにしています。

2. CloudFront経由でS3にアクセスした時

f:id:medley_inc:20210129124148p:plain

画像ファイルやPDF、静的なjs、cssファイルなどはサービスのデプロイ時にS3にアップロードしています。クライアントはCloudFront経由でアクセスし、S3から取得して、CloudFrontでgzipに圧縮処理を行っています。また、一定期間CloudFront上にキャッシュされるので、効率よく圧縮コンテンツを返します。

3. CloudFront→NGINX→Rails経由でS3にアクセスした時

f:id:medley_inc:20210129124210p:plain

静的ファイルの中でもシグネチャをチェックしているものは、このフローでアクセスしています。NGINXでも圧縮設定をONにしていますが、Viaヘッダがあるため、NGINXでは圧縮しないようになっています。

まとめ

HTTPコンテンツの圧縮を適切に行うことで、サービス全体のパフォーマンス向上が見込めます。更にCloudFrontを活用することで、アプリケーションやミドルウェアでの圧縮処理をなくし、更なるパフォーマンス向上が見込めます。

今回はHTTPコンテンツのgzip圧縮についてのみ触れましたが、Brotli圧縮についてもNGINX、CloudFrontともに可能なため、今後取り入れていきたいと考えています。もしHTTPコンテンツの圧縮設定を特に気にしたことがない方は一度確認してみてはいかがでしょうか?

最後に

メドレーでは、医療分野の社会課題をITにて解決するために日々邁進しています。医療という分野においては、機微な情報を扱ったり診療を止めないようにするために、パフォーマンス・セキュリティ共に高いサービスレベルが求められます。興味を持った方がいらっしゃいましたら、まずは気軽に面談できればと思いますので、是非ご応募ください!

最後までお読みいただきありがとうございました。

www.medley.jp

UIテストの自動化にMagic Podを導入した話

こんにちは。インキュベーション本部のQAエンジニアの米山です。主にCLINICSアプリのQAを担当しています。メドレーには2020年8月に入社しました。

今回は入社してまず行ったことの一つ、リグレッションテストの自動化と、そのために導入したMagic Podというツールについて、経緯や導入してみた結果をご紹介したいと思います。

CLINICSとは

私の所属するチームで開発しているCLINICSというプロダクトはアプリでオンライン診療や、クリニック・病院から処方箋を発行してもらうことができ、オンライン上で診察からお薬の受け取りまで完結できるサービスです。 プラットフォームはiOSAndroidのネイティブアプリ、それから同様のサービスをWebブラウザからも利用することが出来ます。

QA/リリース周りの状況

CLINICSの開発組織にQAエンジニアがジョインしたのは昨年(2020年)ですが、サービス自体は2016年にローンチされています。

本組織ではリリース前に行うリグレッションテストについては、開発メンバを中心にチーム全体で行う文化があります。 アプリのリリースは隔週で行っており、その都度開発メンバ自身によってテストが行われていましたが、自動化されたUIテストは存在していませんでした。

メドレーではQAエンジニアがジョインして間もないため、やりたいこと・やるべきことは多岐にわたる中でまず何から着手するべきか検討しました。

QAプロセスの策定・改善から、新機能をリリースまで推進するためのQA活動もあり、並行して幾つか動いている中でテスト自動化をどのタイミングで、どうやってスタートするか悩みました。

バリューから考える

f:id:medley_inc:20210115142126p:plain

メドレーのバリューはこの三つです。これらのバリュー視点で考えてみました。

「凡事徹底」として、リリース前のリグレッションテストをしっかり行うことは当然のこととして考えられます。

「中央突破」の視点ではどうかと考えると、やはりテストプロセスにおいて、特にリリース毎に繰り返し作業となるリグレッションテストを自動化することは王道であり、ベストプラクティスの一つだと考えられます。 そのため自動化は優先度高く進めるべきではあります。

残る一つ「未来志向」については、例えば1~2年後やその先を考えて、リグレッションテストが自動化されているべきか否かで言うとやはりYesです。

また、別の観点として、現在はわずか2人のQAエンジニアに対して、複数のプロダクトが存在している状況で、QAエンジニアがアサインされていないプロダクトも多くあります。

私自身も昨年10月からアプリ・基盤チームに異動したこともあり、今後についてもまた体制が変わっていくことは十分に考えられました。

そんな状況下では、仮にUIテストを自動化した環境を用意できたとして、その後に担当者が不在になった場合も考慮しておく必要があります(自動テストにおいて、担当が不在になったことでメンテされなくなり形骸化するケースはよくある話です)。

そのため、仮に実装者が不在となった後でも誰かに引き継ぎやすく、またエンジニア以外でも運用できる環境が望ましいと考えました。そういった観点でツールとしては基本ノーコードでもメンテできるMagic Podは有力な候補となりました。

これらをまとめると、以下のような結論に至りました。

  • テストの自動化は推進した方が良い
  • ただし、他のメンバでもメンテしやすい環境を選定する

ただしQAとしてやるべき事が沢山ある中で、テスト自動化だけに専念できる状況ではありません。 そのためなるべく他タスクと並行して小コストで進められる事も重要な要素でした。

自動化されたUIテストは全くない事や、他のテストの密度も鑑みると、なるべく早い段階で一定の自動テスト環境は用意したいという想いもありました。

これらの状況も踏まえ、ツールを選定・トライアルしてみた結果、Magic Podを導入することに決めました。

Magic Podの紹介

Magic Podについて、サービス自体の詳細は割愛しますが、端的にいうとクラウド環境かつGUIからUIテストの実装及び実行を行うことができるツールです。

GUIで自動テストが実装できるツールだと、Autifyなども有名です。 Autifyはブラウザ向けのツールですが、実装方法はMagic Podとは少し異なり、操作をレコーディングしてテストシナリオが自動で生成される形が基本です。

一方、Magic Podは以下のようにアプリの画面をまずキャプチャで取り込み、そこからテストで使いたい項目を選択し、シナリオにドラッグアンドドロップしていくことでテストシナリオを生成することができます。

f:id:medley_inc:20210115141554p:plain

ログインなど、複数のテストで使う部分は共通化しておきます。

テスト対象がiOSアプリであろうと、Androidアプリやブラウザであろうと基本的に同じI/Fからテストの生成・メンテが出来ることは大きな強みの一つです。

また、テストで使用するフィールドの要素を選択可能なことも、状態変化に強いテストとする上での強みとなります。

例えば「調剤薬局名でさがす」というテキストフィールドに対して、そのテキストを使うのか、IDなのか、テキストフィールドなのかxpathなのかといった所です。

そのため、

  • テキストが頻繁に変わるような場所(例えば日付など)ではテキストを使わない
  • アプリ内部でリファクタリングなどが動いている場合であれば逆にIDは変わる可能性が高いため、テキストで指定する

UIテストを作り込む上では当然のことではありますが、上記のような工夫によりテストの成功率を上げることができます。

導入してみて

トライアル中は探りながらの部分はあったものの、慣れると実装工数は非常に短期間で実装でき、トータルでもiOSで2~3週間(オンボーディング含む)、AndroidのUIテストについては実質2~3日で基本的なテストシナリオの自動化を行う事ができました。

その後、運用しながら落ちやすいテストの改修を行ったり、運用が安定してからはCIにも連携しています。

UIテストの運用においては定期的に実行することは非常に重要なことですが、Magic Podの場合、BitriseではUI上から設定でき、Circle CIに対してもドキュメントを参照しながら比較的容易に設定できます。

実際、昨年1クォーター運用してみて、幾つかのクラッシュをリリース前に検知してくれました。

また、私自身、過去にはXCTestにおけるUITest(Testing Your Apps in Xcode)やAppiumを使ってUIテストを運用していたため、以下ではそれら他ツールとの比較も含めて紹介してみたいと思います。

実装コスト

実装コストにも初期構築と、その後のメンテコストで分かれますが、他のツールと比較して、大きく異なるのは初期構築コストだと思います。

Magic Podについては環境構築コストは非常に低コストで行うことができます(基本的な部分は1日あれば十分だと思います)。 またテストのレポーティングやキャプチャ機能なども標準で付いていますので、この辺りも自前で頑張る必要はありません。

次にメンテコストですが、例えばXCUITestではまずビルドを行い、debugして各ボタンなどの要素のIDなどを確認し、それらを用いてコーディングしていました。 Magic Podでは一度アプリをアップロードして、スキャンすることで画面の要素を一括で取得でき、その中から操作したい要素を選択することができます。

そのためこちらもコストはだいぶ下がります。ただ、この部分については他のツールや言語でも慣れればそう時間はかからないのでもしかしたら大差ないかもしれません。

あえて言うとdebugでIDを確認する手間が楽になる、実装したテストを試して実行するのが容易(ビルド待ちの時間がない)といった辺りでしょうか。

運用コスト

UIテストといえばFlakyなテスト(落ちたり落ちなかったりするテスト)に悩まされることは多いですが、運用してみると最初の内はそういったこともありましたが、現状ではほぼ起きていません。

これはMagic Podに限った話ではありませんが、

  • クラウド上で実行されることで環境要因で落ちることは稀
  • 落ちた時には自動でリトライされる
  • ビルドもCI上で実行している
  • 実行はメンバが活動していない時間帯に行っている

といった辺りが要因かと思います。

またMagic Podのようなツールを使っている場合に助かる部分としては、Xcodeなど、UIテストに必要なツールのアップデートに対するメンテが不要ということも挙げられます。

逆に少し辛い所

ここまでMagic Podの良い部分を多く書きましたが、逆にこのようなGUIでのテストツールを使うことで少しやり辛い点も紹介しておきたいと思います。

1. テストコードのレビュー

テストコード(ケース)はMagic Pod上で管理されているため、PRレビューなどのプロセスを行うことができません。 そのため、ケースの修正に対して、反映させる前にレビューしてもらいたい場合は、テストケースをコピーしてから編集するなど少し工夫が必要になるかと思います。

現状では困ることはありませんが、複数人で同一のプロジェクトに対して運用したい場合は少し煩雑になりそうです。

2. テストコードの管理

自動テストにおいて、テスト結果に影響が出る仕様変更が入るような場合、仕様変更に対するテストコードの修正は開発と並行して用意しておき、プロダクトへの変更がマージされるタイミングで同時にテストコードの修正もマージしたいケースがあります。

Magic PodではGitHub上でテストコードを管理していないため、このようなケースへの対応を自動で行うことが難しく、予めテストケースを分けて用意しておき、実装がマージされた後に手動で置き換えるか、マージされた後に影響のあるテストケースを修正するといった手動でのプロセスが必要になります。

現時点で気になったのは上記の2点ですが、これらも今後改善されていく可能性は大いにありますし、プロセスの中での工夫次第で対処も可能かと思います。

その他

基本的にUIテストを自動化する上で気をつけるべきことやアンチパターンはどんなツールを使っても同じです。 他のツールでは難しいことが、このツールでは実現出来るということも稀で、時にはプロダクト側で手を入れる必要もあります。 どんなツールであれ、何かしら工夫すれば達成出来ることが多いため、違いが出るのは実装や運用、オンボーディング等のコスト部分が最も大きいのではないかと感じています。

周囲のサポート

テスト自動化を行う場合(だけではないですが)、周囲の理解を得ることは大事な部分ですが、チームメンバは皆前向きで興味を持ってくれて進めやすい環境でした。

特にCI連携の部分ではiOS/Androidの開発の方にもサポートしていただき大変助かりました。

そしてMagic Podについては、数年前から運用している株式会社ノハナの武田さんにも事前に話を伺ったり、オンボーディング中は質問させていただいたりしました(ありがとうございました!)。

またMagic Podの伊藤様には導入時からトラブルシューティングに多大なサポートをいただいています。

Circle CIに入れ込む際には、ちょっと詰まった点があり伊藤様とメールでやり取りしていたのですが、その日のうちにドキュメントがアップされたり、 とある環境下で不明なエラーが出ていて相談した際には、ストアからCLINICSアプリをダウンロードして試していただいたり、とにかくいつも迅速かつご丁寧な対応が印象的でした。

まだQAチームもないような少人数の状況では、こういったトラブルに対して相談でき、共に解決法を探れる方がいるという意味でも非常に心強いです。

今後について

アプリのUIテストについて、改善していきたいことはまだまだ沢山あるのですが、現状でも基本的なテストは用意できているため、じっくり腰を据えて改善していきたいと考えています。

また現在はブラウザのテスト自動化を進めています。メドレーのCLINICS以外のプロダクトの多くはWebブラウザをプラットフォームとしているため、Webについてはプロダクトを跨いだ活動も行っていければと考えています。

長くなりましたが、最後までお読みいただきありがとうございました。

www.medley.jp

ChatOpsな稟議ワークフローシステムを開発しました

はじめに

こんにちは。コーポレートエンジニアの溝口です。
メドレーでは、今年7月に稟議ワークフローシステムを導入しました。
詳細は「システム概要」の章でご紹介しますが、システムの全体像としては以下のようになっております。

f:id:medley_inc:20201225155753p:plain

ワークフローシステムと聞かれたら、どんなシステムを思い浮かべますか?
申請者がシステムで申請すると、予め定められた承認者へ承認依頼がメールで通知され、申請内容の確認及び承認のためにシステムへログインする、という流れがよくあるワークフローシステムではないかなと思います。

我々はコーポレート部門として「徹底的に合理性を追求した組織基盤や、仕掛けづくりを行っていく」ことを目指しています。故に、メドレーの稟議ワークフローシステムにおいても便利合理的なシステムを目指して開発を行いました。今回ChatOpsの概念を取り入れることで、一般的なワークフローシステムよりも洗練されたシステムを構築できたかなと思います。
本稿ではシステム概要及び、裏側の仕組みをご紹介していきます。
最後までお付き合いいただければ幸いです。

ChatOpsとは

ChatOpsとは「チャットサービス(Chat)をベースとして、システム運用(Ops)を行う」という意味です。ざっくり書くと「システムからChatへメッセージを飛ばし、次のアクションが同じChat画面で開始できる」というものとなります。(下記フローはあくまで一例です)

f:id:medley_inc:20201225155817p:plain

ChatOpsには以下のメリットがあると考えています。

  1. 常に立ち上げているツールという共通インターフェースである

  2. インタラクティブなコミュニケーションにつながり、スピーディである

  3. 共有しやすく、記録に残しやすい

本稿では、詳しく説明はしませんが、興味がある方は事例等を解説しているサイトもあるので、是非探してみてください。

なぜChatOpsなのか

稟議申請においては
承認者「これ値引きしてもらって××円になったはずだけど、金額間違ってない?」
申請者「すいません、変更し忘れました。差戻しお願いします」
などのコミュニケーションが度々発生します。
通常のシステムであれば、確認事項がある際はシステム内のコミュニケーション機能を使う、もしくは、ChatにURLや稟議番号を転記して確認のためのコミュニケーションを取ることが想定されます。

メドレー内の業務コミュニケーションはSlack上で殆ど完結しています。
Slackではない他の場所で会話が発生すると情報が分散しますし、SlackにURLを転記するといった行為や、別システムへのログインなども非効率です。
そこで、共通インターフェースのChatを中心にシステム構築する=ChatOpsを採用し、稟議ワークフローを構築してみようと考えました。結果、稟議ワークフローシステムの情報をSlackへ連携し、稟議におけるコミュニケーションはSlackに集約、承認行為もSlack上で可能、というシステムを構築することができました。

システム概要

申請

申請者はTeamSpirit上で稟議内容を記入し、稟議申請を行います。 TeamSpiritとは、勤怠管理や工数管理、経費精算などを管理できるクラウドサービスです。Salesforceをプラットフォームとして採用しており、アイデア次第でいろいろなカスタマイズが可能です。

f:id:medley_inc:20201225155854p:plain

Slackから申請できるようにするのがChatOpsのあるべき姿かもしれませんが、過去の申請からコピーしたい、申請種別ごとに入力する項目が異なる等の要件を考慮し、TeamSpiritから申請するように設計しました。申請の導線については、今後もよりよい仕組みに磨き上げていきたいと考えています。

承認

申請者が「稟議申請」ボタンを押下すると、Slackの稟議チャンネルに申請内容及び添付ファイルが自動投稿されます。
承認者は申請内容に問題がなければ、投稿に配置されているボタンを利用して承認・差戻しが行えます。

f:id:medley_inc:20201225155917p:plain

承認者は稟議ワークフローシステムへアクセスすることなく、Slackで承認行為が完結できます。稟議内容において確認事項がある場合にはSlackの投稿スレッドで申請者と質疑応答のやり取りができ、承認・差戻しの判断に必要なコミュニケーションが行えます。

後続のアクション

承認後には、
・申請者に承認or差戻し結果をSlackのDM(ダイレクトメッセージ)で通知する


・後続の担当者へSlackで通知する
・(法務押印などの)承認後タスクを作成し担当者に通知する
等、後続のアクションへつながっていく仕組みも用意しました。

システムの裏側

入力インターフェース

入力画面は、TeamSpiritで標準提供されている稟議オブジェクトを利用しました。入力項目は標準で用意されているコンポーネントを利用し、メドレー独自で定義しています。承認プロセスを定義すれば、Slackを使わずにTeamSpiritのみでも運用は可能です。

Slack通知

Salesforceの標準機能とApex を用いたScript処理を使ってSlack通知をしています。

Apexとは、Salesforce内で利用するビジネスロジック用のオブジェクト指向プログラミング言語(ほぼJava)のことです。

Slack通知までの大きな流れは以下です。
1. 稟議申請ボタンを押したタイミングでステータス項目を「未申請」から「申請中」へ変更
2. プロセスビルダーにてステータス項目が「申請中」になったことを検知してApexをコール
3. Apex内で申請情報や承認者情報の取得
4. Slack APIをコールし、Slackへ投稿

1~4のプロセスを詳しく見ていきます。

1. 稟議申請ボタンを押したタイミングでステータス項目を「未申請」から「申請中」へ変更

申請者が「稟議申請」ボタンを押したタイミングで承認プロセスを走らせます。
申請時のアクションとして、 ステータス「申請中」とします。ステータスが変わる毎に処理を走らせているので、ステータス定義は一つ肝になります。

2. プロセスビルダーにてステータス項目が「申請中」になったことを検知してApexをコール

プロセスビルダーを利用することで「稟議レコードを作成または編集したとき」に何らかの処理を実施することが可能です。今回は、ステータスが「申請中」になった場合にApexをコールする、という処理にしています。

3. Apex内で申請情報や承認者情報の取得

通知に必要な情報を揃えるため、Apexの処理では稟議オブジェクトの申請情報と合わせて次の承認者情報も取得しています。

String ownerId = p.OwnerId;
//申請者のユーザ名を取得
String applicant = [SELECT Username FROM User WHERE Id = : ownerId].Username;
//承認プロセスのレコード取得
String processInstanceId = [SELECT Id FROM ProcessInstance WHERE TargetObjectId = : p.Id ORDER BY CreatedDate DESC limit 1].Id;
//承認者のIDを取得
String approveId = [SELECT OriginalActorId FROM ProcessInstanceWorkitem WHERE ProcessInstanceId = : processInstanceId].OriginalActorId;

4. Slack APIをコールし、Slackへ投稿

Apexが取得した情報をもとに、Slackに投稿します。
稟議内容を記載し、申請者・承認者に対してメンションされるようにユーザ名も記載します。
また、今回は承認者用にインタラクティブボタンを配置する必要があったので、Block Kitを利用し、ボタン付きメッセージを作成しました。

{
  "text": "hoge",
  "blocks": [
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "fuga"
      }
    },
    ...
    {
      "type": "actions",
      "elements": [
        {
          "type": "button",
          "text": {
            "type": "plain_text",
            "text": "承認"
          },
          "value": "Approve",
          "style": "primary"
        },
        {
          "type": "button",
          "text": {
            "type": "plain_text",
            "text": "差戻し"
          },
          "value": "Reject",
          "style": "danger"
        }
      ]
    }
  ]
}

TeamSpirit(Salesforce)→Slackへの投稿は開発において苦労したポイントの一つです。

Slackからのアクション

Slackの投稿に埋め込んでいるボタンがクリックされた際は、Lambdaを経由してTeamSpirit(Salesforce)のRestAPIをコールし、承認処理を実行しています。
また承認後は、ボタンを「承認」スタンプに置き換えています。

開発を終えて

稟議ワークフローシステムを導入するにあたり、ChatOpsの概念を取り入れSlackに連携する業務システムを構築しました。
承認者からは「Slackで承認やコメントができ、社外からでもすぐに対応できるので便利」「Salesforce-Slack連携は他にも活用できるので是非やっていこう」などのコメントをいただきました。また、承認後にもスレッドにて、「振込お願いします」「物品届きました」等のやりとりも行っており、情報がSlackに集約されていく狙い通りの運用になったかと思っています。

Chatサービスを利用している会社では、今回ご紹介したChatOpsは業務効率化するにあたり、有効な手法になるのではないでしょうか。もちろん、すべてChatに連携すればよいというものでもなく、しっかり設計や運用検討を行う必要があります。
今後はChatOpsに限らず業務効率化につながるものはどんどんやっていきたいと考えています。

さいごに

メドレーのコーポレート部門では「徹底的に合理性を追求した組織基盤や、仕掛けづくりを行っていく」ことを目指して、業務効率改善のための開発を推進しています。面白そう!と感じた方、メドレーでどんどん改善してみたい!と思っていただけた方は、ぜひ弊社採用ページからご応募お願いします!

www.medley.jp

最後まで読んでいただきありがとうございました。

CloudFront のエラー監視精度を上げた話

はじめに

はじめまして、メドレー新卒入社2年目の森川です。

インフラ経験がまだ4ヶ月ほどの未熟者ですが、AWS認定資格クラウドプラクティショナー の試験に合格することができました。上位の資格取得に向けて今後も勉強していきます。

先日私が担当させていただいた CloudFront のアラート改善について、問題の原因と対応方法を本記事で書かせていただきます。

よろしければお付き合いください。

背景と問題

弊社が運営しているプロダクトの一つ ジョブメドレー ではインフラ環境に AWS を利用しています。

監視には CloudWatch や Datadog などを使用しています。サービスの異常を検知するための設定のひとつに、CloudFront のエラーレスポンス増加を検知するためのアラート通知があります。

CloudFront が返すレスポンスのうち、特定の時間範囲の中で4xx, 5xx系のエラーを返した割合が閾値を超過したことを検知して、CloudWatch アラームから Lambda を通して Slack に通知を行っています。

ところが、ある頃を境に CloudFront での4xx系エラーレスポンスの発生割合が増加し、アラートの通知頻度が想定以上に高くなってしまいました。

f:id:medley_inc:20201221172048p:plain

原因

調査を行ったところ、刷新した社内システムにて以下2つの原因でアラートが発生していることが分かりました。

f:id:medley_inc:20201221172100p:plain

原因1. 社外サービスからのアクセスでアラートが発生

CloudFront のログを確認したところ、社外サービス(Slack, Google スプレッドシートなど)からのアクセスに対してステータスコード 403 を返しているレスポンスログが数多く記録されていました。

これらのサービスに弊社の社内管理システムの URL がポストされると、プレビューを表示するためのリクエストが送信されますが、この時のリクエストが社外からのアクセスとして WAF で制限されていました。

f:id:medley_inc:20201221172124p:plain

インフラ刷新前から現在まで稼働している CloudFront のログも確認したところ、こちらでも同様のエラーレスポンスが発生していることが分かりました。しかし、エラー割合増加のアラートが頻発することは現在でもほとんどありません。

以前はジョブメドレーが持つシステム全体へのアクセスをひとつの CloudFront で処理していたため、アラート通知の割合として計算する際の母数が大きく、社外からのアクセスによるエラーが発生していても、その割合が閾値を超過することが少なかったからだと考えられます。

インフラ構成を刷新したことをきっかけに、これまで目立っていなかった社外からのアクセスという問題が表面化してきたのです。

原因2. 利用者が少ない時間にエラーレートが高くなりアラートが発生

CloudWatch アラームでは、一定期間内でのレスポンスのうち、4xx, 5xx系のエラーごとにその割合が閾値を超過したことを検知してアラートを発生させる設定としていました。

しかし、深夜など利用者が少ない時間に一度でもエラーが発生すると、その割合が跳ね上がってしまうことでアラート発生頻度が増加し、誤検知と言える状態になっていました。

以下の画像では、4xx系エラーの割合が夜間に100%となっている箇所が確認できます。(表示時間は UTC です)

f:id:medley_inc:20201221172140p:plain

対応方法

2つの原因に対し、それぞれ対応を行いました。

対応1. 特定の社外サービスからのアクセスをエラー検知の対象外とする

各サービスの設定により、プレビュー表示によるアクセスを停止させる選択肢が考えられます。しかし、該当するサービスすべてに設定を行うのは難しく、管理も複雑になりそうです。

そこで、特定の社外サービスからのアクセスを エラー検知の対象外とする 方針で対応を行いました。

ログのすべてを CloudWatch アラームの評価対象としていたために、誤検知と言えるアラートが発生しているのが現状です。したがって、評価させたいログだけに絞り CloudWatch で評価させることができれば解決が図れます。今回であれば、特定のユーザーエージェントや IP アドレスなどを除外して CloudWatch に渡すという処理が求められます。

その実現のため、今回新たに作成したのが Lambda の関数です。

f:id:medley_inc:20201221172156p:plain

S3 に CloudFront のログが保存されたことをトリガーに Lambda を起動させるように設定しました。

ログごとに記録されているリクエスト元のユーザーエージェントや IP アドレスなどを確認し、除外対象かどうかを判定します。

そうして選別を通過したログを今度はステータスコードの5つのクラス(1xx, 2xx, 3xx, 4xx, 5xx系)ごとに振り分けます。

ただし、CloudFront ではステータスコード000 が入ることがあります。

f:id:medley_inc:20201221172213p:plain https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/AccessLogs.html

ステータスコード 000 はアラートで検知したところで対応できることが特にないため、検知対象から除外する方針としました。

(S3 のログを直接確認すると 000 なのですが、Athena でログを確認すると 0 で表示されるため、少しハマりました)

こういった意図しない値がステータスコードに含まれていた場合などを検知できるようにするため、5つのクラス以外の値が含まれていた場合に UNKNOWN_STATUS_CODE なクラスとして分類するようにしました。

必要なものに絞ったログを6つのステータスパターンに分け、それぞれの件数を CloudWatch メトリクスへ PUT させます。

f:id:medley_inc:20201221172229p:plain

ここまでが Lambda の仕事となります。

各ステータスのログの件数を CloudWatch メトリクスで確認できるようになったので、レスポンス全体における4xx, 5xx系エラーの割合が算出できます。これを元に閾値を設定し、以前のようなアラートを作成することができました。

対応2. CloudWatch アラームの検知ルールを調整する

利用者が少ない時間にエラーレートが高くなりアラートが発生する件については、 CloudWatch アラームの検知ルールを調整 することによって対応しました。

一定期間でのエラー数に閾値を定め、超過した際にアラートを通知するように変更しました。つまり、割合ではなく絶対数で判断させるようにしています。

以下の画像の緑色のグラフが新たな検知ルールで参照するものとなります。橙色で示しているのが4xx系エラーの割合ですが、これが100%となっている箇所においても新たな検知ルールには反応していないことが分かります。

f:id:medley_inc:20201221172242p:plain

対応を終えて

Lambda を用いた集計処理の作成と、アラートの検知ルールの調整を行うことで、CloudFront のエラー監視精度を向上させることができました。

以前は頻繁にアラートがあがっていましたが、対応後はすっかり落ち着きを見せています。

システムの安定稼働を実現するためにも、適切にアラートを検知できるように今後も改善を図っていきたいと思います。

今回の課題に対する解決手段としてはシンプルな対応であったかとは思いますが、私には実りの多い紆余曲折な経験となりました。

AWS の基本的なサービスの連携を学ぶことができたことに加え、新たに作成する AWS のサービスの課金額の試算や、実行計画を定めてからの実装など事前準備を意識して取り組むことができました。恵まれた環境の中、日々学ばせていただいております。

さいごに

メドレーでは「医療ヘルスケアの未来をつくる」というミッションを掲げ、各プロダクトの開発・運営が進められています。

エンジニア・デザイナーをはじめ多くのポジションで新たなメンバーを募集しています。ご興味をお持ちいただけた方は、ぜひお気軽にお話しさせていただければと思います!

ここまでお付き合いいただき、ありがとうございました。

www.medley.jp

AWS MediaConvert と hls.js で動画配信サービスを構築しました

こんにちは、第一開発グループの矢野です。ジョブメドレー開発エンジニアとして、主にバックエンドを担当しています。

直近では、ジョブメドレーが先月リリースした 「動画選考」 機能の開発プロジェクトに携わっており、動画ファイルのアップロード/配信環境の設計・実装を行っていました。

今回のブログでは、この「動画選考」機能の開発に利用した AWS Elemental MediaConvert サービスと、hls.js という OSS ライブラリについて紹介したいと思います。

ジョブメドレーの「動画選考」機能

はじめに、今回リリースした「動画選考」機能について概要を紹介します。

新型コロナウイルス感染拡大によって、対面での面接に不安を感じたり、公共交通機関の利用が難しくなったりすることにより、満足な転職活動ができなくなっている方もいらっしゃるかと思います。 このような課題を解決するために、ジョブメドレーではリアルタイムにオンラインで面接を行う「WEB面接」と、事業者があらかじめ設定した質問に対して応募者が動画で回答を送る「動画選考」の2つの機能を提供開始いたしました。

ref. WEB面接・動画選考機能のリリースのお知らせ

動画選考(動画面接)は、近年増加傾向にあるオンライン選考の一種です。一般的に、求職者 / 就活生が PC ・スマートフォン等のカメラで、予め用意された設問に応じて動画を撮影し、企業に送ることで選考を行います。

ref. WEB面接・動画選考とは? 実施の流れ、使用ツール、マナー、注意点などを徹底解説!

私たちジョブメドレーの動画選考では、事業所があらかじめ設定した質問に対して、求職者が回答動画を提出することができます。事業所も求職者も、動画で質問・回答を送ることで、書類だけでは伝わらない雰囲気や強みを相手に伝えることができます。

f:id:medley_inc:20201126150024p:plain

WEB面接・動画選考機能のリリースのお知らせ

動画配信サービスの設計ポイント

Web アプリでこのような動画配信サービスを開発する場合、「ユーザによる動画アップロード環境」と「ユーザへの動画の配信・再生環境」を提供する必要があります。

ジョブメドレーで扱う動画は一般公開されるものではなく、公開条件も複雑です。

よって今回は、この「動画アップロード/配信環境」を自サービス内に構築する方針をとり、以下のような動画まわりの設計ポイントについて検討・技術選定を行うことにしました。

(もちろん、要件によっては YouTube や、法人向け動画配信プラットフォームを契約した方が手軽な場合もあるかと思います)

  • 動画の録画・撮影
    • サポートしたい動画ファイルのフォーマットをどうするか
    • Web アプリ内に録画機能を設けるか
  • 動画のアップロード(ストレージ)
    • 動画ファイルのバリデーションで「動画ファイルの解析」を行うか
    • 動画ファイルのアップロード先(ストレージ)をどこにするか
  • 動画のエンコード
    • 動画ファイルのエンコード形式(H.264、HLS 等)をどうするか
    • 非同期エンコードの場合、ステータス検知・エラーハンドリングをどうするか
  • 動画の配信(ダウンロード)
    • 配信形式(ダウンロード/ストリーミング)をどうするか
    • 暗号化をする場合、復号をどのように行うか
    • 動画ファイルの公開方法(アクセス制限)をどうするか
  • 動画の再生
    • Web ページ上で再生させるのか、その場合の表示・再生制御をどうするか
    • ブラウザサポートをどこまでにするか、非対応・エラー時の制御をどうするか

今回は、上記の太字で記載した 「動画のエンコード」に MediaConvert を、「動画の再生」に hls.js をそれぞれ採用しています。

各項の詳細は省きますが、全体を通して大まかに、以下のフローで「動画アップロード→エンコード(変換)→配信・再生」を実現することにしました。

  1. ブラウザから Ajax で動画を S3 へアップロードする
  2. MediaConvert が動画を HLS 形式にエンコード(変換)する
  3. ブラウザで hls.js を使い動画を CloudFront からストリーミング形式で受信、再生する

今回はこの「動画アップロード→エンコード(変換)→配信・再生」に焦点を絞り、MediaConvert と hls.js をどのように使ったのかを紹介します。

MediaConvert による HLS エンコード

AWS Elemental MediaConvert は、S3 との親和性が高いファイルベースの動画変換サービスです。自前で ffmpeg などを使って動画エンコードサーバを構築・管理することなく、スケーラブルな動画変換処理を手軽にシステムに組み込むことができます。

f:id:medley_inc:20201126150358p:plain

ref. AWS Elemental MediaConvert

料金は出力する動画の再生時間に応じた従量課金です。AWS コンソールから GUI ベースでエンコード設定を作成したり、ジョブ(エンコード処理)を登録することができます。

また、他 AWS サービス同様に API が提供されており、AWS CLI や各言語の SDK を使ってプログラムからエンコード処理を登録することができ、システム連携も容易です。

# CLI でエンコードジョブを登録する例
$ aws --endpoint-url https://abcd1234.mediaconvert.region-name-1.amazonaws.com --region region-name-1 mediaconvert create-job --cli-input-json file://~/job.json

上記 CLI コマンドで下のようなエンコード設定を記載した JSON を使いジョブを作成すると、S3 上の動画ファイルをサクッとエンコードしてくれます。ジョブはキューイングされ、内部で並列処理されるため、大量のエンコード要求にも簡単に応じることができます。

{
  ...
  "Settings": {
    "Inputs": [
      {
        # 入力元の S3 バケット上の動画ファイル key を指定
        "FileInput": "s3://testcontent/720/example_input_720p.mov"
      }
    ],
    "OutputGroups": [
      {
        "OutputGroupSettings": {
          "FileGroupSettings": {
            # 出力先の S3 バケット key を指定
            "Destination": "s3://testbucket/output"
          }
        },
        # 動画・音声のエンコード設定を指定
        # ここで品質レベル毎に振り分けた複数のファイルを出力したり
        # サムネイル jpg を作成したりすることも可能
        "Outputs": [
          {
            "VideoDescription": {},
            "AudioDescriptions": {}
          }
        ]
      }
    ]
  }
}

ref. AWSCLIを使用したAWSElemental MediaConvertCreateJobの例

エンコードが完了したジョブは、cron + SDK などで API を介して定期チェックする他に、CloudWatch Events によるイベント監視 → Lambda で処理するようなこともできます。

ref. AWS Elemental MediaConvert による CloudWatch イベント の使用

なぜ動画を再エンコードするのか

通常、ユーザからアップロードされる動画ファイルは、既に何らかのコーデックで圧縮され .mp4.mov などのコンテナフォーマットに変換されていることが殆どです。

しかし Web ページで <video> タグを使いこれら動画ファイルを再生しようとした場合、 「動画フォーマットにブラウザが非対応だと再生できない」 という環境依存問題があります。

ブラウザと動画フォーマットのサポート表

f:id:medley_inc:20201126150540p:plain

ref. HTML5 video > Browser support

この問題に対応するため、多くの動画配信サービスでは、ユーザの動画を多くの環境で再生可能な MP4 コンテナフォーマット(H.264 + AAC コーデック)などの形式へ「再エンコード」しています。

ジョブメドレーの動画選考では上記目的に加えて、動画閲覧時の回線・端末負荷を抑える 「HTTP ストリーミング形式」 で動画を配信するために、アップロードされた動画を全て HLS 形式エンコードしています。

HLS - HTTP Live Streaming 形式

HLS は HTTP Live Streaming の略で、Apple 社の開発した規格です。HTTP ベースのストリーミング通信プロトコルで、細切れにした MP4 動画ファイルを分割ダウンロードさせることで動画のストリーミング配信を実現しています。

HLS 形式にエンコードされた動画は .ts という分割されたメディアファイル群と、 .m3u8 という、メディアファイルの取得先や秒数などを記載したテキストファイルで構成されます。

.m3u8 ファイルの例(マニフェストファイル、プレイリストファイルとも)

#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:9.97663,
media-0.ts
#EXTINF:9.97663,
media-1.ts
#EXTINF:7.10710,
media-2.ts
#EXT-X-ENDLIST

ref. RFC 8216: HTTP Live Streaming

HLS は他のストリーミング形式と比較して、ライブ配信 / VOD どちらにも対応可能なこと、対応ブラウザが多いこと、専用の配信サーバを使わずに配信可能なことなどから、近年の動画配信サービスで広く利用されています。

Web エンジニアの視点から見ても、 HTTP ベースなためキャッシュや HTTPS 暗号化など、既存 Web 技術と掛け合わせることが想像しやすく、扱いやすい印象でした。

MediaConvert の HLS エンコードジョブ設定

実際にプログラムから API 経由で HLS エンコードジョブを登録する際の設定 JSON は、以下のように GUI でジョブテンプレートを作成して確認することができます。

f:id:medley_inc:20201126150659p:plain f:id:medley_inc:20201126150726p:plain f:id:medley_inc:20201126150804p:plain f:id:medley_inc:20201126150827p:plain

この「 JSON を表示」で、前述した CLI コマンド mediaconvert create-job --cli-input-json に渡せる JSON が表示されます。実装の際にはこちらを参考にしながら、ユーザーガイド を参照して利用したい機能にあわせた設定を追加していくことをおすすめします。

注意点・つまづいたポイント

  • 利用前に IAM で MediaConvert 用ロールの設定が必要です
  • AWS コンソールの Service Quotas > AWS サービス > AWS Elemental MediaConvert から確認できますが、エンコード並行処理の同時実行数上限は 20 になっています
    • AWS ルートアカウント 1 つにつき 1 サービスが割当てられるので、これを増やしたい場合は申請が必要です
  • エンコードジョブをキューイングする「キュー」を作成して、ジョブの登録時に選べるのですが、上記した「並行処理の同時実行数上限」はこの「キュー」毎に均等に振り分けられます
    • 例えば「本番キュー」と「検証キュー」の 2 つのキューを作成した場合、それぞれの並行処理の同時実行数上限は 10 ずつになるので注意してください
  • マニフェスト期間形式(Manifest duration format)に整数(INTEGER)を指定していると、iOS Safari で「動画の実際の再生時間と、再生プレイヤーのシークバーに表示される合計時間にズレが生じる」問題がありました
    • 浮動小数点(FLOATING POINT)に変更することで対応しました、マニフェストファイルに出力される各 .ts ファイルの長さが、浮動小数点 → 整数に変換され切り上げられることでズレが生じているようでした

hls.js による HLS 動画の再生制御

MediaConvert により HLS 形式にエンコードされた動画を、Web ブラウザで再生するために必要なのが、hls.js です。

f:id:medley_inc:20201126151007p:plain

ref. video-dev/hls.js

実は HLS によるストリーミング配信は、現状 Safari など限られたブラウザでしかネイティブでサポートされていません。

ref. https://caniuse.com/http-live-streaming

この HLS 動画を Safari 以外の Google Chrome や IE11 などの主要ブラウザで再生可能にするため、hls.js が利用されています。内部的には、非対応ブラウザ環境において、ブラウザの MediaSource 拡張 を使って HLS 動画を再生する仕様になっています。

Video.js との比較

似たようなライブラリに Video.js というものもあり、導入を迷ったのですが …

  • Video.js は UI もセットになった「 HLS に対応した再生プレイヤー」ライブラリ
    • HLS 対応以外にも、字幕や章分けなど機能が豊富
  • hls.js はブラウザ標準の <video> タグで HLS に対応することだけを目的にした「 HLS クライアント」ライブラリ
    • UI などはなく、動画再生プレイヤーはブラウザ標準のまま

…と、上記のように hls.js の方がシンプルにやりたいことを実現できるため、今回は hls.js を採用しました。

GitHub のスター数は先発の Video.js の方が多いのですが、hls.js も開発は活発で、日本では グノシー さん、世界的には TEDTwitter でも採用されており、十分実績があるかと思います。

hls.js による実装

基本的には README の Getting Started の通りで実装できます。一部 README のサンプルコードから抜粋して解説すると...

  var video = document.getElementById('video');
  var videoSrc = 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8';

  if (Hls.isSupported()) {
    var hls = new Hls();
    hls.loadSource(videoSrc);
    hls.attachMedia(video);
    hls.on(Hls.Events.MANIFEST_PARSED, function() {
      video.play();
    });
  }

上記 Hls.isSupported() の分岐で、HLS をネイティブサポートしていないブラウザの処理を実装しています。 本来 <video>src 属性にセットするべき .m3u8 ファイルの URL へ hls.loadSource() でアクセスさせ、クライアントから XHR リクエストを飛ばします。その後 hls.attachMedia()インスタンスを DOM 上の <video> タグに紐づけています。

  else if (video.canPlayType('application/vnd.apple.mpegurl')) {
    video.src = videoSrc;
    video.addEventListener('loadedmetadata', function() {
      video.play();
    });
  }

上記の分岐が iOS Safari など、HLS 動画をネイティブサポートしているブラウザ向けの処理です。単純に .m3u8 への URL を <video> タグの src へ付与しているだけですね。

(サンプルコードでは、マニフェストファイルのロード後に自動再生させるようになっているようです)

注意点・つまづいたポイント

  • hls.js クライアントが取得する HLS 動画ファイル群は、CORS ヘッダで GET リクエストを許可された環境に設置する必要があります
  • .m3u8 マニフェストファイルをアプリの API などから返却する場合、Content-Type を application/x-mpegURL にして渡す必要があります
  • iOS Safari などの hls.js 非対応ブラウザ向けの実装を意識する必要があります
    • hls.js による制御が複雑になるケースでは、同じような制御を hls.js 非対応ブラウザ向けに実装できるか?をイメージできないと手戻りが発生しそうです

この他、フロントエンドでは <video> タグのブラウザ毎の挙動や、表示の違いに時間がかかりました。(ある程度予想はしていましたが、やはりメディアの取り扱いは難しい…)

hls.js 自体は導入も手軽で、サクッと HLS 動画のマルチブラウザ対応が実現でき、とても使いやすかったです。@types も存在するので、TypeScript 環境でも難なく実装できました。

SSR や HLS + AES-128 の再生にも対応しているので、興味のある方は一度 公式ドキュメント を確認してみてください。

おわりに

従来、動画配信サービスを構築する場合、ffmpeg を載せたエンコードサーバや、ストリーミング配信サーバを別建てして、負荷に応じてスケールさせて…のような設計が必要だったかと思います。

今回、MediaConvert をはじめとした AWS サービスと hls.js を利用することで、手軽に、スケーラブルな動画エンコード/HTTP ストリーミング配信環境を構築することができました。

ジョブメドレーの動画選考はまだリリースしたばかりですので、今後反響を見ながら、さらなる改善を重ねていけたらと思います。最後までお読みいただきありがとうございました。

www.medley.jp