シンクロ・フード エンジニアブログ

飲食店ドットコムを運営する、株式会社シンクロ・フードの技術ブログです

担当サービス体験会をやってみた

こんにちは。開発部の飛澤です。
求人飲食店ドットコム関連サービスの開発・運用保守を担当している開発チームのチームリーダーをやってます。

普段は飲食店ドットコム求人飲食店ドットコムといった弊社の「飲食店と求職者のマッチングサービス」(※以下求人事業)に関わるサービスや業務システムの開発に携わっています。

今回はチーム活動の一環として、担当サービス体験会を実施したのでその感想などをお伝えしたいと思います。

担当サービスについて

弊社の飲食店向けサービス「飲食店ドットコム」は、飲食店の出店・開業から運営、退店までをサポートする経営・運営支援プラットフォームです。私たちの開発チームは、「飲食店運営」における重要な項目の1つである人材確保にフォーカスした求人サービスの開発を担当しています。
飲食店のお客様が「飲食店ドットコム」からお店の情報や求める人材についての求人情報を作成し、その求人情報は「求人飲食店ドットコム」に掲載されます。「求人飲食店ドットコム」に掲載された求人情報を求職者が閲覧し、希望に沿った求人に応募するといった飲食業界に特化した求人・求職のサービスです。
求人飲食店ドットコムに掲載の個人店の9割が弊社サービスのみで求人募集をされているため、求職者は個性を持った飲食店を探すことができます。また、飲食店専門の求人媒体であることから利用されている求職者は飲食経験者も多く、即戦力となる人材を効果的に採用できることが弊社の求人サービスの特徴です。

私たち求人サービス開発チームではこの求人事業領域を大きく3つの区分に分けて日々開発業務を行っています。
1つ目は飲食店が利用する飲食店ドットコム、2つ目は求職者が利用する求人飲食店ドットコム、3つ目は弊社社員が顧客・契約情報等を管理するために使用する業務システムです。
それぞれ個別のシステムとして動いており、それらの開発・運用保守を行っています。

背景・目的

今回の体験会を実施したのはSlackでのチームメンバーのつぶやきがきっかけでした。

「単語は聞いたことあるけど、ドッグフーディングってどんな効果があるのだろう?」と気になって調べてみると、「自社製品をユーザとして日常的に利用することで改善点を見つけることができる」ほかに「プロダクトを実体験を通して学ぶことができる」といった効果なども見込めるとのこと。導入ハードルもそこまで高くなく、今のチームの状況的にやってみる価値はあると感じました。

昨年度から今年度にかけて、求人サービス開発チームは新しいメンバーを複数人迎えることができました。
新メンバーの方には勉強会や業務を通じて、サービスが提供している機能や仕組みについて理解を深められるようにしていますが、どうしても 陰が薄い 触る機会の少ない機能についてカバーしきれないことも少なくありません。
また、求人サービス全体を通して概要は知っていても具体的な知識として定着できていないため、パターン漏れによるバグを埋め込んでしまったり、作業後半になって不足部分が発覚し予定よりもリリースが遅れてしまうという課題もありました。

今回はドッグフーディングを体験しつつ、各メンバーが知らなかった機能・仕様について1つでも多く学びながら共有することでチームの知見を増やすことを目的として実施することにしました。

また弊社開発部は現在フルリモート勤務が推奨されており、日本であればどこでも勤務OKとなっています。
チームメンバーには関西在住の方もおり、直接顔を合わせる機会も少ないためこういったオフライン活動の中でコミュニケーションが活発に取れれば良いなと考えました。

やったこと

求人サービスの全体の流れの復習

まずはシンクロ・フードの求人サービスを体験するにあたって、全体的な流れ・概要をチームで認識合わせを行いました。
以下、行った説明内容を抜粋

求人募集の掲載手続き〜掲載開始まで

  1. シンクロ・フードが飲食店(法人or個人事業主)に対して、求人飲食店ドットコムへ求人募集を掲載しないか営業を行う
    1. 飲食店からシンクロ・フードに対して問い合わせからどのようなプランで掲載するか営業担当とやりとりを行うケースもある
  2. シンクロ・フードと飲食店の間で契約を結ぶ
    1. ※基本的に契約の手続きはシンクロ・フードの営業担当が主となって進める。
  3. 掲載する求人原稿を作成する
    1. 飲食店のお客様が飲食店ドットコムで作成
    2. シンクロ・フードの営業担当が作成
  4. 掲載スタート

掲載された求人募集へ応募~面接まで

  1. 求職者が求人飲食店ドットコムに会員登録する
  2. 会員となった求職者は求人飲食店ドットコムに求人募集を掲載している飲食店に応募する
    1. ※先に会員登録が済んでいない場合は、会員登録とともに応募することも可能
  3. 応募先の飲食店と応募した求職者の間でメッセージのやりとりを行い、面接日程の調整を行う
    1. ※飲食店と求職者のやりとりは「飲食店ドットコム」「求人飲食店ドットコム」のそれぞれに用意されたメッセージ機能を通して行われる
    2. ※シンクロ・フードが提供できるのはここまで。この後の面接・採用のフォローなどはサービスとしては提供してない

番外編: スカウト

  1. 求人募集を掲載している飲食店が「スカウト」オプションサービスを購入する
    1. ※購入元は「飲食店ドットコム」内のページまたは営業担当を通じて契約を新たに結ぶ
  2. オプションを購入した飲食店は求職者の一覧から求める人材を探し、スカウトを送信することができる
    1. ※求職者がスカウトを受け取る設定にしていることが前提となる
  3. スカウトした求職者から反応があれば、応募同様メッセージ機能でやり取りを行い、面接日程を調整する

チーム分けと触って欲しい範囲についての決定

提供しているサービスの根幹部分の流れについて理解を深めるため、以下のようにチーム分けと通って欲しいシナリオを決めました。

飲食店チーム

  • 対象: 飲食店ドットコム
  • シナリオ
    • 会員登録
    • シンクロ・フードへの問い合わせ
    • 求人原稿の作成
    • シンクロ・フードと契約を結ぶ
      • シンクロ・フード営業担当が行う業務システムの操作は別途実施する。今回は意識しない。
    • 応募した求職者者とのやりとり
    • 面接日時の設定
    • (できたら)スカウトしてみる

求職者チーム

  • 対象: 求人飲食店ドットコム
  • シナリオ
    • 会員登録
    • プロフィールの登録
    • 飲食店チームが作成した求人募集に応募
    • 応募先の飲食店とやりとり

両サービスともにPC版とスマートフォン版があるため、それぞれのチームでPC版・スマートフォン版両方を操作してユーザー目線でサービスを利用して意見を出し合いました。

今回はオフライン参加が8名、オンライン参加が4名でした。
オフライン参加の方は会議室に集まり、オンライン参加者はGoogleMeetで参加しました。

サービスの体験ステップに入ってからオンライン参加者は個別の通話で行っていたため、リアルタイムに反応や意見を受け取りづらかったり、置いてけぼりになってしまった面が今後活動を続けるとしたときの課題だと思いました。オフライン側でわちゃわちゃしているとオンライン側の雑音になってしまうし、かといってオフライン側をマイクミュートしてしまうのもどうかなぁ…と今でも悩んでます。

こういったチーム活動は多くないのでうまく進むか心配でしたが、それぞれのチームが様々な意見を出しながら盛り上がっていました。新しいメンバーにとって初めて見る機能について質問があったり、昔からある仕様に戸惑う場面もありました。
特に飲食店側は求人掲載までの手順が多かったり初見だと分かりづらい部分もあるため、「原稿作成の手間が多い」「この項目の設定の仕方、お客さんからしたら分かりづらくない?」みたいな声がありました。

取り組みを通じて出た意見・感想

今回の活動を通じて参加した方から頂いた感想や意見を抜粋してお伝えします。

  • 求人サービスは成熟しきっているイメージだったが、「これで大丈夫だろうか?」という部分もあり、まだまだ改善の余地がたくさんあるんだなと思った
  • 入社して間もないため、飲食店側・求職者側の挙動を見ながら一連の流れを理解することができたので非常に良い取り組みだと思った
  • UI面で気になる箇所も見つかったので、企画チームに提案したい
  • 普段触ることがほとんどないところをじっくり触れて、とても勉強になった
  • 飲食店側をしっかり見るのは数年ぶりだったが、ユーザーの使い勝手をより意識させられた会だったと思う
  • 時間の都合でちゃんと触れない部分もあったので、はやめに次の開催を希望する

それぞれが今まで知らなかった機能や仕様について学び、活発なコミュニケーションが取れた活動になったと思います。
一度プログラムから離れてユーザーとしてサービスと向き合い、疑問や改善点を多く見つけることでチームとしてもっと求人サービスを成長させられることを認識できたと思います。
今回は求人募集の掲載から応募、面接日時決定までのやりとりまでを行いましたが、次回以降では今回触れなかったスカウト機能を使ってみたり利用者のペルソナを設定して操作してみるなど操作範囲ややり方を検討していきたいと思います。
また、ユーザー視点でサービスを触っていきましたが、今度はディレクター視点でサービスを見て機能やUIの現状の仕様の理由を考えることで今後のサービス開発に活かせるんじゃないかと考えています。

まとめ

求人サービス開発チームで「飲食店ドットコム」と「求人飲食店ドットコム」の担当サービス体験会を実施しました。
日々の開発業務の中ではサービス全体を通して使用感を確かめる機会が多くないので、担当領域の知識を深めるとともに自分たちの開発しているサービスと向き合うよい機会になったと思います。
今回出た意見や疑問についてはしっかりまとめて企画チームに提案してサービスをより良くしていけたらと考えています。
次回を希望される声もありますので、やり方や進め方を模索しながらチーム活動の一環として定着していきたいと思います。

突撃!隣のキーボード 2024年

こんにちは。 開発部の四之宮です。

エンジニアブログ界で定番となっているであろう「突撃!隣のキーボード」ですが、弊社では記事にしたことがなかったので、この度記事にしてみました。

弊社開発部ではフルリモート制度が導入されており、日本国内であればどこからでもリモートワークで勤務可能となっています。
そのため、自宅の開発環境に投資する社員も増えていますが、まずはキーボードに投資する方も多いのではないでしょうか?

そこで、エンジニアを対象にアンケートの実施と、数名にこだわりについて語っていただきました。

アンケート結果

弊社のエンジニアを対象に、使っているキーボードについてのアンケートを実施しました。
結果は下記の通りになりました。

使用しているキーボード

使用しているキーボードの種類について、ノートPCのもの(外部キーボードを使用していない)、市販、自作 から選択してもらったところ、下記のようになりました。

使用しているキーボードの割合

あまり偏りがない結果でした。
筆者としては、ノートPCのものをそのまま使用している人が多く、少しびっくりしました。 (大部分のエンジニアは MacBook Pro を使用しています)

キー配列

キー配列について、JIS、US、その他 から選択してもらったところ、下記のようになりました。
キーマップを変更している場合は、近いものがあればそれを選択してもらっています。

キー配列の割合

こちらは、JIS と US でほぼ半々という結果になりました。

外部キーボードの形状

外部キーボードを使用している方を対象に、形状について、スタンダード、分割、その他 から選択してもらったところ、下記のようになりました。

外部キーボードの形状の割合

スタンダードが少し多いですが、分割を使っている人もかなり多いといえるのではないでしょうか?

外部キーボードの種類

外部キーボードを使用している人に、どんなキーボードを使っているのかを聞いてみました。
複数名いる場合のみ、括弧内に人数を記載しています。
市販では REALFORCE と keychron が、自作では Corne が人気でした。

市販

  • keychron(6名)
  • REALFORCE(4名)
  • HHKB(2名)
  • Magic Keyboard
  • Majestouch Xacro M10SP
  • Majestouch 2
  • Logicool MX Keys

自作

  • Corne(5名)
  • Helix
  • Lily58
  • NIU mini
  • TheEndpoint
  • Killer Whale

使用しているキーボードについて

ここからは使用しているキーボードについて、何人かにより詳しく語っていただいたものになります。
奇しくも自作勢だけになってしまいましたが、個性的なものも多く読み応えがあるものになっていると思います。

大久保

使用しているキーボード

Corne V4 Cherry

そのキーボードを選んだ理由

もともとは、Ergodox EZ→Iris→Corne V1 という感じでキーボードを移り変わり、特に不満もなく Conre V1 を使っていたのですが、Corne V4 が発売された、という話を聞いてなんとなく購入し、そこから気に入って使っています。

推しポイント

Conre V4 は、自作キーボードの終着地点、と言っても過言ではないキーボードだと思っています。
僕はかなり初期の頃から自作キーボードを触っていると思うのですが、Conre V4 を触って、「遂にここまで来たか…」と感動してしまいました。
自作キーボードに興味がある人に対して、一切の迷いなくおすすめできるキーボードだと思います。

半田付けがいらないという点も初心者向きですが、なにより「ケース」が良いです。
自作キーボードは、ケースのクオリティはあまり求められていない(というか自分も求めていなかった)のですが、Corne V4 はケースの密閉度が非常に高く、それによる「高級キーボード感」が、自作キーボードっぽさを打ち消してくれます。
持ち運びもいままではしていなかったのですが、このケースによる安心感があるため、持ち運びもしています。

滑り止めのポッチをはめる部分が凹んでいるのも、非常に良いです。なんならここが一番良いです。

V4 と V1 の違い

ケースの完成度の違いがわかるんじゃないかと思います…!

購入検討者へメッセージ

自作キーボードに対して、少しでも興味があったら即買った方が良いです。2台買った方が良いです。
というか、これはもはや自作キーボードではなく、組み立て式市販キーボードです。
今は品切れ中なので、発売したらすぐ買いましょう。

四之宮

使用しているキーボード

Helix rev3 LP と Corne V4 Chocolate

そのキーボードを選んだ理由

自作キーボードに沼ったのは、首や肩まわりに激痛がでるようになったのがきっかけです。
ペインクリニックに通いブロック注射をするほどひどい時期がありました。
分割キーボードにしてから痛みは徐々に改善していき、今では全く病院に行く必要がなくなりました。

最初は市販の分割キーボードから入ったのですが、「親指に enter キーある方がよくない?」とか「親指で押すキーもっと欲しい」などと思い始め、自作キーボードに入っていきました。
その後、いくつか試した結果、Helix の配列やキー数が自分にはあっていたようで数年間使っています。

そんな中、会社の先輩(上記の大久保さん)が Corne V4 を大絶賛していたので、興味を持ち購入しました。
数字行無しキーボードは初だったり、キーの数が変わったり、と大変な部分も多いですが徐々に慣れてきています。こちらもいいキーボードでした。
現状はまだ Helix の方が自分には合っているなと思っているのですが、しばらくは使い込んでみようと思っています。

推しポイント

キースイッチに kailh×COSMOX Wind Engine を採用しているため、ロープロファイルでありながら打鍵感がいいです。
Lofree Ghost を使用していたのですが、同じような打鍵感でより軽いものがあると知りこちらを愛用しています。
昔からキーの高さはロープロファイルが好きだけど打鍵感が好きではないため、ロープロファイルの使用を諦めていたので、Lofree FLOW の登場はとてもうれしかったです。
更に、キーキャップに静音化リングをつけたり、フォームを使ったり、マットを敷いたり、とロープロファイルながらも打鍵感や打鍵音をよくしようとしています。

あとは、qmk の LT 系の処理には満足できなかったので、process_record_user を使って、単推し/ 複合押し / 長押し の動きを自分好みにしました。
また、夏も近いので、Helix は涼し気なキーキャップにしてみました。

購入検討者へメッセージ

自作キーボードは沼ると散財するので注意が必要です。
Helix に落ち着くまでは、自作キーボードを結構転々としていたので結構散財しました。
キーボードに限った話しではありませんが、自分で設計しない限り100点のものが見つかることはほぼない、80点でほぼ最高点、というのを心がけるのが重要だと思います。

大庭

使用しているキーボード

Corneにアルミニウムケースを付けています!

普段は、キーボードの間にMagic Trackpadを置いています。
また、分離型のパームレストを使用しています。

そのキーボードを選んだ理由

Macのキーボードをずっと使っていたのですが、数字キーを押す際に手が大きく動くためホームポジションを見失ってしまうところと、「B」キーを押す際に左右どちらの手で押すか毎回変わるところが嫌で、縦3キーのcolumn staggered配列のCorneに辿り着きました。
この配列だと、どのキーも対応する指だけ動かせば良く、全てのキーがいずれか1つの指と対応するので、非常に気に入っています。

アルミニウムケースをつけている理由は特にないです。強いて言うなら、防御力が高くかっこいいからです。

推しポイント

キーの打鍵感には非常にこだわっています。
具体的には、「なめらかさ」と「ぐらつき」と「軽さ」です。
長い間、NovelKeys Box Creamのスプリングを35gに換えたものを使用していたのですが、HHKB Studioのキースイッチはさらに「静音性」があるので気に入っています。

購入検討者へメッセージ

最初から今の構成にたどり着いたように書いていますが、キーボードは3代目でキースイッチはそこそこの回数入れ替えています。
改善の日々で楽しいですが、お金が飛んでいきます。ただ、キーボードはずっと触っているものなので、小さな改善を大きく実感することができます。

熊谷

使用しているキーボード

Killer Whale

そのキーボードを選んだ理由

初めて自作キーボードを使い始めた理由は、一般的なキーボードの運指距離に不満があったからです。最初は右も左も分からなかったので使用者が身近にいたIrisを購入しました。親指側のキーが若干遠くて押しづらいこと以外には大きな不満も無かったのでつい最近までずっと使い続けていました。

tech.synchro-food.co.jp

今回紹介しているKiller Whaleに乗り換えたのは、とある競走馬擬人化スマホゲームがきっかけでした。そのゲーム内のキャラクターがトラックボール付きの分割キーボードを使っているのを見て、「マウス操作もキーボード上で完結したら全く手を動かさなくていいじゃん!」という天啓を得て、なんやかんやあってこのモデルを選びました。

キーの配置が直線的なIrisと違い弧を描いているので最初は小指付近のタイプミスが多かったですが、慣れると前よりも快適になったので概ね満足しています。ただトラックボールは精度的に完全にマウスの代替とはならず、結局普通のマウスも併用しています。(とはいえ役立つ場面もあるので利便性は確実に向上しているのを実感しています)

推しポイント

トラックボール以外にもホイール、ジョイスティック、トグルスイッチなどギミック増々で気分がアガります。 本当は打鍵感とか静音化とか突き詰めたい気持ちはあるのですが、手間や費用を考えるとなかなか行動に移せず‥‥(写真を見るとわかりますが、キーキャップの刻印や配色も適当です)

あとキーマッピングは細かい調整を重ねて日々改善を続けています。 例えばショートカットキーで多用するCtrlキーは組み合わせるキーとの距離を考慮して小指側と親指側の2つを配置したり、マウス操作時にEnterキーが押せると便利なシーンが多いのでメイン使いの右手側とは別に左手の別レイヤーにもEnterキーを用意したりなどです。

購入検討者へメッセージ

自作キーボードは確実にQoLを上げてくれますが、二度と普通のキーボードは使えない体になります。 今は快適でも将来もし使用中のキーボードが故障した時、その時も同じモデル(もしくは代替となり得るもの)が果たして入手可能なのかという不安と日々闘い続けることになります。

覚悟が出来た方から扉を開け次のステージへお進みください。

大津

使用しているキーボード

トラックボールを「人差し指側」または「親指側」に設置できる分割キーボードです
しかも左右1個ずつ設置可能!

そのキーボードを選んだ理由

マウスとキーボードの移動が面倒に感じ始めたので、トラックボール付きのキーボードを探していました。
そんな中で、左手にも右手にもトラックボールを設置できて、人差し指側と親指側で設置場所を選べるという選択肢の多さに惚れてTheEndpointを選びました。
実際、購入当初は左手人差し指側(白い端子が少し見えている部分)に設置していましたが、キー配置や操作性を考えているうちに、右手親指側に移動させました。
(実は追加のトラックボールユニットも購入したのですが、付属のネジを失くしてしまったので左手側にはまだつけられていません)

推しポイント

いわゆる50%キーボードですが、親指キーや人差し指キーが豊富なので、配列を色々試す事ができます。(色々試した結果不要になったキーはスイッチごと外しています)
また、トラックボールを使う際に不安になるのが親指の疲労ですが、このキーボードはトラックボールの設置場所が豊富なので、場所を変えたり左右で交互に使ったりなど自分にあった対策ができます。

購入検討者へメッセージ

「50%キーボードに挑戦してみたいけどキー数が少ないと不安」であったり、「トラックボール付きキーボードが欲しいけどトラックボールの場所がしっくりこなかったらどうしよう」という思いを抱えている方はぜひ購入してみてください!

まとめ

いかがだったでしょうか?
弊社エンジニアのキーボードについて、詳しい状況をお届けできたのではないでしょうか?
あまり偏りがなく、多種多様な結果になっているのかなと思います。
自作勢も結構な数がいるということで、新入社員や中途社員の方も、毎年何人かは自作に足を踏み入れています。

また、以前デスク環境に関する記事を公開していますので、キーボードだけではなくもう少し広い環境に興味がある場合は、下記からご確認いただければと思います。
tech.synchro-food.co.jp

Railsアプリケーションで使われているモデルをRSpecを使って自動的に機能ごと一覧化する

こんにちは。開発部の竹内です。
飲食店ドットコムモビマルといったサービスで Ruby on Rails での開発に携わっています。

Railsアプリケーションで使われているモデルをRSpecを使って自動的に機能ごと一覧化する対応を行いましたので、それについてお話ししたいと思います。

背景

コードを修正するにあたり、既存の機能に影響がないか・整合性があるかを調べるのは重要です。ですが私が所属する会員企画開発チームでは影響範囲の確認が漏れてバグや仕様上の不整合が発生することが多く、課題となっていました。
そのため機能ごとにどのモデルが使用されているかが分かるドキュメントを整備し、影響範囲の調査やサービスの理解に役立てようという提案がありました。ドキュメントは人力で作成していたのですが、作成に時間がかかりメンテナンスも大変であることから、自動的に出力する仕組みを作りたいと思いました。弊社には業務時間の10%でサービス改善のためにエンジニアの好きなことをできる制度があるので利用することにしました。

方針

Ruby の言語の特性上、静的に解析を行うのは難しそうです。
そのため動的に情報を得る方針を取りました。
弊社が運営しているモビマルでは Request Spec がほぼ全機能で書かれているのでこれを利用できないかと考えました。
カバレッジツールやプロファイラでも使用されている TracePoint を用いればメソッド呼び出しをフックできるので、これを記録すればどのモデルのメソッドが呼ばれているかが分かります。
また、どの機能が実行されているかは Active Support Instrumentation によって分かるためこれも利用することにしました。

実装

まず、TracePoint を使ってメソッドが呼ばれたクラスを記録するクラスを定義します。

# TracePoint を使ってメソッド呼び出すを記録するためのクラス
class Tracer
  attr_reader :used_classes

  # @param [Proc] filter_proc
  #  TracePoint で記録するメソッド呼び出しを絞り込むためのブロック
  #  引数に TracePoint を受け取り、true を返した場合のみ記録する
  def initialize(&filter_proc)
    @used_classes = Set.new
    @filter_proc = filter_proc || proc { true }
  end

  def trace(&block)
    tr = TracePoint.new(:call, :c_call) do |tp|
      next unless @filter_proc.call(tp)

      @used_classes << tp.self.class
      @used_classes << tp.self if tp.self.is_a?(Class) # クラスメソッドが呼ばれた場合
    rescue NoMethodError, ArgumentError # rubocop:disable Lint/SuppressedException
      # tp.self.class 取得時エラーになることがあるので、それは無視する
    end
    tr.enable(&block)
    self
  end
end

Tracer#trace をブロック付きで呼び出すと、ブロック中で呼ばれたメソッドのレシーバのクラス、またはレシーバがクラスの場合(クラスメソッドが呼ばれた場合)はレシーバ自身を @used_classes に記録します。
コンストラクタにブロックを渡すことができ、それによりクラスを記録するかどうかを判定します。こちらは後ほど説明します。

これを使ってアクションごとに使用されているモデルを取得します。

# frozen_string_literal: true

require 'csv'

MODEL_ANALYZER_TMP_FILE = 'coverage/model_analyzer_tmp.txt'
MODEL_ANALYZER_RESULT_FILE = 'coverage/model_analyzer.tsv'
MODEL_ANALYZER_EXCLUDED_CLASS_NAMES = Set.new([
  'ApplicationRecord',
])

RSpec.configure do |config|
  # テストスイート実行開始時、一時ファイルを削除する。存在しなければ何もしない
  config.before(:suite) do
    FileUtils.rm_f(MODEL_ANALYZER_TMP_FILE)
  end

  config.around do |ex|
    # controller の action が呼ばれた時にその controller と action を記録する
    controller = nil
    action = nil
    subscriber = ActiveSupport::Notifications.subscribe('process_action.action_controller') do |_, _, _, _, payload|
      controller = payload[:controller]
      action = payload[:action]
    end
    tracer = Tracer.new { RequestDurationMiddleware.in_request? }
    tracer.trace { ex.run }
    ActiveSupport::Notifications.unsubscribe(subscriber)

    # 使われたクラスを行ごとの JSON 形式で 一時ファイルに出力する
    # ディレクトリが存在しない場合は作成する
    FileUtils.mkdir_p(File.dirname(MODEL_ANALYZER_TMP_FILE))
    File.open(MODEL_ANALYZER_TMP_FILE, 'a') do |f|
      break unless controller && action

      f.puts JSON.generate({
        controller: controller,
        action: action,
        classes: tracer.used_classes.map { |klass|
          next if klass.name.nil? || MODEL_ANALYZER_EXCLUDED_CLASS_NAMES.include?(klass.name)

          source_location =
            begin
              Object.const_source_location(klass.name).first
            rescue NameError
              nil
            end

          # models 下で定義されたものが対象
          next unless source_location && source_location.start_with?('/mobimaru/rails/app/models/')

          klass.name
        }.compact,
      })
    end
  end
end

RSpec の config.around で、各ケース実行時に処理を追加します。
Active Support Instrumentation によりテスト対象のコントローラとアクションを取得します。
Tracer#trace に渡したブロック内でケースを実行し、使用されたクラスを記録します。
Tracer.new に渡しているブロック内の RequestDurationMiddleware.in_request? は実行タイミングがリクエスト中かを判定します。詳しくは後述します。
ケース実行後は、coverage/model_analyzer_tmp.txt にコントローラ・アクション・使用クラスの情報を行ごとの JSON 形式で追加します。この時 /mobimaru/rails/app/models/ 下以外のクラスやトップの ApplicationRecord は除外しています。

RequestDurationMiddleware の定義は以下となります。

# frozen_string_literal: true

# リクエスト中かを保持するためのミドルウェア
class RequestDurationMiddleware
  class << self
    def in_request?
      Thread.current[:request_duration_middleware_in_request]
    end
  end

  def initialize(app)
    @app = app
  end

  def call(env)
    self.in_request = true
    @app.call(env)
  ensure
    self.in_request = false
  end

  def in_request=(value)
    Thread.current[:request_duration_middleware_in_request] = value
  end
end

Rails.application.config.middleware.use RequestDurationMiddleware

Rack ミドルウェアでリクエスト中かを判定します。
RSpec のケース実行中には準備段階でのデータ作成などがあり、機能内で使われないモデルのメソッドが呼ばれることがあるので、除外するために行なっています。

全てのケース実行後は結果をファイルに出力します。

# ... 省略

RSpec.configure do |config|
  # ... 省略

  # テストスイート終了時、request spec で使用されたモデル一覧を集計する
  config.after(:suite) do
    controller_and_actions = {}

    File.open(MODEL_ANALYZER_TMP_FILE) do |f|
      f.each_line do |line|
        used = JSON.parse(line)
        key = [used['controller'], used['action']]
        (controller_and_actions[key] ||= Set.new).merge(used['classes'])
      end
    end

    # controller_and_actions から、| controller | action | used_classes(カンマ区切り) | の形式のTSVを作成する
    # controller と action の昇順でソートする
    File.open(MODEL_ANALYZER_RESULT_FILE, 'w') do |f|
      # ヘッダを出力
      f.puts(%w[controller action used_classes].to_csv(col_sep: "\t"))

      controller_and_actions.sort_by(&:first).each do |(controller, action), used_classes|
        f.puts([controller, action, used_classes.to_a.sort.join(',')].to_csv(col_sep: "\t"))
      end
    end
  end
end

一時ファイルに保存した内容を読み取った後、コントローラ・アクションごとに集計し、TSVで出力します。

実行

spec_helper.rbrequest_spec_model_analyzer_helper.rb を require し、RSpec を実行すると TSV が出力されます(一部抜粋)。

controller    action  used_classes
# ...
Home::MagazinesController   index   Contact,Magazine,MagazineCategory
# ...
Home::NewsController    show    Contact,News,NewsTarget,NewsTargetManage
# ...

まとめ

Request Spec を用い、機能ごとに使われているモデルを一覧化することができました。
これを使ってチーム内でドキュメントを作成するのに役立てています。
普段の Web 開発では馴染みの薄い TracePoint や Active Support Instrumentation を使うことができ、作っていて面白い取り組みになりました。
シンクロ・フードでは他にも自動化など改善の余地がある箇所がたくさんあるので、関わってみたい!という方をお待ちしています。

Aurora MySQL v3 (MySQL 8.0 互換) へのアップグレード対応

はじめまして、SRE チームの村山です。

今回、弊社の主要サービスで使用している Aurora MySQL のアップグレードを行いましたので、対応内容や得られた知見についてご紹介したいと思います。

背景

弊社で使用している Aurora は、2022 年 10 月に Aurora MySQL v1 (MySQL 5.6 互換) から Aurora MySQL v2 (MySQL 5.7 互換) へとアップグレードしております。その際の対応については、以下の記事をご覧ください。

tech.synchro-food.co.jp

その後も v2 を使用していましたが、2024 年 10 月 31 日 をもって v2 の標準サポートが終了になるため、Aurora MySQL v3 (MySQL 8.0 互換) へのアップグレードが必要になりました。

docs.aws.amazon.com

変更点の影響調査

アップグレードに向けて、まずはドキュメントを中心に変更点と影響範囲の調査を進めました。 これは SRE チームではなく、Web サービスの基盤部分を横断的に見るアプリケーション基盤チームを中心に実施して頂きました。

この中で対応が必要だと判断し、実施した主な内容を以下に記載します。

デフォルトの COLLATION が utf8mb4_0900_ai_ci になる

MySQL 8.0 では、utf8mb4 のデフォルトの COLLATION が utf8mb4_general_ci から utf8mb4_0900_ai_ci に変更になりました。

Each character set has a default collation. For example, the default collations for utf8mb4 and latin1 are utf8mb4_0900_ai_ci and latin1_swedish_ci, respectively.

これまで DB のテーブル定義 (マイグレーションファイル) には明示的に COLLATION を設定していなかったので、今後新規にテーブルを作成する際は COLLATE=utf8mb4_general_ci を設定するように開発ルールを変更しました。
DB アップグレードのタイミングに合わせ、既存のテーブルも COLLATE を設定するように変更しています。

FLOAT, DOUBLE, DECIMAL タイプのカラムに対して UNSIGNED 属性が非推奨になる

MySQL 8.0.17 で非推奨になったため、ALTER TABLE で対象のカラムから UNSIGNED 属性を削除するようにしました。対象のカラムが多いため、稼働中の DB には ALTER TABLE を適用せず、後述するクローンの DB にだけ適用するようにしています。詳しくは「アップグレード方法」の項に記載します。

As of MySQL 8.0.17, the UNSIGNED attribute is deprecated for columns of type FLOAT, DOUBLE, and DECIMAL (and any synonyms);

SQL_CALC_FOUND_ROWS, FOUND_ROWS() が非推奨になる

MySQL 8.0.17 で非推奨になったため、使用箇所を修正しました。アップグレード前に修正できる内容でしたので、事前に対応しています。

The SQL_CALC_FOUND_ROWS query modifier and accompanying FOUND_ROWS() function are deprecated as of MySQL 8.0.17;

utf8mb3 が非推奨になる

MySQL 8.0 で非推奨になったため、ALTER TABLE で対象のテーブル・カラムを utf8mb4 に変換しました。こちらも後述するクローンの DB にだけ適用するようにしています。

The utf8mb3 character set is deprecated. utf8mb3 remains supported for the lifetimes of the MySQL 8.0.x and following LTS release series, as well as in MySQL 8.0.

予約語利用箇所の対処

MySQL 8.0 より RANK や LEAD の単語が予約語に追加されました。

Reserved words are permitted as identifiers if you quote them as described in Section 11.2, “Schema Object Names”:

既存のテーブルに同名のカラムがあることを下記クエリで確認しましたが、アプリのライブラリにより引用符で囲む処理をするため、対応は不要の想定でした。

select table_schema, table_name, column_name 
from information_schema.columns 
where table_schema not in ('information_schema', 'mysql', 'sys') 
  and column_name in (select WORD from information_schema.KEYWORDS where reserved = 1)
and TABLE_NAME NOT IN ('ar_internal_metadata');

しかし、後の段階で一部のアプリではその処理がされず、エラーになることが判明したため、最終的にはカラム名を変更する対応をとりました。

GROUP BY 句での ASC/DESC の廃止

MySQL 8.0 より GROUP BY 句で ASC/DESC をつけることができなくなりました。後述する「テスト」の段階で、各サービスの開発担当者にて使用箇所を修正して頂きました。(昔からある一部の機能で利用されていました)

The deprecated ASC or DESC qualifiers for GROUP BY clauses are removed. Queries that previously relied on GROUP BY sorting may produce results that differ from previous MySQL versions. To produce a given sort order, provide an ORDER BY clause.

上記のほか、アップグレードにより変更されたパラメータグループの設定値の調査や検討なども行いました。

アップグレード方法

アップグレードの方法は、前回のメジャーバージョンアップ時と同じく、binlog レプリケーションを使った Blue-Green Deployment を採用しました。採用の理由は、実績があり手順が確立できていること、リリース当日のメンテナンス作業の時間を短くできること、切り戻しを容易にできることです。

具体的な手順は以下の記事を参考に検討しました。

aws.amazon.com

最初に検討した手順の大まかな流れは以下のとおりです。
※ テストで不備がみつかり修正したため、実際の手順とは異なります。詳しくは「アップグレード手順のテスト」の項に記載します

検討時のアップグレード手順

  1. 既存の Aurora MySQL v2 からクローンを作成する
  2. クローンを v3 へインプレースアップグレードする
  3. v2 から v3 へレプリケーションする
  4. v3 へ ALTER TABLE を実行する
  5. v2 から v3 へのレプリケーションを停止する
  6. アプリケーションの接続先を v3 へ切り替える
  7. v3 から v2 へレプリケーションする

手順 1 ~ 4 は事前に実施し、5 ~ 7 をリリース当日のメンテナンス作業時に実施します。そうすることで、リリース当日の作業時間を短くすることができます。
「変更点の影響調査」の項に記載した ALTER TABLE は、手順 4 のタイミングで v3 にだけ実行します。この時点でアプリケーションは v2 へ接続しているため、ALTER TABLE を安全に適用できます。
手順 7 は、アップグレード後に v2 へ切り戻せるようにするため実施します。

なお、RDS がサポートする Blue-Green Deployment の方法も候補にありましたが、切り戻しの容易さから前述の方法をとることにしました。

docs.aws.amazon.com

テスト

開発環境にてアップグレード手順のテストと、アップグレード後のアプリケーションのテストを行いました。

アップグレード手順のテスト

「アップグレード方法」の項で述べた手順で進めましたが、手順 4 の ALTER TABLE 実行後、v2 から v3 へのレプリケーションでエラーが発生してしまいました。

Column 17 of table 'テーブル名' cannot be converted from type 'varchar(765(bytes))' to type 'varchar(1020(bytes) utf8mb4)'

utf8mb4 へ変換する ALTER TABLE を v3 側だけに実行していたため、データ型の不一致が原因のようです。これを許容してレプリケーションできるようにするため、v3 側のパラメータグループで replica_type_conversions を設定するようにしました。
今回は source (v2) よりも target (v3) 側の型が大きいため、非不可逆変換を許可する ALL_NON_LOSSY の値を設定します。設定値の詳細は以下をご参照ください。

設定後、v2 から v3 へのレプリケーションがエラーにならないことを確認できました。
ただここで、手順 7 の v3 から v2 へのレプリケーションにおいても同じ問題に遭遇することが予想できました。型の大小関係が逆になるため、不可逆変換を許可する ALL_LOSSY を設定する必要があります。しかし、値が切り捨てられデータの不整合が生じる可能性があるため、他の方法を検討することにしました。

考えた方法は、v2 切り戻し用のクラスターを別に作成し、クラスター 3 台でレプリケーションする構成です。

実際のアップグレード手順

具体的な手順は以下になります。

  1. 既存の Aurora MySQL v2 からクローンを 2 つ作成する (v3 用と切り戻し用)
  2. 片方のクローンを v3 へインプレースアップグレードする
  3. v3 から切り戻し用の v2 へレプリケーションする
  4. v2 から v3 へレプリケーションする
  5. v3 へ ALTER TABLE を実行する
  6. v2 から v3 へのレプリケーションを停止する
  7. アプリケーションの接続先を v3 へ切り替える

切り戻し用の v2 には、v3 からのレプリケーションにより ALTER TABLE が反映されているため、型の不一致が起こりません。そのため、ALL_LOSSY を設定することなくレプリケーションすることができました。
また副産物として、v3 から切り戻し用の v2 へのレプリケーションを事前に実施できるため、リリース当日の作業を 1 つ減らすこともできています。

アプリケーションのテスト

上記の手順で開発環境を v3 へアップグレードした後、各サービスの開発担当者にてアプリケーションのテストを実施して頂きました。テストコードや、前回のバージョンアップ時に作成した実行計画を比較する機能を活用してテストをしています。

テストにより修正した内容の 1 つに、パラメータグループの optimizer_switch の変更があります。
実行結果が変わってしまうクエリが見つかったため、実行計画を確認するなどして調査を進めました。試行錯誤の結果、セミジョイン最適化の duplicateweedout の動きが原因ではないかと考え、開発環境で optimizer_switch の duplicateweedout を off にしたところ、問題が解消しました。
バグレポートをみると、duplicateweedout 含めセミジョイン関連のバグがいくつか報告されていました。ただ今回のテストでは、セミジョインに起因する他の問題は起こらなかったので、duplicateweedout だけ off にする方針にしています。

その他にも問題のあった箇所は、各開発者にて修正をして頂きました。

リリース

テストが完了した後、リリースへ向けた対応を進めました。
まずリリースの前週に「アップグレード手順のテスト」の項に記載した手順の 5 までを実施します。v3 へのインプレースアップグレードは約 15 分、ALTER TABLE の実行は約 15 分で完了し、作業全体は 70 分程で完了しました。

そしてリリース当日のメンテナンス中に手順 6, 7 を実施しました。正確にはレプリケーションの同期確認やアプリのデプロイ作業も含むため、メンテナンス時間は 2 時間設けていましたが、ほぼ予定時間内に作業を完了することができました。
ちなみにレプリケーションの同期確認では、各 DB のレコード件数が一致することの確認をしています。テーブルごとに SELECT COUNT をしているのですが、処理を並列化することにより 10 分程で完了しました。 直列で行なうと 1 時間以上かかることがわかっていたため、作業時間の短縮に繋がりました。

リリース後の修正

v3 へのアップグレード後、v2 へ切り戻すほどの致命的なエラーはありませんでしたが、いくつか問題が発生しました。
ひとつは、パフォーマンスが極端に劣化した SQL の問題です。実行時間が著しく伸び、DB の負荷も増大したため急ぎ修正する必要がありました。
その主な原因は、「テスト」の項でも述べたセミジョインによるものでした。
MySQL 8.0 では内部のオプティマイザに変更が多く、サブクエリを使う SQL において v2 ではセミジョインが使われなくても、v3 だとセミジョインが使われるケースが多くあります。
オプティマイザの不具合なのか、セミジョインを使うと速度が大きく劣化するようなケースにもかかわらず、セミジョインを利用してしまうようなこともありました。
具体的な例を示します。以下は同一の SQL について、セミジョインを on(デフォルト) または off にしたときの、EXPLAIN FORMAT=TREE の実行結果になります。(最初の 2 行のみ抜粋)

optimizer_switch: semijoin=on のケース

-> Aggregate: avg((db_1.table_1.column_1 / (case when (((to_days(db_1.table_2.column_1) - to_days(db_1.table_2.column_2)) / 30) < 1) then 1 else round(((to_days(db_1.table_2.column_1) - to_days(db_1.table_2.column_2)) / 30),0) end)))  (cost=7839673608.28 rows=6050893371)
    -> Nested loop semijoin  (cost=7234584271.20 rows=6050893371)

optimizer_switch: semijoin=off のケース

-> Aggregate: avg((db_1.table_1.column_1 / (case when (((to_days(db_1.table_2.column_1) - to_days(db_1.table_2.column_2)) / 30) < 1) then 1 else round(((to_days(db_1.table_2.column_1) - to_days(db_1.table_2.column_2)) / 30),0) end)))  (cost=302837.05 rows=618503)
    -> Nested loop inner join  (cost=240986.72 rows=618503)

セミジョインを利用する場合だと、rows=6050893371 という内部テーブルができてしまっています。実際にこれが原因で、SQL の実行時間が著しく伸びてしまっていました。
このようなケースに対処するためには、SQL を書き換えたりして局所的にセミジョインを回避する必要がありました。オプティマイズヒントを使って無効化する方法も把握していましたが、フレームワーク経由で SQL が実行される都合上、SQL を書き換えることで対応し、解決することができました。

他には、バッチ処理で以下のエラーが発生した問題がありました。

The table '/rdsdbdata/tmp/#sqlxxx_xxx' is full

これは MySQL 8.0 から一時テーブルの動作が変更されており、それに起因するものでした。リソースの制限値を超えてしまったことによるエラーのため、パラメータグループから temptable_max_ram と temptable_max_mmap の値を調整することで解決することができました。

docs.aws.amazon.com

その他にもいくつかの問題が発生しましたが、内容に応じて各サービスの開発者や SRE チームにて修正を行っております。

リリース後のパフォーマンス

v3 へのアップグレード後、主要サービスの全体的なレスポンスタイムは大きな変化がありませんでした。
先に述べたパフォーマンスが劣化する SQL の影響があり、リリース日は悪化していましたが、修正によりその後は元に近い値となっています。
現状、v3 の仕様や追加機能にあわせたチューニングはしていないので、大きく改善することもなく変化は少なかったのかなと考えています。

レスポンスタイムの状況

まとめ

各チームの開発者とチーム横断で協力して対応した結果、無事に Aurora MySQL v3 へのアップグレードを完了することができました。
本記事が Aurora アップグレードへ取り組む方にとって、少しでもお役に立てれば幸いです。

RubyKaigi 2024に参加しました

はじめまして、開発部の大庭です。
普段はRuby on Railsを用いたアプリケーション開発を行なっています。

今回、那覇で開催されたRubyKaigi 2024に参加してきましたので、印象に残ったセッションや感想をお伝えしたいと思います。

なお、弊社はSilver SponsorとしてRubyKaigi2024に協賛させて頂きました。

rubykaigi.org

印象に残ったセッション

Day1 Keynote 「Writing Weird Code」

発表資料はこちらから確認できます。

初日の最初のセッションで、初参加の私はRubyKaigiの洗礼を受けました。
一見して複雑で実行不可能に見えるコードが、実際には文法として有効で実行可能であること、さらにはQuine(自身のプログラムを出力する性質)であるプログラムが紹介されました。

普段の業務では、読みやすく簡潔なコードを目指していますが、このセッションではその対極にある世界を垣間見ることができ、Rubyの奥深さに衝撃を受けました。

このセッションは衝撃の連続であり、正規表現を用いて連立方程式の解を返すプログラムや、Rubyのプログラムとして実行可能な画像など、Rubyの高い表現力や拡張性に大変驚きました。

帰宅してから、セッションで取り上げられていたプログラムを実行してみたのですが、実行結果を見てただただ驚くばかりでした。 改めてまじまじと見ても、何が書いてあるのか、何故動くのかがまったく理解できませんでした。

github.com

次回のRubyKaigiでは、このようなコードが集まるTRICK(超絶技巧 Ruby 意味不明コンテスト)というも催しも開催されるようで、非常に楽しみです。

Day1 「Unlocking Potential of Property Based Testing with Ractor」

発表資料はこちら確認できます。

speakerdeck.com

こちらは、Rubyの並列処理機能であるRactorを、テスト手法であるProperty Based Testingに適用し、実行速度の向上を試みたというセッションになります。

Property Based Testingとは、RSpecでテストするようなExample Based Testingに対して、ランダムな値を大量に生成しテストするという手法です。 ランダム値を大量に生成して試行するため、プログラマが予期しなかったバグを発見可能という利点があります。 この手法ではテストを大量に実行する必要があるのですが、それらは独立した処理であるということに注目し、Ractorの適用を試みたという内容でした。

RubyKaigi 2024を通して、個人的に非常に興味を惹かれたセッションでした。
Property Based Testing自体が魅力的な手法で、開発時に手軽に素早く実行できるとより良い開発体験になりそうと感じました。 Integerなどの型をもとに入力データを生成していることから、(近年のRubyのトピックの1つである)型推論と組み合わせることでさらなる手軽さを導入できるのではと思いました。

RubyKaigi 2024を通しての感想

印象に残ったセッション以外にも、言語処理系など普段あまり関わる機会のない分野のセッションも聞きました。 これまで勉強してこなかった分野のため、内容のほとんどは理解できなかったものの、「Rubyの1+1は常に2ではない」(Integer#+の定義を容易に上書きできる)などといった話は印象的でした。 このように、Rubyについての幅広い話題が取り上げられており、そこから新たな考えや発見を得られるのは非常に良い経験でした。

私は外部の勉強会などに参加してこなかったのですが、RubyKaigiに参加してブースや懇親会で、他社のエンジニアさんと交流することができて非常に刺激を受けた良い経験となりました。他社のエンジニアさんを勝手に畏怖していたのですが、実際に交流してみるとそんなことはなく、会社の違いはあれど同じエンジニアなんだなと感じることができました。 また、弊社に在籍していた先輩がLTに登壇されており、こちらも刺激を受けるとともに、登壇(者)を少しだけ身近に感じ、RubyKaigiに限らず機会があれば何か発表してみたいなと思いました。

さらに、各社ブースを巡ってみて、使用しているツールなどのアンケート結果に驚いたり、掲示されているプログラムに付箋でコードレビューする催しなど、セッション以外も非常に面白かったです。

おわりに

非常に熱気のある発表に対して、自分は知らないことだらけで、学習欲・プログラミング欲が刺激される3日間でした。 いつか発表の内容が理解できるように精進していきたいです。

中途入社モバイルアプリエンジニアが入社後4ヶ月で感じたこと

自己紹介

こんにちは、開発部モバイルアプリチームの横山です。

普段は求人飲食店ドットコムのAndroidアプリの開発をしている私ですが、今年の2月に中途入社しました。
社会人になって10年目になります。
新卒でIT業界とは関係のない業界に就職し、コロナの影響など紆余曲折を経て前職でエンジニアとしてのキャリアをスタートしました。
前職は会社ではWebサイト開発とFlutterを使ってiOS・Androidアプリケーションの開発をしていました。

そんな私が入社して4ヶ月を経とうとしている今、入社エントリーとして、転職活動をしていた当時や、入社してからのシンクロ・フードでの経験を振り返り、感じたことを今回お伝えできればと思っています。

なぜシンクロ・フードに転職したか

ネイティブでのアプリ開発に徐々に興味が出てきていた事と、社内でのアプリ開発者が私一人で、自分よりも知見や知識が豊富なエンジニアと共に働き、自身の技術を向上させたいと考え転職を決意しました。

転職の軸として「成長できそうか」「アプリ開発に携われるか」という観点で会社を探していました。
弊社とのカジュアル面談での説明が、他社と比べて技術スタックや会社の評価制度について詳しく説明をしていただき好印象だったことや、アプリ開発の方針や、アプリ開発をクロスプラットフォームのFlutterやReact Nativeで開発するのではなく、Kotlin、Swiftで開発を行っていることなど、今後エンジニアとして働いていくなかで技術力を伸ばしていける理想的な環境が整っていると思ったのでシンクロ・フードを選びました。

また、評価制度がしっかりしてると感じたので、納得して業務を取り組んでいける環境でもあると思いました。

入社前の不安と実際に入社した後の心境

弊社の開発部はフルリモート制度が2023年5月より採用されており、フレックスタイム制で働くことができます。
コアタイムの11:00〜16:00に出勤していれば、フレキシブルタイムの7:00〜22:00の間で好きな時間に業務を行うことができ、月の労働時間が所定の時間を満たすように気をつければ出勤時間と退勤時間は自由にすることができます。
このように、柔軟に働くことができるので、とても魅力的な環境だと思っていたのですが、フルリモート勤務、フレックタイム制どちらとも未経験の私には不安のもとでした。
仕事とプライベートの区別が難しいのではないか、オフラインで直接相談して聞けないことで、チームメンバーとのコミュニケーションや業務の進め方、社内ツールの使い方など上手くやっていけるかなど様々なことに不安を感じていました。

しかし、実際に働き始めてみると、仕事とプライベートの切り替えにはそれほど問題がなく、フルリモート勤務ならではのメリットを感じています。
腰痛があるため前職ではずっと座っていることが辛かったのですが、これを機会に自宅のデスクを昇降式デスクに変更するなど作業環境の改善に取り組み、身体的ストレスが軽減されるようになりました。
私は本社がある東京ではなく関西圏からフルリモート制度を利用して勤務をしてるので、おかげで自宅でも集中して業務に取り組めるようになり作業効率は良くなったと感じています。

現在のデスク

コミュニケーションに関しても、Slack内のチームチャンネルや1on1の場を通じて、気軽に質問や雑談ができる環境が整っているので、不安を感じることなく業務に取り組めています。週1回チームのミーティングでは、雑談の時間があり好きな雑談テーマや、近況などを話すことができます。最近あった話題は「よく見るYoutubeチャンネルやテレビ番組」、「雑学やトリビアなど、ちょっとした知識や発見、タメになったり面白い情報」など業務とは関係ないことなどを業務委託の方々含め談笑していたりします。

また、社内のドキュメントが整備されており、必要な情報をすぐに見つけることができるため、仕事に取り組む上での情報に困ることは今のところないです。あと、オンボーディング期間も十分に設けられ、社内の業務フローやツールの使い方が丁寧に教えてもらえたので、スムーズに業務に取り組むことができる環境だと感じています。

■ オンボーディング期間中の1日の流れ

  • 09:00:メール確認、昨日の復習など
  • 11:00:チームMTG
  • 12:00:お昼休憩
  • 13:00 ~ 17:00:学習、業務フローやサービスの説明
  • 18:00:1on1、メンターとの振り返り

シンクロ・フードに入って感じたこと

一番の大きな印象は、社内ドキュメントがしっかりしている整理されていることでした。
情報共有サービスなどに社内業務の進めかたや、開発のルール、仕様等がまとまっていて、大体の探したいものは検索したらすぐに見つかるので、とても便利で仕事がしていきやすいと感じています。あと入社間もない頃は、大量に覚えないといけないという気持ちが少しあったのですが、検索したら情報が出てくるので、心理的な負担が軽くなった覚えがあります。

esa環境構築などの一覧

あと、社内アシスタントbotの「ナレット」や「さえずり」の開発を行うなど、業務をより便利に、かつ効率よくしていこうという活動や、前職ではなかったドキュメントを作るだけでなく、アップデートをしていこうというチームとしての方針を感じているので、とてもいい環境だなと感じています。私も何かしらアプリ開発やその他の業務に関することを残して貢献していけたらと思っています。

業務でもネイティブでのアプリ開発がしたくて、入社したので現在の開発業務全般にやりがいを感じています。

課題・改善点について

求人飲食店ドットコムAndroidアプリには、技術的にレガシーな部分やフォルダ構成が機能によってバラバラな点があるのでその改善をしていきたいです。(フォルダ構成に関しては現在リアーキテクチャを進めているので時間の問題で解決をするのかなと思うのですが...)

あと、AndroidOS特有のナビゲーションバーの「戻るボタン」をタップした時やジェスチャーナビゲーションの戻る操作を行った際の動作について、もっとこうしたらいいのにと感じる点があるので、そのことが課題かなと思っています。感じた課題に関しては機能改善として提案していきたいです。

これからについて

まだまだ力不足なところもあり十分には貢献できてはいないのですが、求人飲食店ドットコムAndroidアプリの施策機能の実装を進めて行きたいです。求人飲食店ドットコムiOSアプリと比べて、開発が追いついていない状況なので開発を進めていき、使っていただくユーザーに便利なアプリを提供していきたいです。

機能開発以外ではiOSアプリではSwiftUIについて取り組んでいるので、Android側でも徐々にですがJetpack Composeの導入について考えていければといいなと思っています。

便利な社内アシスタントBotのシステム構成を大公開!

こんにちは、開発部モバイルアプリチームの小関です。

普段は求人飲食店ドットコムのiOS・Androidアプリの開発をしている私ですが、昨年4月に設立された「GPTプロジェクトチーム」にも参加しており、この1年でより一般的にも身近になってきた生成AIをサービスや普段の業務に使えないかと模索する仕事もする日々です。

今回は、そんな「GPTプロジェクト」の一環で作成した社内アシスタントBotによって、ナレッジの検索しづらさを解決しようとした事例をご紹介しようと思います。

GPTプロジェクトとは

弊社では2023年4月から、CTO直下に「GPTプロジェクトチーム」を新設して(*1)、ChatGPTのような生成AIを活用する取り組みを進めています。
このプロジェクトでは、弊社が運営するサービスや社内業務に対して生成AI技術を適用し、飲食店の業務効率化や負荷削減を図ることを目的として活動しています。

チーム発足まもない頃は、そもそも生成AIとはどう動いているのだろうといった部分や、ChatGPTなどの生成AIサービスはどういった形で利用でき、どのような場面に適用することができるのかを探るところから行っていました。

生成AI自体は様々な種類がありますが、手始めに取り組むものとしてはやはりChatGPTからだろうとなり、APIの利用方法やLangChainという文章生成に関わるライブラリを調べるところから行い、この1年間でいくつか、ChatGPTを組み込んだツールや機能の開発を行いました。

今回はその中から、社内アシスタントBotを作ったことでナレッジ検索がしやすくなった事例を紹介していきます。

社内アシスタントBot 第一弾「ナレット」

弊社の社内ルールや労務関連の情報をまとめたナレッジはGoogleサイトで管理をしているのですが、ナレッジが存在するかを調べるときに検索ワードが適切に選定することが難しかったり、それらしいキーワードで調べたときに本来見たいはずのナレッジが上位にでてこなかったりする課題がありました。

そんな中、他社の事例でチャットツールを経由したナレッジ検索のやり方が公開されており(*2)、弊社でもそれを参考にして作ることでこの課題をクリアすることができるのではないかという話がチーム内で挙がり、実際に開発に至りました。

そうして作られた社内アシスタントBotは「ナレット」と名付けられ、ナレットに対してメンションをつけて質問を投げかけると、質問に類似したナレッジ記事から必要な箇所を情報として引っ張ってきて、その情報を元に質問に答えてくれるといった、まさにほしかった仕組みが完成したのでした。

ナレットとのやりとり
ナレットとのやりとり

(ナレッジ+キャットからついた名前のとおりに猫キャラを付与しています)

このBotの良い点は、適切なナレッジを検索するためのキーワードがある程度曖昧でも、自然言語で質問をして検索ができるところにあります。

また、参照したナレッジがリンクで提示されるため、そのナレッジにアクセスして、より詳しい情報を得ることもできますし、正しい情報を言っているのかどうかも、参照したナレッジが適切そうかどうかである程度判断することができます。
本当に正しいのだろうか?という回答の際には、大半が関係のないナレッジを参照してしまったことによるもののため、その際には質問の聞き方を変えてみることで、本来知りたかった情報を得られるということもありました。
回答は必ずしも正しいとはいえないため、最終的には質問者がソースまで見て判断することを一応推奨しているのですが、ある程度は正しいナレッジが参照されていれば適切な回答ができるようになっています。

また、回答にフィードバックボタンをつけて意図どおり動作しているかを把握できるようにしています(適切な回答が得られなかった際にGPTプロジェクトメンバーがサポートを行い、質問の仕方をアドバイスしたり、ナレッジがないことで回答できていなければナレッジを作成・更新してもらうよう働きかける想定でつけた機能です)。

ここからはシステム的な話になりますが、実際に組んだ構成は以下のようになりました。

ナレットシステム構成
ナレットシステム構成

中心となっているのは、Slackが提供する Node.jsベースのフレームワークの Bolt for JavaScript を使って構築したアプリケーションで、実験的に動かしたいということもあり、PaaSのherokuを利用して動かしています。

また、今回はChatGPTを利用するにあたり、「LangChain」というChatGPTなどの大規模言語モデル(LLM)を使いやすくするためのフレームワークも併せて利用しています。
このLangChainはTypeScriptかPythonの形で提供されていましたが、社内的にもGPTプロジェクトメンバー的にもTypeScriptのほうが都合が良かったため、Slackアプリケーションも含めて言語はTypeScriptで統一して、このような構成になりました。

全体的な処理の流れとしては、

① Slackでナレット宛のメンションとともに質問をすると、Webhookによってアプリケーションにその情報が送られる
② 質問文をベクトル化する
③ 質問文に近い内容のナレッジがないかを、②のベクトルを使ってナレッジのベクトルDBに対して問い合わせる
④ ③で得た検索結果上位数件のナレッジの情報と質問文を組み込んだプロンプトでChatGPTに回答を作成してもらう
⑤ Slackの質問のスレッドに対して、④を返信する

となっています。

この中にでてくるナレッジが格納されたDBにはPineconeを利用しており、あらかじめGoogleサイトから取得したナレッジデータを一定の文量で区切って(チャンク化)、それをOpenAIのEmbedding APIにてベクトル化したデータを保存しています。
チャンク化するデータサイズは、大きすぎると最終的にChatGPTに渡す情報量が大きすぎることで当時は最大文字数の関係で扱いづらかったり課金額が増えてしまう課題につながり、小さすぎても情報が限定されることで質問に似ているかどうかの検索マッチ度がブレてしまって適切なデータが参照できないため、ここの調整には時間をかけて実験を重ね、ちょうどよいサイズを模索しました。
(最近では、ChatGPTに渡せる最大情報量(=トークン量)がかなり拡張され、課金額も安くなっているので昔よりもこのサイズは大きくしても問題なくなってきています)

こうして作られたナレットですが、単純にナレットを利用することでナレッジが検索しやすくなったこと以外にも、古いナレッジを放置するリスクがより高まったことで、なるべく更新をしようとする動きが働いたこともプラスの一面でした。

ちなみにChatGPTに詳しい方であればお気づきのとおり、このように大量の文章データを元にChatGPTに質問に回答させるようなことは、現在はChatGPT単体で解決できるようになっており、Assistantsという機能で提供されています。
ただし、柔軟なカスタマイズはできないので、ベクトルデータに対してタグを付与することや、引用元のURLを表示したりすることなどは工夫が必要になりそうではあり、今の構成を変えるほどではないとも考えています。

社内アシスタントBot 第二弾「さえずり」

弊社ではナレッジサービス・ツールをいくつか利用しており、部署などによっても使い分けをしています。
その中でも、エンジニア、デザイナー、ディレクターが利用するesa.ioというナレッジサービスがあり、業務に関するナレッジを保存したり、普段の会議での議事録としても利用しています。

そんなesaには、弊社が運営するサービスの知識や、開発における知見・ルールなどがまとめられており、前述のナレットのナレッジデータをesaにしたバージョンを作れないかという話が挙がったため、作成することになりました。

アプリケーションの全体的な仕組み自体は流用ができ、ナレッジを保存するベクトルDBの内容を社内ナレッジではなくesaの記事に変えるだけで済むといった具合に、比較的に容易に完成に至り、この新しいBotにはesaのサービスキャラクターの鳥から連想して「さえずり」という名前が付けられました。

さえずりとのやりとり
さえずりとのやりとり

ただ、社内ナレッジはGoogleサイトの都合上、手動でデータを定期的に更新しているのですが、社内ナレッジの更新頻度が低いためにある程度許容できた部分でした。
esaに関しては基本的には毎日誰かがいろいろなナレッジを最新化したり、新規記事作成を行っており、更新間隔は短いほうが好ましかったことと、esaそのものにWebhookの機能があったため、更新や新規作成などをリアルタイムに受け取ってDB側も更新できそうだというところで、ナレットよりも少し広がったこのようなシステム構成となりました。

さえずりシステム構成
さえずりシステム構成

やはり、労務などのルールに関する疑問よりも実業務における疑問のほうが発生しやすいこともあり、ナレット以上にさえずりは利用されているのを見かけます。
また、ナレットが任意のチャンネル内でしか利用できないのに対して、さえずりはDMでクローズドに質問ができる点も良いところのため、ナレットも同様にDMで利用できるようにすることを検討しています(DMでのやりとりの際にはログが残らないようにも設定をしています)。

おわりに

GPTプロジェクトではこの1年で他にも、求人@インテリアデザインにおける職務経歴例自動生成機能のリリース(*3)に協力したりと、ChatGPTを活用した試みをいくつか行ってきました。

最近は、Claude 3というLLMがGPT-4を超えているのではないかと話題になっていたり、まだまだこの領域は目まぐるしく進化をしている毎日ですが、プロジェクトチームではChatGPTのみに限らずに多くの生成AIの活用を模索していきたいと考えています。
この1年は小さい改善を中心に検証を行って、社内でのノウハウも少しずつ溜まってきたため、今後はLLMを利用した大きい改善に挑戦していくフェーズになっていきます。
これからも飲食に関わる方々へ、より価値を提供できるような開発を行っていきたいと思います。