TECH PLAY

株式会社エニグモ

株式会社エニグモ の技術ブログ

240

こんにちは!UIUXデザイナーの和田です。 こちらは Enigmo Advent Calendar 2024 の7日目の記事です。 この記事では、定期的に実施している BUYMA のユーザーインタビューについてご紹介します。 また、ユーザーインタビューで得た気付きをもとに新設した【 まるわかりBUYMA公式スタートガイド 】についても後述します。 www.buyma.com 1. ユーザーの声を直に聞くメリット エニグモ では、社内のデータアナリストがBUMYAの利用データを分析して日々サービス改善に努めています。 定量 的な分析に加えて、的確なUX改善・向上を進めていくためには、ユーザーさまの声を直にお聞きして、なかなか数値からは解明できない 【ユーザーの行動の背景・ インサイト 】 をより深く理解することが重要だと考えています。 BUYMA では、ユーザーさまの声を直にお聞きするために、 定期的にオンライン形式(Zoom)のユーザーインタビューを実施しています。 社内のUIUXデザイナーとデータアナリストがインタビューアーをすることで、ユーザーさまからいただいたご意見やヒントをもとに「すぐに詳細な分析をおこない、施策化して改善を進められる」ようにしています。 2. BUYMA のユーザーインタビュー BUYMA では、ユーザーさまを対象に実施しているユーザーアンケートの中でインタビューにご協力いただける方を募っています。 毎回とても多くの方に、インタビューへのご協力に前向きな回答をいただいており、全国男女問わず幅広い年代の方にご参加いただいています! ユーザーインタビューの内容についてご紹介します。 🌟 2-1.ユーザーさまご自身について ユーザーさまご自身についてのお話を広くお聞きしています。 主に、『普段の生活スタイル』や『どのような「価値観・嗜好」をお持ちなのか』といった点や、 BUYMA との接点になりそうな「ファッション情報にふれる機会」「お買い物傾向」に関するお話などを伺っています。 🌟 2-2.サービス利用に関する ヒアリ ング ユーザーさまが普段 BUYMA を『どのように利用して』『どのように感じていらっしゃるか』、実際の利用エピソードをもとにプラスとマイナスの両側面から率直な話をお聞きできるようにしています。 ・ BUYMA の利用をはじめたきっかけ ・ BUYMA を使う理由( BUYMA の価値) ←→  BUYMA を使わなくなった理由(離脱理由) ・ BUYMA の気に入っているところ ←→ いまいちだと感じるところ、残念なところ ・ BUYMA を安心して利用できるポイント ←→  BUYMA に不安を感じるポイント ・ BUYMA でのよかった・満足できたエピソード ←→  BUYMA でのマイナスエピソード BUYMA において価値を感じていただいているポイント → より多くの方にその価値を広げていけるように BUYMA における課題・ペイン → 課題の背景を理解して適切な改善につなげていけるように 上記のように、プラス・マイナスそれぞれの気付きをもとに、UX改善・向上にむけた検討を進めています。 🌟 2-3.利用を見せていただく(行動観察) 実際にアプリやサイトを利用している様子を画面共有いただいて、どのように BUYMA をご利用いただいているか見せていただきつつ、お話を伺っています。 例えば、ユーザーさまの普段の利用(検索や商品閲覧・比較検討など)に即したシナリオをベースに、どのような操作をどのような手順でされているのか、利用の中の些細なつまずきやペインがどういったところにあるか、などを操作いただきながら ヒアリ ングを進めています。 🌟 2-4.サービスに関する認知・理解に関する ヒアリ ング BUYMA サービス・機能における「認知(知っていたか)」や「理解(理解できていたか)」についてもお聞きしています。 『どこで認知・理解したか』『どのように解釈していたか』なども率直にお聞きすることで、【対象サービス・機能の露出や導線が十分であるか】など、改善のヒントをいただけるようにしています。 🌟 2-5.検討中の施策に関するコンセプト ヒアリ ング 検討中の施策に関するプロトタイプをZoomミーティングの中で投影させていただきながら、率直な印象やご意見をお伺いしています。 施策を進める前に、初見の印象やニーズにマッチしているかなどを確認することができ、よりよいかたちに細かな調整をかけることができます。 BUYMA のUXリサーチについては、昨年の記事でもご紹介しておりますので、ぜひこちらもご覧ください。 tech.enigmo.co.jp 3. ユーザーの声から「気付くこと」「改めてわかること」がある ユーザーにとっての「ペイン・課題」「 BUYMA の価値」など、ユーザーの声から「気付くことができること」「改めてわかること・理解が深まること」がたくさんあります。 🌟ユーザーにとっての「ペイン・課題」に関する気付き BUYMA 利用におけるつまずきがどんなタイミングで起こっているのか、「 BUYMA 利用におけるペイン・課題」がどのくらいUXに影響を及ぼしているか、など、ユーザーインタビューを通じて具体的に理解を深めることができます。また、サービス提供側として想定していた捉え方と、ユーザーさまの捉え方が異なっていたり、思った以上に浸透していない情報についての気付きを得ることもあります。 🌟ユーザーにとっての「 BUYMA の価値」に関する気付き 一方で、ユーザーさまにとって「 BUYMA の価値」がどのように認識されていて、ユーザーさまのまわり(ご家族やご友人)に浸透しているか、などの気付きを得ることもできます。 以下では、ユーザーインタビューを通じて、改めて BUYMA の価値だと再認識することができた機能についてご紹介します。 世界中からさがす|リクエスト一覧 BUYMA には『リク エス ト』という機能があります。 世界中にいる BUYMA の出品者に、ほしい商品を探してもらうことができる機能ですが、 インタビューでは、この機能を利用して購入経験のある方は「リク エス ト機能の満足度がとても高い」ことがわかりました。 「どこを探しても見つからなかった国内完売商品がほしくて、 リク エス トを利用してみたら見つかって購入できて本当に嬉しかった!」 「ずっと探していたバッグがなかなか見つからず、中古は状態がいいものがなく諦めかけていたけれど、リク エス トを使って購入することができて非常に満足。」 また、インタビューの対象者の中でリク エス ト機能を使ったことがある方は、複数回リク エス トを利用しており、 「探したいと思うアイテムをリク エス トして世界中から探してもらう」体験を気に入ってくださっている傾向がありました。 今後は、「探したいアイテムがあるけれどリク エス ト機能を知らない・使ったことがない方」に、 このプラスの体験をより広げていけるように検討を進めていければと考えています。 4. ユーザーの声からまるわかり BUYMA 公式スタートガイドを作った話 ユーザーインタビューが終わったあとは、まとめ作業を行って主要な気付き( BUYMA の価値、ペイン・課題、仮説検証の結果など)を社内に共有しています。 そこから、UX改善・向上につなげる施策化をおこなっています。 今回は、ユーザーインタビューで得た気付きからコンテンツ作成を行った事例を紹介します。 まるわかりBUYMA公式スタートガイド 4-1.まるわかり BUYMA 公式スタートガイド新設の経緯 インタビューの中では、 BUYMA に関する分からないことや不安なことについてもお伺いしています。 その中で、以下のようなご質問・ご意見をいただくことがありました。 「 BUYMA の運営会社って日本企業なんですか?」 「 BUYMA の商品ってなんで安いんですか?」 「 BUYMA でほしい商品があるけれど購入して大丈夫なのか不安があります。」 このようなご質問・ご意見をいただいたことをきっかけに『 BUYMA を安心してご利用できると判断するための情報の重要性』について改めて認識することができました。 その後、社内で検討を進め「 BUYMA とはどういったサービスなのか」「安心して利用できるのか」などの疑問や不安を解消いただくために、【 まるわかりBUYMA公式スタートガイド 】というページを新設することになりました。ぜひこちらもご覧ください! www.buyma.com インタビューの中で BUYMA に関する分からなさや不安があるとお話されていたユーザーさまから、 いろいろと会話させていただいたインタビューの最後に 「思い切ってインタビューに参加してみてよかった。これまでより安心して買い物ができそう。」 とおっしゃっていただいたことがありました。 ユーザーインタビューを通して、ユーザーさま1人1人に向き合うことの大切さを実感するとともに、 UX改善・向上に向けて一歩ずつ前進するためのエネルギーをいただけていると感じています! 4-2. BUYMA ユーザーの声をよりたくさんの人に広げたい ユーザーインタビューの中で、積極的にご利用いただいているユーザーさまは、「まわりのご家族やご友人も BUYMA をご利用いただいている方が多い」という傾向が見えてきました。 一方で、 BUYMA でのお買い物を躊躇される理由として、「まわりに BUYMA を利用している人がいない」といった方が多くいらっしゃいました。 そこで、『どんな人が BUYMA を利用しているかイメージがつくことで親近感がわくようにするとよいのではないか』という仮説から、 BUYMA をご利用いただいているユーザーさまのお声を BUYMA のコンテンツとして掲載させていただくことにしました。 🌟 BUYMA ユーザーが感じる BUYMA のよさ BUYMA の利用者が「どんなところに BUYMA の魅力を感じてくださっているのか?」より多くの人に知っていただくために、 BUYMA を使う理由についてユーザーさまのご意見をピックアップしています。 BUYMAユーザーに聞く|なぜBUYMAを使ってるの? 🌟 BUYMA ユーザーが安心して利用できる理由 BUYMA を安心して利用してくださっている利用者が「どうして BUYMA を安心して利用できると判断してくださったのか?」より多くの人に知っていただくために、 BUYMA を安心についてお聞きした際のご回答をピックアップしています。 BUYMAユーザーに聞く|安心して利用できてる? www.buyma.com BUYMA についてより深く知っていただき、安心してご利用を続けていただけるように、今後も BUYMA のことをもっと深くわかりやすい情報を発信していければと考えています。 明日の記事の担当は・・・ 【 BUYMA サイトの開発と運用保守をされている レミー さん】です! お楽しみに! 株式会社 エニグモ すべての求人一覧 hrmos.co
アバター
こんにちは、データサイエンティストの髙橋です。業務では企画/分析/ 機械学習 モデル作成/プロダクション向けの実装/効果検証を一貫して行っています。 この記事では類似画像検索システムの内製化にあたり、システム面での課題をどのように解決したかについて紹介します。内製化の背景や 機械学習 部分などについては 以前作成した記事 で説明しており、この記事はその続きとなります。 この記事は Enigmo Advent Calendar 2024 の 5 日目の記事です。 内製化の目的・事業インパクト 類似画像検索のシステム化における課題 システム化における各課題の解決策 「商品画像ダウンロード > セグメンテーション > Embedding 計算 > アップロード」の高速化 Embedding 同士の類似度計算処理の高速化 画像ハッシュ計算処理の高速化 複数 GCP サービスを連携させてスケジュール実行するアーキテクチャ作成 実装時の工夫 Cloud Composer からの GCP サービス呼び出し方法 Vertex AI Vector Search での類似度計算時にリトライ まとめ 追記:2025/01/21 第48回 MLOps 勉強会で登壇しました! 内製化の目的・事業 インパク ト 内製化の目的は、弊社が運営する CtoC EC サイト BUYMA において、商品 名寄せ で利用している他社製の類似画像検索システムの精度を保ちながらコストを削減することでした。既に内製化後のシステムに移行しており、同等の精度を維持しつつ年間数百万円規模(約8〜9割)のコスト削減を見込んでいます。 また、 名寄せ 以外にも類似画像検索システムを利用している施策があり、その移行も進めることでさらなるコスト削減の可能性があります。さらに、他社製の類似画像検索システムではコストが高く断念していた EC サイト上での画像起点のレコメンドや、画像による類似商品検索機能なども検討出来るようになりました。 より詳細な説明については 前編である機械学習編の記事 を参照ください。 類似画像検索のシステム化における課題 類似画像検索のシステム化における課題として、非常に大規模なデータを現実的な時間で処理する必要がありました。 機械学習編の記事に記載した類似画像検索の各ステップ に対して、毎月処理するデータ量は以下の通りでした。 ステップ 毎月処理するデータ量 商品画像をダウンロードし、物体存在箇所をセグメンテーションして切り抜き 約 200 万画像 切り抜いた画像の Embedding 計算 約 1000 ∼ 2000 万画像 切り抜いた画像・Embedding ファイルを GCS ( Google Cloud Storage) にアップロード 約 2000 ∼ 4000 万ファイル Embedding による画像同士の類似度計算 数十〜数千億の組合せ 画像ハッシュによる画像同士の類似度計算 数十万の組合せ 単純に単一のサーバー上で各ステップを実行する方法では、全体で1ヶ月以上かかる見込みであり、毎月定期的に処理を行うのは現実的ではありませんでした。 また、データ量以外の課題として、弊社では 機械学習 基盤として Vertex AI Pipelines を利用していましたが、今回のシステムはその基盤上に実装できない課題がありました。先述した規模のデータを処理するにはそれに特化した複数の GCP ( Google Cloud Platform) サービスを組み合わせる必要がある一方で、現行の基盤は VM インスタンス 上で Python コードを実行する用途を想定していたためです。そのため、複数の GCP サービスを連携して毎月スケジュール実行する アーキテクチャ を作る必要性がありました。 システム化における各課題の解決策 前述した課題をどのように解決したかを説明します。ただし、前提として類似画像検索の各ステップのうち、以下は1つのかたまりとして処理することにしました。 商品画像をダウンロードし、物体存在箇所をセグメンテーションして切り抜き 切り抜いた画像の Embedding 計算 切り抜いた画像・Embedding ファイルを GCS にアップロード 理由は、セグメンテーションおよび Embedding 計算の両方に GPU が必要であったため、また GCS とのデータのやり取りに時間がかかることから、「商品画像ダウンロード > セグメンテーション > Embedding 計算 > アップロード」の一連のステップを同一メモリ上で行いたかったためです。 「商品画像ダウンロード > セグメンテーション > Embedding 計算 > アップロード」の高速化 Dataflow  という並列分散処理が行える GCP サービスを利用することで高速化を実現しました。Dataflow とは、 GCP が提供するマネージドのバッチ・ストリーミングデータ処理サービスであり、並列分散処理により大量のデータを効率的に処理することが可能です。 今回 Dataflow を選択するにあたり、 Ray on Vertex AI も候補に上がりました。いずれも並列分散処理は実現できそうでした。Ray はその構文がネイティブの Python に近く、既存の Python コードに大きな変更を加える必要がなさそうだったため、実装コストが低そうに見えました。一方で、Dataflow は Apache Beam の構文を理解し覚える必要があり一定の学習コストが伴いそうでしが、社内で利用実績があり困ったときに既存の資産を参考に出来そうであったため Dataflow を選択しました。 実際に、 Dataflow の実装で困ったときに他プロジェクトでの ソースコード を参考にして解決することができ、この選択は正解であったと考えています。また、 Apache Beam の構文もそれほど複雑ではなく、初期の学習コストは多少ありましたが慣れれば実装に大きく手間取ることはなかったです。 約 200 万枚の画像に対して、並列分散処理無しでは約 30 日かかる見込みでしたが、Dataflow により約 56 時間に短縮することができました。 Dataflow の設定は ワーカーマシンタイプ: n1-highmem-4 (vCPU 4 、メモリ 26 GB) ワーカー数: 4 GPU :  nvidia -tesla-t4 としました。ハイメモリのマシンタイプを利用した理由は、メモリ枯渇でジョブが途中で停止してしまうことがあったためです。 Embedding 同士の類似度計算処理の高速化 Vertex AI Vector Search というサービスを利用しました。Vertex AI Vector Search とは、 GCP が提供するマネージドサービスで、膨大な数の Embedding 同士の類似度計算を高速に行うことができます。 Vertex AI Vector Search を利用することで約 1700億個の Embedding の組合せを約 8 時間で処理することができました。単一のサーバー上で処理した場合の処理時間は見積もっていませんが、おそらくこの規模のデータをこの速度で処理するシステムを作るにはそれなりの 工数 がかかったと思います。 Vertex AI Vector Search の設定としては、Algorithm type 、 Shard size はそれぞれデフォルト値である tree-AH 、 Medium で速度やコストに問題がなかったためそのままとしました。 Approximate neighbors count は値を変えて実験したところ検索速度に大きな違いが生じました。具体的には、 約 1 千万件の 768 次元のベクトルに対して類似度計算を 100 回行い、処理速度の統計量を算出したところ以下の通りでした。 num_neighbors mean (sec) std (sec) min (sec) max (sec) 10 0.031 0.013 0.021 0.076 100 0.189 0.034 0.146 0.266 1000 0. 218 0.034 0.17 0.297 10000 0.519 0.149 0.39 1.109 上記実験結果より可能な限り低い値にすることで処理時間が大きく短縮できそうでした。今回の用途では 1 画像に対して同一と検知される見込み画像数は数件程度であったため、それをカバーできる 10 としました。 画像ハッシュ計算処理の高速化 このステップでも Dataflow を利用しました。 約 27 万件の画像の組合せに対して、並列分散処理無しでは約 54 時間かかる見込みでしたが、Dataflow により約 45 分に短縮することができました。 Dataflow の設定は ワーカーマシンタイプ: n1-standard-1 (vCPU 1 、メモリ 3.75 GB) ワーカー数: 120 としました。Dataflow を採用したことで、ワーカー数を自由に変更することができ、120 ワーカーで並列分散処理を容易に実現できました。 複数 GCP サービスを連携させてスケジュール実行する アーキテクチャ 作成 Cloud Composer という Airflow のマネージドサービスを利用しました。Cloud Composer は、 GCP が提供するワークフロー オーケストレーション サービスで、複数の クラウド サービスを連携してスケジュール実行することができます。 今回 Cloud Composer を選択するにあたり Cloud Workflows も候補に上がりましたが、 Airflow の社内での利用実績が豊富であったため Cloud Composer を採用しました。こちらでも困ったときに他プロジェクトの既存の ソースコード が参考になる場面が多く、また移行前の類似画像検索システムの一部で Airflow を利用しており、既存システムの理解がスムーズに出来たメリットもありました。 以下のようなフローで Dataflow や Vertex AI Vector Search などを連携し、類似画像検索の各ステップを実行するシステムを Cloud Composer で実装しました。 開発生産性や保守性を向上させるために、 Dataflow では各ステップごとに Docker Image と Flex Template を、 Cloud Batch でも Docker Image を利用しました。これにより各ステップを独立に開発・テスト・デプロイ出来るようにしました。この部分の詳細については、別途機会があれば記事として執筆する予定です。 実装時の工夫 ここでは、システム実装時の工夫を2つ紹介します。同じようなシステム構成の実装をされる方の参考になれば幸いです。 Cloud Composer からの GCP サービス呼び出し方法 Cloud Composer から 各 GCP サービスを呼び出すに当たり、Dataflow には Airflow に専用のクラス が存在しましたが、 Vertex AI Vector Search には存在しませんでした。そこで、 GCP の REST API ( 例:インデックス作成 API )を呼び出すことでリソースの作成や作成状況のポーリングを行うクラスをカスタムで実装しました。具体的には Airflow の Sensor クラスを利用して以下のようなイメージで実装しました(あくまで ソースコード のイメージとして簡易化したものであり、実際のものとは異なります)。 from typing import Any, Dict from airflow import models from airflow.decorators import task from airflow.sensors.base import BaseSensorOperator @ task (task_id= "create_resource" ) def create_resource (args1: int , args2: str ): """GCP のリソースを作成する(Vertex AI Vector Search のインデックスなど)。 Args: args1 (int): 引数1。 args2 (str): 引数2。 """ # call_create_resource_api 関数は別途実装。内部で GCP の REST API を呼び出す。 response = call_create_resource_api(args1, args2) return response class ResourceSensor (BaseSensorOperator): """GCP のリソース操作の状況をポーリングする。 Attributes: poke_task_id (str): ポーリング対象のタスクID。 args1 (int): 引数1。 args2 (str): 引数2。 """ def __init__ (self, *, poke_task_id: str , args1: int , args2: str , **kwargs): """GCP のリソース操作の状況をポーリングするクラスを初期化。 Args: poke_task_id (str): ポーリング対象のタスクID。 args1 (int): 引数1。 args2 (str): 引数2。 """ super ().__init__(**kwargs) self.poke_task_id = poke_task_id self.args1 = args1 self.args2 = args2 def poke (self, context: Dict[ str , Any]) -> bool : """ リソース操作の状況をポーリングして、完了したかどうかチェックする。 Args: context (Dict[str, Any]): Airflow のコンテキスト。どのような値が格納されているかは以下参照。 https://airflow.apache.org/docs/apache-airflow/stable/templates-ref.html Returns: bool: リソース操作が完了したかどうか。 """ # 作成したリソース情報(IDなど)を取得。 response = context[ "ti" ].xcom_pull(task_ids=self.poke_task_id) # call_get_resource_status_api 関数は別途実装。内部で GCP の REST API を呼び出す。 status = call_get_resource_status_api(response, args1=self.args1, args2=self.args2) # status = {"done": True} のような値と仮定 return status[ "done" ] with models.DAG( "dag_name" , schedule_interval= "0 0 * * mon" , ) as dag: create_resource_task = create_resource(args1, args2) wait_create_resource_task = ResourceSensor( task_id= "wait_create_resource" , poke_interval= 60 * 10 , timeout= 3600 * 3 , poke_task_id= "create_resource" , args1=args1, args2=args2, ) create_resource_task >> wait_create_resource_task ここで、 Airflow のコンテキストを利用してポーリング時に必要なリソース情報(ID など)を Sensor クラスで取得するようにしました。 Vertex AI Vector Search での類似度計算時にリトライ Vertex AI Vector Search で類似度計算を行う際に、 Exponential backoff アルゴリズム によるリトライ処理を入れるようにしました。理由は、実際に運用していると Vertex AI Vector Search の呼び出し時に google.api_core.exceptions.InternalServerError: 500 Failed to call Service Control Check. や google.api_core.exceptions.Unknown: None Stream removed というエラーが稀に発生することがあったためです。Vertex AI Vector Search による類似度計算は1回の定期実行あたり約 8 時間かかるため、途中で停止するとリトライにかかる時間が大きいという問題がありました。 Exponential backoff アルゴリズム によるリトライは backoff ライブラリ を利用し、リトライ対象のエラーは google.api_core.exceptions.ServerError としました。 ソースコード を見ると今回発生した google.api_core.exceptions.InternalServerError や google.api_core.exceptions.Unknown がこのクラスの子クラスであり、他の子クラスも GCP 側のサーバーエラー起因のものであるためリトライする方が良いと判断したためです。 以下が実際の ソースコード のイメージです。(簡易化したものであり、実際のものとは異なります)。 from typing import List import backoff from google.api_core.exceptions import ServerError from google.cloud.aiplatform import MatchingEngineIndexEndpoint from google.cloud.aiplatform.matching_engine.matching_engine_index_endpoint import ( MatchNeighbor, ) # エラーが解消するまでの待ち時間が不明なため、リトライの最大時間は1時間半とした。 # 類似度計算は1回の定期実行あたり約 8 時間かかるため、1時間半の待ち時間は許容する。 @ backoff.on_exception (backoff.expo, ServerError, max_time= 5400 ) def find_neighbors ( index_endpoint: MatchingEngineIndexEndpoint, deployed_index_id: str , queries: List[List[ float ]], num_neighbors: int , ) -> List[List[MatchNeighbor]]: """デプロイされたインデックスで与えられた Embedding に対して近似最近傍探索を実行。 Args: index_endpoint (MatchingEngineIndexEndpoint): Vertex AI Vector Search のインデックスエンドポイントクラス。 deployed_index_id (str): インデックスのデプロイID。 queries: List[List[float]]: Embedding のリスト。 num_neighbors (int): 近似最近傍探索で取得する Embedding 数。 Returns: List[List[MatchNeighbor]]: 類似度トップ `num_neighbors` の id と類似度のリスト。 """ return index_endpoint.find_neighbors( deployed_index_id=deployed_index_id, queries=queries, num_neighbors=num_neighbors ) 実際にこのリトライ処理を入れたことで、その後の運用時に同じエラーが発生することがありましたが、無事にリトライされることで途中で停止せずに実行完了していました。 まとめ 本記事では、類似画像検索システムの内製化におけるシステム面での課題と、それをどのように解決したかについて説明しました。大規模なデータを毎月現実的な時間で処理しなければならない課題を、複数の GCP サービスを組み合わせて高速化することで解決しました。また、複数の GCP サービスを連携してスケジュール実行する必要がある課題を、 Cloud Composer を利用して実装することで解決しました。 今後は他の機能への応用を検討しています。本記事が類似のシステムを構築されている方々の参考になれば幸いです。 明日の記事の担当は UI/UXチーム の飯沼さんです。お楽しみに。 追記:2025/01/21 第48回 MLOps 勉強会で登壇しました! 第48回 MLOps 勉強会 にてこのブログの内容を発表いたしました。勉強会ページには配信 アーカイブ も掲載されておりますため、ご興味ある方はご覧いただけますと幸いです。 speakerdeck.com エニグモ では一緒にデータを利用したサービス価値向上を実現していただけるデータサイエンティストを募集中です!世界178ヶ国に1100万人超の会員を有し、出品数は630万品を超える BUYMA には膨大なデータが蓄積されており、データ活用の余地はまだまだあります。ご興味ある方はカジュアル面談からでもお話できますと幸いです。 他の職種も絶賛募集中です! 株式会社 エニグモ すべての求人一覧 hrmos.co
アバター
こんにちは!フロントエンドエンジニアの張です! この記事はEnigmo Advent Calendar 2024の3日目の記事です。 エンジニアは日頃のタスクを対応するためにいろんなツール駆使していますが、絶対になくてはいけないツールと言いますと、やはり テキストエディタ ー一択だと、私は思っています。 その テキストエディタ ーですが、実は何種類もあって、側から見ると、どれも同じように見えるかもしれませんが、毎日何時間もそれで仕事をするエンジニアにとって、自分に合うエディターを選ぶことは実は仕事の効率とモチベーションに大いに関わっています。 本記事では、ここ約一年半、私が毎日使っている最強エディター Neovim について、ご紹介します。 Neovim とは もしかしたら Neovim を知らない方も多いかもしれませんが、それがエンジニアなら誰しも一回は使ったことがある、名高い Vim エディターをリファクターしたものです。 そのため、 Neovim は Vim の特性と独自の機能を備える、モダンな コマンドライン エディターになっています。 特性 Vim から受け継いだもの ベースが Vim になっている Vim を fork して開発したもの Vim motion Vim command 反応速度が早い ミニマル 独自の特性 Lua でコンフィグ可能 LSPのネイティブサポート 強み Neovim について簡単に説明しましたが、それは一体ほかの主流のエディターと比べて、何の強みがあるでしょう。 Vim Motion Vim Motionというのは Vim 、あるいは Neovim 上で使える、カーソルを移動させたり、テキストを編集したり、できるショットカットです。 Motionを使いこなせると、マウスなしでテキストを高速で編集、ナビゲートすることが可能になります。 それは日々大量なテキスト、あるいはコードを処理するエンジニアにとって、とても貴重なスキルです。 実例 個人的に最も使っている Normal Mode 下の基礎Motionが以下となります Motion 機能 [count] h カーソルを( count 行)左に移動させる [count] j カーソルを( count 行)下に移動させる [count] k カーソルを( count 行)上に移動させる [count] l カーソルを( count 行)右に移動させる [count] w カーソルを次/ count個後の単語に移動させる [count] b カーソルを前/ count個前の単語に移動させる i insert mode に移行して、カーソルを左に一行移動させる a insert mode に移行して、カーソルを右に一行移動させる d [motion] motionの終着点までの文字を削除する x カーソルの位置の文字を削除する y [motion] motionの終着点までの文字を複製する p yで複製した内容をペーストする Text object selection を利用したMotion Motion 機能 ciw カーソルが位置する単語を丸ごと削除して、insert modeに移行する diw カーソルが位置する単語を丸ごと削除する dap カーソルが位置する段落を削除する yap カーソルが位置する段落を複製する 使えるMotionは実際まだまだありますが、量が多いのもありますし、 公式ガイド を参考した方が的確なので、ここでは割愛させていただきます。 効率的で楽しい 以上の実例を見ると、 Vim / Neovim が高速なテキスト編集を実現できることがわかると思いますが、それは実は私にとって、一番大事なメリットではありません。 では、一番のメリットは何でしょうか! 楽しい! そう!楽しくテキストを編集できること自体が一番のメリットだと思います。 よく見ますと、 Vim Motionってゲームのコンボに見えないでしょうか? Vim / Neovim なら一日中コードを書いても退屈することはありません! それがエンジニアのモチベーションを引き上げて、効率を改善できることを私はこの一年半ですごく実感できました。 PDE(Personalized Development Environment) Neovim は一部のエンジニアから、 PDE 、つまり 個人的開発環境 とも呼ばれています。 それは Neovim のコンフィグ自由度がとても高くて、ユーザーがそれを思い通りに自分好みにできるからです。 具体的に言うと、UIの表示、文字を打つ時のエフェクト、ユーザー入力への反応など、その気になれば全部自分で実装できます。 結果として、ユーザーは自分のワークフローにピッタリな開発環境を構築できて、開発の効率を大幅に上げられます。 効率改善以外、PDEはユーザーのモチベーションの向上にもつながると思います。 なぜかと言うと、エンジニアが仕事のタスクをPDEで対応することで、自分が設計したツールをテストできるからです。 開発が好きなエンジニアなら、それだけで仕事へのモチベーションが大幅に上がるでしょう。 多彩な プラグイン 近年、開発を支援したり、ワークフローをスムーズにしたり、できる プラグイン がプログラミングエディターの必要不可欠な要素だと思われる傾向が強まりつつあります。 当然、 プログラマー のためのエディターである Neovim にも充実した プラグイン エコシステムがあります。 ここでは、重点的に、何個か人気な プラグイン についてご紹介します。 Telescope Telescopeは Neovim コアの機能を利用した、高性能な Lua 製 Fuzzy Finder です。 簡単に言うと、指定した範囲以内のコンテンツをキーワードで検索するツールです。 例えば、コードベース内のファイルを大まかな文字列で検索したり、特定な文字列がコードベースのどこに記載されているかを探したり、することが可能です。 それだけでも結構便利なツールなんですが、もっとすごいのはTelescopeの拡張性です。 Neovim と同様に、Telescopeは拡張性に重きを置いてデザインされました。 そのため、Telescopeの既存機能を利用して、新しい機能を作成することも比較的に簡単にできます。それに、純 Lua 製の プラグイン であるため、 Lua でカスタマイズするのもとても便利です。 もちろん、原作者と他のコミュニ ティー メンバーによって、もうすでにたくさんの 拡張機能 が作られたので、まだ自分で機能を作成したくないユーザーでも充実した既存機能を利用できます。 私がよく使っている機能をリストアップすると、以下となります。 builtin.buffer 開いたバッファの中からファイルを検索する builtin.find_files コースベース内のファイルを検索する builtin.live_grep コースベース内で特定の文字列やパターンを検索する 詳細に興味がある方はぜひ 公式GitHubページ をご覧ください! LSP(Language Server Protocol) 関連 プラグイン Neovim と Vim の違いの話になると、ビルトインのLSPサポートの有無が必ずといっていいほど話題に出されます。実際、LSPのサポートを目当てで Neovim に移行したユーザーも結構います。 では、LSPとは一体何なのでしょうか? 一言で言うと Microsoft 社が開発した、エディター/ IDE とLanguage Serverの間のコミュニケーションを可能にする プロトコル です。 Visual Studio Code などのエディターがコードのオートコンプリート、定義元への移動、コードの診断(Diagnostic) ができるのもLSPのおかげです。(LSPは元々 Visual Studio Code のために作られています) 詳細は こちらの公式サイト で閲覧できるので、興味がある方はぜひご確認ください。 前にも話した通り、 Neovim にもそのLSP機能を搭載されています。そして、それを中心に作られた プラグイン もたくさんあります。それらも利用すれば、簡単かつ自由度が高いLSP設定ができるため、 Neovim でのLSP体験はいろんなエディターの中でもトップクラスです。例えば、Language Serverから受信した情報に基づいて、画面上にコードに関する情報を表示するのも簡単にできます。 複雑だと思うかもしれませんが、以下のLSP プラグイン を利用すれば、大体のLSP設定ができます。 nvim-lspconfig いろんなLanguage Serverのデフォルトコンフィグを提供する mason.nvim Neovim 用のLanguage Serverパッケージマネージャー 簡単に主流のLanguage Serverをインストールできる mason-lspconfig nvim-lspconfig と mason を繋げるもの LSPを管理する mason. vim 今ではLSPを利用するエディターも結構増えましたが、設定の手軽さ、自由度、エコシステムの大きさのため、 Neovim を選択するユーザーもまだまだ増えています。 おわりに 以上が私が約一年半 Neovim を使って、未使用の方にも知ってほしい Neovim の見どころでした。 まだまだ説明しきれてないことが多いですが、 Neovim が素晴らしいプログラミングエディターであることが伝われたら嬉しいです! 文章を読むだけではピンと来ないかもしれないので、時間がある時、ぜひ、 Neovim を使ってみてください! 明日の記事の担当は 採用広報担当 の戸井さんです。お楽しみに。 おまけ 初心者が自分で0から Neovim を始めるのは結構ハードルが高いので、自分が最初に使ったリソースを幾つか共有したいと思います。 Neovim公式 kickstart.nvim Neovim のコア開発者の一人が開発した Neovim コンフィグ clone したらすぐ使える コードの説明がとても詳しくて、初心者に優しい vimtutor Vim Motionを勉強できる cli tool vim をインストールすれば、一緒にインストールされる Unix-like のシステムでは大体デフォルトでインストールされている
アバター
こんにちは!Webアプリケーションエンジニアの 川本 です! 最近は BUYMA の出品者向けのチームでパフォーマンス改善に取り組んでおります。 この記事は Enigmo Advent Calendar 2024 の 2日目の記事です。 少し日が経ってしまいましたが、2024年10月16に開催された「Datadog Summit Tokyo 2024」に参加してきました。 www.datadoghq.com 直近の業務でパフォーマンス改善に取り組む機会が多かったのですが、その際にオブザーバビリ ティー の向上を支えてくれる、Datadogに興味を持ちました。開発エンジニアにとってもこれらを活用できると視野が広がると感じ参加を決めました。 印象に残ったセッション Datadog ダッシュ ボードで 見える化 する、新たなビジネス価値創造のチャンス www.datadoghq.com このセッションでは、リリースした機能が意図通りに使われているかを分析し、期待されるスコアを可視化する ダッシュ ボードの活用例が紹介されました。 ダッシュ ボードから課題を発見し、改善につなげることでビジネス価値を創造していくという内容でした。 特に印象に残ったのは、エンジニアがビジネス観点を持つ重要性です。 ダッシュ ボードを作成するためには、案件の目的や必要なデータを理解する必要があり、それがビジネス観点を養うきっかけになると感じました。 弊社でも新機能をリリースする際に、エンジニアが ダッシュ ボードを準備し、ビジネス側と連携しながら価値創造に貢献していきたいです。 開発者の生産性向上 www.datadoghq.com このセッションでは、Datadogを活用した開発生産性向上に関するパネルディスカッションが行われました。 最も印象に残ったことは、 Wantedly 社の市古さんがおっしゃっていた「オブザーバビリティは開発を加速させる」という点です。 www.wantedly.com 大規模 リファクタリング やライブラリのバージョンアップ等を行った際の影響範囲はとても広いので、ステージング環境でなるべく確認しようとしても、全てを完璧に把握することは難しいことが多いかと思います。そのため、本番環境にリリースする際はどうしても慎重になりスピード感が損なわれてしまいます。 しかし、オブザーバビリティを向上させると、問題が発生しても素早く発見・対応できるという安心感が得られます。これにより、適切なリスクを取れるようになり、レビュー 工数 の削減や認知負荷の軽減につながるため、スピードと品質の両立が可能になるという内容はとても納得できました。 弊社で運営しているサービスの「 BUYMA 」も歴史の長いサービスで、これから大規模リプレイス、大規模 リファクタリング 、ライブラリの大幅なバージョンアップを控えております。その際に今回学んだことを活かして、オブザーバビリティを向上させながら開発生産性を維持しながら問題と向き合っていきたいです。 導入したいDatadogの機能 今回のサミットを通して様々なDatadogの機能について知ることができました。 その中でも自社で導入したいと考えている機能は Datadog Continuous Profiler です。 www.datadoghq.com Datadog Continuous Profilerとは? Datadogの公式ページでは以下のように紹介されております。 Datadog Continuous Profiler を使用すると、最小限のオーバーヘッドでスタック全体にわたって本番環境でのコードパフォーマンスを分析できます。コードプロファイリングを利用して、アプリケーションで最もリソースを消費するメソッドまたはクラスをすばやく検出および最適化できます。これにより、コードの効率が向上し、 クラウド プロバイダーのコストが削減されます メソッド単位でどれだけ時間がかかっているかや、CPU時間、メモリ使用量等のリソースの消費量が可視化されるので、パフォーマンスの ボトルネック を発見するのに最適です。 導入したい理由 この機能を導入したいと思ったのは、弊社で運用されはじめて長年経過した機能のパフォーマンス改善に取り組んだことがきっかけでした。 私は ボトルネック を見つけるのにまず該当するコード全体を読んでいたのですが、これは根拠のない推測をしているだけで効率が悪かったなと反省しております。 そういった際に Continuous Profiler で計測してコードレベルで可視化して ボトルネック になっている箇所を明確にし、修正の目処を立てるといったことができていればより効率的かつ効果的なアプローチがとれていたなと感じております。 おわりに 今回のイベントを通してDatadog、オブザーバビリティに関して新たな知見を得ることができたのと同時によりこの分野に興味を持つことができました。 今回ワークショップには参加できなかったので Datadog Learning Center で興味のある講座を受講して実際に手を動かしてDatadogに関する知見を深めていこうと思います。 最後に、帰り際にいただいたDatadogのかわいいグッズも大切に使わせていただきます! 運営の皆様、素晴らしいイベントをありがとうございました! 明日の記事の担当は フロントエンドエンジニア の張さんです。お楽しみに。 株式会社 エニグモ すべての求人一覧 hrmos.co
アバター
こんにちは!WEBアプリケーションエンジニアの 川本 です! この記事はEnigmo Advent Calendar 2024の1日目の記事です。 弊社の運営する BUYMA では、社内システムよりタイムセールを毎週 約100万商品 に設定しています。しかし従来のシステムでは、この処理に 約100時間 もかかっており、運用負荷が大きな課題となっていました。本記事では、パフォーマンス改善によって処理時間を 約5時間 に短縮し、運用効率を向上させた事例をご紹介します。 タイムセール設定機能について 処理フロー タイムセール設定機能はざっくり以下のような処理フローになっています。 CSV ファイルのアップロード タイムセール設定依頼情報を記載した CSV ファイルを S3 にアップロードします。 SQSへのエンキュー S3にファイルがアップロードされると、SQSにメッセージがエンキューされます。 Redisに保存 常駐しているデーモンがSQSキューをポーリングし、SQSから取得したメッセージをRedisに保存します。 Sidekiqジョブの実行 Redisに保存されたメッセージをSidekiqがデキューして、タイムセール設定の処理を実行します。 性能 タイムセール設定を行うSidekiqの性能は以下の通りです。 専用プロセスの使用 タイムセール設定ジョブは専用のSidekiqプロセスで実行。 並列処理数 1プロセスあたりの並列処理数は10。 冗長化 2台のサーバー構成で冗長性を確保。 設定例(sidekiq.yml) --- :labels : - default :concurrency : 2 :pidfile : tmp/pids/sidekiq.pid :logfile : ./log/sidekiq.log production : :concurrency : 10 :queues : - [ setting_timesale, 1 ] 課題の発見 従来の仕組みでは、タイムセール設定依頼の CSV ファイル1つを1スレッドで処理していました。これにより、以下のような問題が発生していました。 スレッド活用不足 Sidekiqはマルチスレッド対応で高い並列処理性能を持っていますが、1つのスレッドが1つの CSV を丸ごと処理していたため、マルチスレッドであること効果的に活用できていませんでした。 長時間実行による運用負荷 1つの CSV には約100万件の商品データが含まれており、これを1スレッドで処理することで、処理時間が長時間に及び、運用上現実的でない時間になっていました。 CSV 分割の運用負荷 スレッドを有効活用するには、 CSV をあらかじめスレッドの数だけ手動で分割する必要があり、これが運用上の手間となっていました。 改善アプローチ 上記の課題を解決するため、システム側で1つの CSV データをバッチ単位(100商品)で分割し、各バッチを複数スレッドで並列処理する方式に変更しました。これにより、処理の効率化と運用負荷軽減を同時に実現することが可能になりました。 改善後の処理の流れ 親ジョブ が CSV データをバッチ単位に分割し 子ジョブ を作成・エンキューする。 子ジョブ が実際のタイムセール設定処理を実行する。 監視ジョブ が 子ジョブ の進捗を追跡し、全て完了したら設定完了の処理を実行する。 登場してきたジョブについて↓ 親ジョブ CSV ファイルから取得したタイムセール設定依頼データをバッチ単位に分割。 各バッチについて 子ジョブ をエンキューし、ジョブIDを記録。 最後に 監視ジョブ をエンキューし、 子ジョブ の進捗を監視。 実装例↓ module SidekiqWorker # 親ジョブ class Parent include Sidekiq :: Worker sidekiq_options queue : :setting_timesale BATCH_SIZE = 100 def initialize (args) # 省略 end def perform child_job_ids = [] # バッチ単位でタイムセール依頼データを分割 timesale_request_data.each_slice( BATCH_SIZE ) do |batch| # バッチ単位でタイムセール設定を行う 子ジョブ をエンキュー child_job_ids << SidekiqWorker :: Child .perform_async(batch) end # 子ジョブの進捗状況を管理する 監視ジョブ をエンキュー SidekiqWorker :: Monitoring .perform_async(child_job_ids) end private # s3からタイムセール設定依頼データを取得 def timesale_request_data # 省略 end end end 子ジョブ バッチ単位(100件)の設定依頼に対してタイムセール設定の処理を実行。 ジョブの進捗状況をRedisに記録。 子ジョブの状態管理に sidekiq-status というgemを使用しました。 github.com sidekiq-status は、Sidekiqで実行中のジョブの状態を追跡するためのGemです。ジョブの状態(例: queued, working, completeなど)をRedisに保存し、進捗をリアルタイムで確認できるようになります。 module SidekiqWorker # 子ジョブ class Child include Sidekiq :: Worker include Sidekiq :: Status :: Worker sidekiq_options queue : :setting_timesale # 24時間ジョブの状態をRedisに保持する def expiration @expiration ||= 60 * 60 * 24 end def perform (timesale_request_data) SetTimeSaleService .new(timesale_request_data).call end end end 監視ジョブ 子ジョブの進捗を追跡し、全ての子ジョブが完了した場合に完了処理を実行 追跡できない子ジョブが存在した場合はアラートを飛ばす。 sidekiq-statusの API よって以下のように子ジョブの状態を確認することができます。 job_id = SidekiqWorker :: Child .perform_async # :queued, :working, :complete, :failed or :interrupted, nil after expiry status = Sidekiq :: Status ::status(job_id) # <- ジョブ状態を確認 Sidekiq :: Status ::queued? job_id # <- キューにあるか? Sidekiq :: Status ::working? job_id # <- 実行中か? Sidekiq :: Status ::retrying? job_id # <- リトライ中か? Sidekiq :: Status ::complete? job_id # <- 完了したか? Sidekiq :: Status ::failed? job_id # <- 失敗したか? Sidekiq :: Status ::interrupted? job_id # <- 中断されたか? 実装例↓ module SidekiqWorker # 監視ジョブ class Monitoring include Sidekiq :: Worker include Sidekiq :: Status :: Worker sidekiq_options queue : :setting_timesale SLEEP_TIME = 15 def perform (job_ids) @job_ids = job_ids loop do @job_ids .reject! do |job_id| job_complete?(job_id) end break if @job_ids .empty? sleep SLEEP_TIME end TimeSaleSettingCompletionService .new(args).call end private # ジョブが完了したか? def job_complete? (job_id) Sidekiq :: Status .complete?(job_id) end end end 学んだこと 性能要件の見直しの重要性 長年運用されているシステムは、リリース当時の性能要件がそのまま適用されていることが少なくありません。しかし、システムの利用状況や運用環境は時間の経過とともに変化します。今回の事例でも、リリース当初は妥当だった処理速度が、現在では運用負荷を引き起こす大きな要因になっていました。 そこで重要なのは、現状の運用方法をしっかりと把握し、必要に応じて性能要件を再定義することです。運用者に ヒアリ ングを行い、現在の問題点を明確にすることで、改善に向けた具体的な指針を得ることができます。 並列化によって増す複雑性 並列化はシステムの処理速度を大幅に向上させる一方で、複雑性を増す側面があります。一連の処理が複数のジョブに分散されるため、それぞれのジョブの状態を適切に管理する必要が生じます。今回の事例でも、Sidekiqを用いた並列化に伴い、以下のような課題が明らかになりました。 ジョブの状態管理コスト 並列化することで、ジョブの進捗や完了状態を追跡する仕組みが必要になります。このため、Redisを活用したジョブの状態管理が不可欠となりましたが、それには追加の開発コストと運用リスクが伴います。 ジョブの状態欠損のリスク 例えば、Redisに障害が発生した場合、一部のジョブの状態が欠損する可能性があります。このリスクを考慮し、ジョブの再実行や障害時の リカバリ ープロセスを検討する必要があります。 並列化の インパク トは大きいですが、システムの複雑性は増してしまいます。まずは 処理内容自体のパフォーマンス改善 に目を向けることが大切です。今回のプロジェクトでは、並列化に先立ち、子ジョブ内で実行されるタイムセール設定処理の最適化を行いました。 おわりに パフォーマンス改善は、単に処理を「速くする」ことが目的ではなく、システムの要件や複雑性を考慮し、最適なバランスを見つけることが重要だなと思いました。 今回学んだことを活かしてこれからもパフォーマンス改善に取り組んでいきたいです! 株式会社 エニグモ すべての求人一覧 hrmos.co
アバター
こんにちは、データサイエンティストの髙橋です。業務では企画/分析/ 機械学習 モデル作成/プロダクション向けの実装/効果検証を一貫して行っています。 この記事では類似画像検索システムを内製化したことで、既に社内で利用していた他社製のものと比較して精度を維持しながらコスト削減が実現できたことについて紹介します。 なお、類似画像検索システムの 機械学習 部分と システム開発 部分( GCP を利用した処理の高速化)との2つに分けて紹介します。今回は前者の 機械学習 部分についての記事となります(後者の システム開発 部分の記事は こちら )。 内製化の目的 内製化による事業インパクト 内製化成功のポイント BUYMA における類似画像検索の課題 類似画像検索方法の概要 類似画像検索各ステップの詳細 商品画像内の物体存在箇所をセグメンテーションし切り抜き 切り抜いた画像の Embedding 計算 Embedding により画像同士の類似度計算 画像ハッシュにより画像同士の類似度計算 Embedding 類似度と画像ハッシュ類似度を組合せ同一商品の可能性判定 まとめ 内製化の目的 弊社が運営している CtoC EC サービス BUYMA は、CtoC という特性上同じ商品が異なる出品者から出品されている場合が多いです。そのため、どの商品が今売れ筋なのか、その商品のサイト全体での在庫状況はどうなのか、価格差はどの程度あるのかなどを正確に把握するには異なる出品者から出品されている同じ商品の 名寄せ が必要です。 それを実現するために、商品画像同士の類似度を計算し一定以上のものを人手で アノテーション することで商品の 名寄せ を社内で行っていました。ここで商品画像同士の類似度を計算するために他社製の類似画像検索システムを利用していました。しかし、その費用がそれなりに高く、内製化することでコスト削減を実現したいというのが目的でした。 内製化による事業 インパク ト 内製化したシステムは既に稼働しており、これまでと比較して精度は維持したまま年間数百万円規模のコスト削減が見込まれています。 加えて、他社製の類似画像検索システムではコストが高く断念していた EC サイトでの購入者向けの画像を起点にしたレコメンドや、画像で類似商品を検索できる機能なども検討出来るようになりました。 内製化成功のポイント 以下2点が内製化成功のポイントであったと考えています。 ト レーニン グ不要で十分な精度が出る 機械学習 モデルの登場を待ち、それを利用したこと 精度と実現スピードのバランスを考えビジネスで活用できるレベルの結果を素早く出したこと 1点目について、私の入社前から社内では 名寄せ に対する要望があり 機械学習 モデル開発を検討したことがあったようですが、 BUYMA には数百万件以上の膨大な商品があり 機械学習 モデルのト レーニン グ・評価の イテレーション に時間がかかる上に中々期待する精度までは至らずストップしている状態でした。 そこで、どうにかして精度を上げる方法は検討せず、世の中では大規模な深層学習モデルの開発が活発であったため、何か利用できそうなものがないか定期的に調査していました。すると、 Contrastive Language-Image Pre-Training (CLIP) や Segment-Anything Model (SAM) のようなト レーニン グ不要で高精度に画像の類似度計算やセグメンテーションが出来るモデルが登場し、このタイミングであれば時間をかけすぎずに内製化出来るのではないかと思い PoC をスタートさせました。 2点目については、精度が期待する水準に至らない場合に、 Fine-Tuning のような時間がかかることよりも先に、後述する簡易な画像処理 アルゴリズム を利用して運用上問題ない精度まで向上させました。これにより、この PJ はほぼ1人での担当でしたが企画から本番運用まで約1年で完遂することが出来ました。おそらくですが、あまり時間をかけすぎてしまうとビジネス上の課題の優先度の変化などから ペンディング する必要性が生じた可能性が高かったと考えています。 BUYMA における類似画像検索の課題 具体的にどのような方法で類似画像検索を実現したかの前に、 BUYMA における類似画像検索の課題について説明します。 BUYMA の商品画像は出品者の方が自由に設定できるものであり、商品単体よりも様々なものをコラージュしている場合が多いです。例えば、色違い/角度違いの商品、着用画像や文字などをコラージュしていることが多いです。 そのため、単純に何らかの手法で画像同士の類似度を計算しても、同じ商品の画像にも関わらず類似度が低く出てしまう課題がありました。 類似画像検索方法の概要 課題に対応するために、画像から商品部分を切り抜いてその部分同士で画像類似度を計算する方法を考えました。はじめは Object Detection が出来る深層学習モデルをト レーニン グし、それにより商品部分を検知し切り抜く方法を考えましたが、 BUYMA には様々な商品がありラベル作成の手間がかかる懸念がありました。 そこで、企画検討当時に発表された Segment-Anything Model (SAM) というト レーニン グ不要で画像から物体をセグメンテーション出来るモデルの利用を検討しました。詳細は後述しますが、 SAM と画像ハッシュを組み合わせることで既存の他社製の類似画像検索システムと同等の精度を実現しました。 類似画像検索各ステップの詳細 類似画像検索は以下のステップに分かれています。 商品画像内の物体存在箇所をセグメンテーションし切り抜き 切り抜いた画像の Embedding 計算 Embedding により画像同士の類似度計算 画像ハッシュにより画像同士の類似度計算 Embedding 類似度と画像ハッシュ類似度を組合せ同一商品の可能性判定 ここで、Embedding とは以下のように画像を数値の列に変換したものです。 似た画像同士が何らかの類似度計算関数(cos 類似度など)で高い値を持つようにト レーニン グされた 機械学習 モデルを利用することで、画像同士の類似度を計算することが出来ます。 各ステップの詳細について説明します。 商品画像内の物体存在箇所をセグメンテーションし切り抜き 課題でも述べた通り、 BUYMA の商品画像はコラージュされている場合が多く、例えば以下のような同じ商品であるが2つの異なる出品者の画像をそのまま Embedding 化し類似度を計算すると 0.496 となり、低い類似度となってしまいました。 そこで、SAM というモデルを利用して商品画像内から商品を含む物体存在箇所をセグメンテーションし切り抜きました。SAMとは、あらゆる物体をセグメンテーションするための汎用的なモデルであり、さまざまな画像に対して物体を抽出することが可能です。大量のデータで訓練されており、Fine-Tuning なしでも高い精度を発揮します。 実際にSAM を利用してそれぞれの画像から物体部分を切り抜くと以下のような画像群が作成されました。この中で黒の財布部分を切り抜けている画像をそれぞれ Embedding し類似度を計算すると0.975 と非常に高い値となりました。 切り抜いた画像の Embedding 計算 前ステップで商品画像から切り抜かれた各画像について、 機械学習 モデルを利用し Embedding を計算します。ここでもラベル作成やト レーニン グの手間を削減するために、事前学習済みの深層学習モデルを利用しました。 事前学習済みモデルとしては、 Japanese-CLIP(Contrastive Language-Image Pre-Training) を利用しました。 CLIP とは、大量の画像とテキストの対応関係を学習することにより、画像と 自然言語 の両方での検索や類似度計算が可能なモデルです。このモデルを選んだ理由は、PoC で実際の BUYMA 画像で精度を検証してみて問題なさそうであったのと、日本語での 自然言語 による画像検索の拡張性も考慮してのことです(CLIP ベースのモデルであるため、 自然言語 と画像の類似度も計算可能です)。 しかし、ある程度開発が進んだ段階で open_clip のいくつかの学習済みモデルと比較した結果、より精度が良いモデルがあり場合によってはそちらを採用しても良かったかもしれないです。今後、さらに精度向上が必要な場合には再度検証を行う予定です。 Embedding により画像同士の類似度計算 前ステップにより1商品画像に対して複数の Embedding が作成されます。画像同士の類似度を1つの値として算出するために、商品内の切り抜かれた部分の Embedding の全ての組み合わせの類似度を計算しその中の最大の値を画像同士の類似度としました。 前の説明で用いた商品画像を例とすると、以下のように切り抜かれた部分同士の全ての組み合わせの類似度を計算し、その中の最大の値(ここでは黒の傾いていない財布部分同士の類似度が 0.975 で最大であった)をもとの商品画像同士の類似度としました。 この方法を試してみた時点で、既存の他社製の類似画像検索システムに近い精度が出せましたがやや及ばない状態でした。そこで後述する画像ハッシュによる類似度計算も組み合わせることとしました。 画像ハッシュにより画像同士の類似度計算 前述したように Embedding よる類似度計算では既存のシステムに精度がやや及ばない状態であり、その主な原因は商品内の一部分での切り抜き同士で過剰に類似度が高くなってしまうパターンが主でした。 具体的な例としては、以下のように商品の一部と背景部分のみを切り抜いてしまい、それら画像同士の類似度が 0.9 以上と高くなるケースがありました。 これを防ぐために、Embedding による画像同士の類似度が一定以上である組み合わせについて、画像ハッシュによる類似度も計算しました。画像ハッシュとは、画像の視覚的特徴を数値化し簡略化した表現で画像を比較する手法です。これにより、細部の違いや大まかな構造の類似性を把握できます。例えば、 Average Hash (aHash) では大まかに言うと画素値が平均よりも高いかで2値化して比較を行います。 実際に上記のようなケースにおいて画像ハッシュによる類似度を計算すると非常に高い値(画像ハッシュでは値が高いほど類似度が低い)となりました。 Embedding 類似度と画像ハッシュ類似度を組合せ同一商品の可能性判定 Embedding による類似度と画像ハッシュによる類似度を組み合わせた しきい値 を設定し、それを超えた商品画像同士のペアを同一商品である可能性が高いと判定するようにしました。既存の他社製のものでも同様に類似度の しきい値 が一定以上を同一商品の可能性が高いとしていました。 しきい値 は、既存のものと判定数をおおよそ同じにした方が移行後の運用がしやすいと考え、そうなるように決めました。また、このとき既存のものとの比較も行い精度に大きな乖離がないことを確認しました。具体的には、 しきい値 調整用のデータ(約704億の画像ペア)とテスト用のデータ(約1785億の画像ペア)を用意し、既存のものでの判定結果を正としたときに Precision 、 Recall を計算したところ以下の値でした。 Precsion Recall しきい値 調整用のデータ 0.7 0.72 テスト用のデータ 0.8 0.77 Precision 、 Recall を悪化させる要因である既存システム/内製化システムでのみ同一の可能性が高いと判定された画像ペアをそれぞれ目視で確認してみると、既存システムでのみ同一と判定されたペアにも実際には異なる商品同士があったり、内製化システムでのみ判定されたペアには実際に同一であるペアがあったりし、既存の外部ツールでも誤検知や見逃しが多くありました。 また、人手で アノテーション いただく方々に既存システムと内製化システムを一定期間並行運用していただき、運用上の精度も問題ないことを確認いただいたため、現在内製化システムに移行しています。 まとめ 内製化により精度を維持しながら大幅なコスト削減を実現し、さらなる類似画像検索機能の活用可能性も広がりました。 次回は、 GCP のマネージドサービスを利用して推論方法の各ステップを高速化した システム開発 部分の詳細を紹介する予定です。実運用では数十〜数千億規模の画像の組合せについて定期的に類似度計算する必要があり、高速化も非常に重要なポイントでした。 株式会社 エニグモ すべての求人一覧 hrmos.co
アバター
こんにちは、 エニグモ 嘉松です。 BUYMA のプロモーションや マーケティング を担当している事業部に所属しており、その中のデータ活用推進室という部署で会社のデータ活用の推進や マーケティング ・オートメーションツール(MAツール)を活用した販促支援、 CRM などを担当しています。 さて、SELECT文で得た結果のデータを(そのまま、直接)テーブルに挿入する INSERT SELECT や、SELECT文で得た結果からテーブルを作成して更にデータまで挿入する CREATE TABLE AS は何かと便利な機能(文法、技?)ですが、 WITH句 (SELECT文による結果を一時的に名前を付けてテーブルのように利用する便利な機能(文法、技?))と併用、一緒に利用しようとした時に、ちょっとした注意点があるので備忘録として記載しておきたいと思います。 例えば、以下のようなにWITH句で複数の(仮想的な)テーブルを定義して、それらを結合(JOIN)して結果を得るような SQL があったとします。(サンプルなのでとてもシンプルな SQL にしていますが、通常では WITH句 を使う場合はもっと複雑な SQL になることが多いと思います。) ※以下の SQL はBigQueryで検証していますので、他の DBMS では異なる結果やエラーになる場合があることをご了承ください。 WITH emp AS ( SELECT 7369 AS empno, ' SMITH ' AS ename, 20 AS deptno, UNION ALL SELECT 7499 AS empno, ' ALLEN ' AS ename, 10 AS deptno, UNION ALL SELECT 7521 AS empno, ' WARD ' AS ename, 30 AS deptno, ), dept AS ( SELECT 10 AS deptno, ' DEVELOPMENT ' AS dname, ' MAYNARD ' AS loc UNION ALL SELECT 20 AS deptno, ' SALES ' AS dname, ' HOUSTON ' AS loc UNION ALL SELECT 30 AS deptno, ' RESEARCH ' AS dname, ' PALO ALTO ' AS loc ) SELECT e.empno, e.ename, e.deptno, d.dname, d.loc FROM emp e JOIN dept d ON e.deptno = d.deptno ; この SQL の結果は以下のようになります。 empno ename deptno dname loc 7369 SMITH 20 SALES HOUSTON 7499 ALLEN 10 DEVELOPMENT MAYNARD 7521 WARD 30 RESEARCH PALO ALTO この SQL の結果を INSERT SELECT でテーブルに挿入しようとした時に、うっかり以下のような SQL を書くとエラーになります。 WITH emp AS ( SELECT 7369 AS empno, ' SMITH ' AS ename, 20 AS deptno, UNION ALL SELECT 7499 AS empno, ' ALLEN ' AS ename, 10 AS deptno, UNION ALL SELECT 7521 AS empno, ' WARD ' AS ename, 30 AS deptno, ), dept AS ( SELECT 10 AS deptno, ' DEVELOPMENT ' AS dname, ' MAYNARD ' AS loc UNION ALL SELECT 20 AS deptno, ' SALES ' AS dname, ' HOUSTON ' AS loc UNION ALL SELECT 30 AS deptno, ' RESEARCH ' AS dname, ' PALO ALTO ' AS loc ) INSERT dataset.emp_dept -- 最終的な結果を得るSELECTの直前に記載 SELECT e.empno, e.ename, e.deptno, d.dname, d.loc FROM emp e JOIN dept d ON e.deptno = d.deptno ; BigQueryでは以下のようなメッセージが表示されます。 Syntax error: Unexpected keyword INSERT at [39:1] 最終的に結果を得るSELECT文の前に INSERT を記載するという、ごく自然な、直感的な、あたかも正しそうな方法ですが、エラーとなります。 正しくは、以下のようにWITH句の前に INSERT を記載する必要があります。 INSERT dataset.emp_dept -- WITH句の前にを記載する必要がある WITH emp AS ( SELECT 7369 AS empno, ' SMITH ' AS ename, 20 AS deptno, UNION ALL SELECT 7499 AS empno, ' ALLEN ' AS ename, 10 AS deptno, UNION ALL SELECT 7521 AS empno, ' WARD ' AS ename, 30 AS deptno, ), dept AS ( SELECT 10 AS deptno, ' DEVELOPMENT ' AS dname, ' MAYNARD ' AS loc UNION ALL SELECT 20 AS deptno, ' SALES ' AS dname, ' HOUSTON ' AS loc UNION ALL SELECT 30 AS deptno, ' RESEARCH ' AS dname, ' PALO ALTO ' AS loc ) SELECT e.empno, e.ename, e.deptno, d.dname, d.loc FROM emp e JOIN dept d ON e.deptno = d.deptno ; 同じように CREATE TABLE AS においても、WITH句の前に CREATE TABLE AS を指定する必要があります。 CREATE TABLE dataset.emp_dept AS -- WITH句の前にを記載する必要がある WITH emp AS ( SELECT 7369 AS empno, ' SMITH ' AS ename, 20 AS deptno, UNION ALL SELECT 7499 AS empno, ' ALLEN ' AS ename, 10 AS deptno, UNION ALL SELECT 7521 AS empno, ' WARD ' AS ename, 30 AS deptno, ), dept AS ( SELECT 10 AS deptno, ' DEVELOPMENT ' AS dname, ' MAYNARD ' AS loc UNION ALL SELECT 20 AS deptno, ' SALES ' AS dname, ' HOUSTON ' AS loc UNION ALL SELECT 30 AS deptno, ' RESEARCH ' AS dname, ' PALO ALTO ' AS loc ) SELECT e.empno, e.ename, e.deptno, d.dname, d.loc FROM emp e JOIN dept d ON e.deptno = d.deptno ; 考え方としては、 WITH句はあくまでもSELECT文の一部である。(WITH句も含めてSELECT文) INSERT SELECT 、 CREATE TABLE AS の後にはSELECT文を記載する必要があるので、その SQL の一部であるWITH句も同じく INSERT SELECT 、 CREATE TABLE AS の後に記載する必要がある。 とうことでしょうか。 以上です。 株式会社 エニグモ すべての求人一覧 hrmos.co
アバター
こんにちは、インフラグループ Kubernetes チームの福田です。 突然ですが、Webアプリケーションでユーザの認証にOIDCを使うことはよくあると思います。 弊社でも様々な箇所でOIDCが利用されてます。 自社で開発しているWebアプリケーションや最近のログイン機能を持つ OSS の多くは、OIDC Providerさえ用意すればOIDCを利用することができます。 しかし、現実的にはログイン機能を持たない OSS のWebアプリケーションでOIDC認証を使いたいケースや自前で開発したWebアプリケーションにおいてもわざわざOIDCのクライアント機能を追加実装するのが面倒なケースがあります。 そんな時に使えるのがOAuth2Proxyです。 OAuth2Proxyはリバースプロキシとして動作しながら、OIDCの認証をしてくれます。 具体的にはクライアントからのアクセスに対してOIDCの認証を行い、認証されたクライアントからのアクセスのみをバックエンドに通過させるといったことが可能です。 サンプル構成の構築 サンプル構成を通して Kubernetes 上での構築方法を紹介していきます。 サンプル構成ではOAuth2Proxyのバックエンドにnginxを利用し、OIDCプロバイダにはoktaを使いたいと思います。 okta(OIDC Provider)の設定 oktaのApplicationを作成します。 Sign -in methodは OIDC を選択し、Application typeとして Web を選択します。 Grant typeでは Authorization Code と Refresh Token を選択します。 Login Redirect URIs はお使いの環境に合わせて設定してください。(ここでは https://corp.example.com/oauth2/callback としておきます。) また、作成後に生成された Client ID と Client Secret をメモしておきます。 最後にユーザのアクセス許可設定をします。 シークレット情報の作成 シークレット情報をSecretとして保存します。 kind : Secret apiVersion : v1 metadata : name : my-credential type : Opaque data : client_id : ********** client_secret : ********** cookie_secret : ********** client_id と client_secret は"okta(OIDC Provider)の設定"のところでメモした値を使います。 cookie_secret にはランダムな値を使います。 Podの作成 OAuth2Proxyとnginxを構築します。 apiVersion : apps/v1 kind : Deployment metadata : labels : app : sample name : sample spec : replicas : 1 selector : matchLabels : app : sample template : metadata : labels : app : sample spec : containers : - name : redis image : redis volumeMounts : - name : cache mountPath : /data - name : nginx image : nginx - name : oauth2-proxy image : bitnami/oauth2-proxy ports : - name : oauth-proxy containerPort : 80 args : - --http-address - 0.0.0.0:80 env : - name : OAUTH2_PROXY_UPSTREAMS value : http://localhost/ - name : OAUTH2_PROXY_PROVIDER_DISPLAY_NAME value : okta - name : OAUTH2_PROXY_PROVIDER value : oidc - name : OAUTH2_PROXY_OIDC_ISSUER_URL value : https://sample.okta.com/oauth2/default - name : OAUTH2_PROXY_CLIENT_ID valueFrom : secretKeyRef : name : my-credential key : client_id - name : OAUTH2_PROXY_CLIENT_SECRET valueFrom : secretKeyRef : name : my-credential key : client_secret - name : OAUTH2_PROXY_PASS_ACCESS_TOKEN value : 'true' - name : OAUTH2_PROXY_EMAIL_DOMAINS value : '*' - name : OAUTH2_PROXY_REDIRECT_URL value : https://corp.example.com/oauth2/callback - name : OAUTH2_PROXY_COOKIE_SECURE value : 'false' - name : OAUTH2_PROXY_COOKIE_SECRET valueFrom : secretKeyRef : name : my-credential key : cookie_secret - name : OAUTH2_PROXY_SKIP_PROVIDER_BUTTON value : 'true' - name : OAUTH2_PROXY_COOKIE_NAME value : SESSION - name : OAUTH2_PROXY_COOKIE_SAMESITE value : lax - name : OAUTH2_PROXY_SESSION_STORE_TYPE value : redis - name : OAUTH2_PROXY_REDIS_CONNECTION_URL value : redis://localhost startupProbe : initialDelaySeconds : 5 periodSeconds : 5 tcpSocket : port : 6379 volumes : - name : cache emptyDir : {} nginxのPodに対して サイドカー としてOAuth2Proxyコンテナを差し込んでいます。 redisコンテナがありますが、これはOIDCプロバイダーへのアクセスを減らすためのセッション情報のキャッシュとして使ってます。 サンプル構成の完了 Ingress やServiceなどのリソース作成の説明は省略しますが、これで構築は完了です。 対象のURL(この記事の場合は https://corp.example.com )にアクセスするとoktaのサインイン画面が表示されます。 そして、サインインして認証をPASSするとnginxにアクセスできます。 参考リンク OAuth Provider Configuration | OAuth2 Proxy Add Auth to Any App with OAuth2 Proxy | Okta Developer まとめ 今回はOAuth2Proxyについて紹介させていただきました。 本記事を通して構築がとても簡単であることが分かったと思います。 OAuth2Proxyのようなツールを自前で社内開発しているところも多いのではないでしょうか。 メンテナンスコストの理由からそういった社内開発のアプリを OSS へ移行することを検討している場合はOAuth2Proxyは選択肢の1つとしてありかと思います。 株式会社 エニグモ すべての求人一覧 hrmos.co
アバター
article p { text-align: justify; } .entry-content h3 { font-size: 140%; } Hello, this is Fernand from the Global Buyma Team. As an engineer, I am responsible for maintaining the English version of Buyma . I recently had the opportunity to attend an AI conference along with my manager. I’m excited to share our experiences and insights with you. So fasten your seatbelt, relax, and enjoy the journey through the world of AI that we embarked upon. Merhaba! On May 10-11, 2024, I had the privilege of immersing myself in the vibrant intersection of technology and tradition at the two-day AI conference organized by Mad Street Den in Istanbul. This city, which uniquely straddles two continents—Europe and Asia—offered the perfect backdrop for an event that brought together global leaders to explore the forefront of artificial intelligence across various sectors. AI on Global Buyma Global Buyma employs the capabilities of Vue.ai's AI service to curate the most suitable product images for display in search results. The AI evaluates and identifies the best quality image from those uploaded by sellers to represent each product. Bridging Cultures, Advancing Technology The conference kicked off with an insightful tour around Istanbul's historic centre, embodying the perfect blend of cultural heritage and modernity. Then we headed to the Sarnıç, a 1500-year-old cistern lying at the heart of Istanbul, which set the stage for an evening of engaging dialogue. Over dinner, we forged meaningful connections with fellow attendees, delving into spirited discussions about the transformative potential of AI and sharing diverse perspectives on its future impact. ・Dinner at The Sarnıç. Honoring the special guests of the event. Key Highlights from the Conference The following day, we journeyed to the magnificent Sait Halim Pasha Mansion, where leaders from diverse sectors, including pharmaceuticals, e-commerce, and logistics, shared their insights on implementing AI within their industries. Ashwini Asokan and Anand Chandrasekaran, the founders of Mad Street Den, provided a comprehensive overview of how their tools are automating the way businesses operate, shifting the focus from infrastructure to innovation. Then, it was followed by a thought-provoking panel discussion that shed light on the banking industry's cautious stance on migrating to cloud services, highlighting the need for policy changes and compliance measures to ensure security. And much wide array of topics, extending from Zenyum's innovative application of AI in dental health assessments to the exciting reveal of Vue.ai's advanced personalization engine, designed to elevate the shopping experience. Their presentations vividly demonstrated the revolutionary capabilities and the expansive future of AI technologies. ・Panelists delved into AI's transformative impact and its common use cases ・Timo Weiss, Nithya Subramanian, and Sandal Kakkar engaged in a compelling discussion about "Gen AI: Existential Crisis or Competitive Advantage?" Evening Soirée: A Sunset to Remember After absorbing a plethora of information, it was time to let loose. The soirée, set against a breathtaking Istanbul sunset, provided a relaxed atmosphere for us to unwind, dance, and revel in the sumptuous dinner prepared for us. ・Turkish performing their traditional dance, Sufi whirling Looking Ahead for Global Buyma Discussions about these AI applications sparked ideas for enhancing the Global Buyma system, such as implementing auto- tagging based on product page content. Additionally, the personalization engine where AI analyzes shoppers' interactions would be perfect, allowing for dynamically tailored product displays and recommendations to cater to individual preferences. However, some guests noted that the use of AI for language translation in page displays requires further refinement.   Bye for now! ・From right: Ashwini Asokan, CEO & Founder of Mad Street Den, Hibaru Maywood, Head of Global Buyma 株式会社 エニグモ すべての求人一覧
アバター
Argo Workflowsを使ったPersistentVolumeの定期バックアップ こんにちは。 インフラグループ Kubernetes チームの福田です。 今回はPV(PersistentVolume)の定期バックアップシステムについて紹介したいと思います。 PVのバックアップについて PVのバックアップといっても色々とありますが、本記事ではスナップショットの取得を意味します。 スナップショットの取得は CSI が対応していれば、 external-snapshotter を利用することで、CustomResourceを書くだけで実現できます。 extenal-snapshotterを全く知らない方は以下の AWS の記事が概要を掴む参考になるかと思います。 aws.amazon.com バックアップシステムの仕組み 概要 システムの概要は以下のようになっています。 Argo Workflowsが定期的にVolumeSnapshotリソースを作成し、それをGitLabへプッシュする。 GitLabのCIが マニフェスト をバリデーションする。 Argo CDが マニフェスト のあるコード リポジトリ の変更を検出して、それを Kubernetes にデプロイする。 詳細 定期的に実行する処理 定期的な処理の実行はCronWorkflowで行っています。 単にCronJobとせず、CronWorkflowとした理由は、一度だけの実行や、定期処理の一時停止などが GUI で簡単にできるからです。 VolumeSnapshotリソースの生成 VolumeSnapshotリソースの生成を行うアプリケーションは Golang で実装した自前のコンテナアプリになります。 コンテナアプリは前述したCronWorkflowで実行されます。 世代管理機能 コンテナアプリはVolumeSnapshot マニフェスト を生成すると共に、古いVolumeSnapshot マニフェスト を削除するローテーション機能を持っています。 何世代分までのVolumeSnapshot マニフェスト を維持するかは 環境変数 で指定可能になっています。 VolumeSnapshot マニフェスト 間の新旧の比較はVolumeSnapshot マニフェスト 生成時のタイムスタンプを独自の アノテーション に記載し、それを比較することで実現しています。 管理対象フラグ ローテーション管理の対象とする否かのフラグを表現する独自の アノテーション もあります。 原則、コンテナアプリで生成されたVolumeSnapshotはこのフラグが立っています。 手動で作成したVolumeSnapshotやコンテナアプリによって作成された永続的に残しておきたいスナップショットについては、このフラグを下ろすことでローテーションの管理対象から外れて自動で削除されないようにできます。 生成される マニフェスト のサンプル apiVersion : snapshot.storage.k8s.io/v1 kind : VolumeSnapshot metadata : name : SAMPLE_NAME namespace : SAMPLE_NAMESPACE annotations : snapshot-tools.enigmo.co.jp/rotation : "true" snapshot-tools.enigmo.co.jp/rtime : 12345678 spec : source : persistentVolumeClaimName : SAMPLE_PVC volumeSnapshotClassName : SAMPLE_VSC snapshot-tools.enigmo.co.jp/rtime アノテーション は マニフェスト 生成時のタイムスタンプを表現している。 snapshot-tools.enigmo.co.jp/rotation アノテーション は世代管理の対象とするか否かのフラグになっている。 コードプッシュ コンテナアプリはVolumeSnapshot マニフェスト を作成、削除するとその変更をプッシュします。 プッシュ時のオプションでMergeRequest( github でいうPullRequest)を作成し、CIが成功したら自動でMRをマージするようにしています。 CIの実行 MRに対しては手動による マニフェスト のデプロイと同じ条件でCIが周り、問題がなければそれをマージするようになっています。 これにより、システムが生成した マニフェスト コードを特別扱いすることなく、統一したポリシー(例えば、フォーマッタやセキュリティチェック等の マニフェスト に対するバリデーション)を適用できます。 Argo CDによるデプロイ masterにマージされた変更を検出し、それを クラスタ へ自動で適用するようにauto-syncを有効化しています。 また、世代管理機能によってVolumeSnapshot マニフェスト の削除される場合もあるので、auto-pruneも有効化しています。 以下がArgoCD Applicationのサンプルになります。 apiVersion : argoproj.io/v1alpha1 kind : ApplicationSet metadata : name : snapshots spec : generators : - list : elements : - namespace : SAMPLE_NS1 - namespace : SAMPLE_NS2 - namespace : SAMPLE_NS3 template : metadata : name : '{{namespace}}-snapshot' spec : project : SAMPLE-PROJECT source : repoURL : GITLAB_URL targetRevision : master path : SAMPLE_PATH destination : server : K8S_ENDPOINT namespace : '{{namespace}}' syncPolicy : automated : prune : true 純粋なApplicationでなく、ApplicationSetとしている理由は我々は複数ネームスペースの運用をしているためです。 (Argo CDのApplicationは1つのネームスペースしかデプロイ先として指定できない。) まとめ Argo Workflowsを使ったPersistentVolumeの定期バックアップシステムについて紹介させていただきました。 実装方法としてオペレータパターンで開発する選択肢もありましが、cronの一時停止やカウントダウンタイマーなどを GUI で管理できる点からArgo WorkflowsのCronWorkflowを利用する実装を選択しました。 今回の記事が Kubernetes ネイティブな自動化システムの開発の参考になれば幸いです。 エニグモ ではエンジニアを含む各種ポジションで求人を募集しております。 株式会社 エニグモ すべての求人一覧 hrmos.co
アバター
「安心して何度も利用したくなる マーケットプレイス 」を作る!UXデザイングループを紹介 エニグモ でTech職種の採用や、採用広報を担当している 廣島 です。 この記事は、 エニグモ で新入社員向けのオンボーディング研修として実施する 部署紹介プログラム の中で プロダクトマネージャーやディレクター、UI/UXデザイナーが所属するグループであるUXデザイングループマネージャーの山田さんがグループの説明をした内容をまとめた記事です。 グループのミッションや、チーム体制、カルチャー、どのように他チームと連携しながらプロジェクトを推進しているかについて説明します。最後に、 BUYMA サービスやUXデザイングループの今後の展望や、UXデザイングループで仕事を行う魅力についてもお話しします。 ※この記事は Enigmo Advent Calendar 2023 の25日目の記事です※ BUYMA サービスについて まずは、 エニグモ が運営するサービスの BUYMA について説明します。 BUYMA は累計会員数1000万人超えの海外ファッションNo.1の通販(EC)サイトです。 世界中に在住する約20万人のパーソナルショッパー(出品者)から、ファッションを中心とした世界中のアイテムを購入できる「売り手」と「買い手」が個人であるCtoCのグローバル マーケットプレイス です。 主に個人の海外在住の日本人のネットワークを構築して海外から商品を購入することが可能で、多種多様な購入者の要望に答えています。商品の約半分は海外から届きます。 BUYMA が間に入ることで安心安全に海外の買い物ができるプラットフォームとして運営しています。 出品アイテムの特徴としては高級品×希少品の商品のラインナップが充実しており、海外限定デザインや、国内未上陸ブランド、国内完売入手困難品など高付加価値のアイテムを世界中から購入することができます。 BUYMA はファッションを主力にしつつも、インテリア・アウトドア、旅行などカテゴリーを拡大しています。 BUYMA のビジネスモデルの詳細は下記をご覧ください。 https://enigmo.co.jp/business/ UXデザイングループのミッション・業務内容とは? エニグモ で ディレクション を担うグループはUXデザイングループ(以下UXDグループ)と呼びます。 (UXとは「ユーザーエクス ペリエ ンス」のことで、ユーザーが商品やサービスを通じて得られる体験を指します) UXDグループとは名前の通り、ユーザーのPain(痛み)に向き合いながら、サービスをより使いやすく・より良くしていくために解決すべきことを考え、実行するグループです。 上記により、ユーザー体験を向上させ、安心して何度も利用したくなるような マーケットプレイス へ BUYMA を成長させることがミッションです。 さらに、UXDグループは BUYMA サービスのプロダクト側(エンジニア・デザイナー)とビジネス側(MD、 マーケティング ・広告、データ、CS、オペレーション等)のちょうど中間に位置して両者を支えサービスを前進させる役割を担います。 具体的な業務領域は? 実際にどういった業務領域を担当しているかについてお話しします。 BUYMA の新機能・既存機能の改修・新サービスの企画・ ディレクション からUI/UXと幅広く担当します。一般的なWeb業界の言葉でいうと具体的には下記のような業務です。 プロダクトマネジメント プロジェクトマネジメント 制作 ディレクション / 進行管理 各施策・プロジェクトの効果検証 UX視点からの企画提案 / サービスデザイン 定性 / 定量 調査 UIデザイン フロントエンドプログラム(主にインタ ラク ションデザイン部分)の実装 上記からも分かるようにUXDグループは ディレクション を行うグループではあるが、UIデザインやフロントエンドプログラムなどの制作も行っていることが特徴的です。 フロントエンドプログラムは、Reactでの実装などはエンジニア部署のフロントエンドエンジニアが行いますが、インタ ラク ションデザインなど実際に画面上で動く CSS や JavaScript の実装はUXDグループがおこないます。 その為、企画から制作までをシームレスにグループ内で行えユーザーの反応や外部環境に合わせてスピーディーに柔軟に対応することが可能です。 色々言いましたがまとめると業務内容は下記となります。 BUYMA の様々な課題に向き合い最適なスコープでユーザーに価値を提供する ユーザーの反応を定性、 定量 の両側面から分析して企画・施策に繋げる スキルとスキル(企画と開発)の間に立ち仲介・翻訳を行い、場合によっては制作も行う グループの体制について Webサービス における様々なスキルを持ったメンバーが集まっています。特徴としては、ディレクター部門であるが、制作するメンバーもいることです。一般的な職種で言うと、プロダクトマネージャー、ディレクター、 SEO ディレクター、UI/UXデザイナー、フロントエンドエンジニアが所属します。 前項で説明した業務領域全てを1人のメンバーが網羅しているのではなく、それぞれのメンバーの得意分野を活かしながら多岐にわたる領域をカバーし業務を行っています。 さらにグループは下記2つのチームに分かれています。 ・ プロダクトマネジメント チーム : マーケティング戦略 系の施策を担当するチーム ・ UI/UXチーム :UX戦略系の施策を担当するチーム グループの体制を示した図です。 明確にチームが別れているのではなく、UXDグループのマネージャーがUI/UXチームのマネージャーを兼任していることもあり、定例は一緒に行っています。週1回グループの定例の他、プロジェクトベースで進めています。 実際の・ プロダクトマネジメント チームとUI/UIXチームの業務は下記のインタビュー記事をご覧いただくと、よりイメージできるかと思います。 ・ BUYMAのWebディレクター(PdM)紹介/エニグモの魅力は?UXDグループとは?インタビューしました! ・ UI/UXデザイナーインタビュー/ユーザーによりよい体験を届けプロダクト価値の向上に取り組む! 開発フロー・プロジェクトの中でのUXDグループの役割 プロジェクトの開発のフローの中で、UXDグループのメンバーが他職種メンバーと協力しながらどういった役割を担うのかについてお話しします。 プロジェクトや施策に伴う開発はエンジニアやビジネスサイドなど他チームメンバーと連携してプロジェクト型チームで進行します。 開発全体の流れは、UXDグループメンバー(ディレクター)が企画をまとめプロジェクトがスタートします。ディレクターとエンジニアのリードメンバーやビジネスサイドのメンバーで企画をどのようにシステムに落とし込むのかを要件定義します。その後、エンジニアのリードメンバーがシステム設計を担当し、タスクを分解してエンジニ アメンバー を アサイ ンします。 プロジェクトや施策はディレクターから発案されることもあれば、 マーケティング やカスタマーなど他の部門からの提案もあります。 要件定義はプロジェクトの特性により異なりますが、通常は主にディレクターが他の ステークホルダー との ヒアリ ングを通じて要求を洗い出し、システムをどう作るかをエンジニアと共に要件定義を進めるのが主流です。 プロジェクトの目標やKPIの策定もディレクターが中心に行います。各プロジェクトでは、新規機能を開発する際に、「なぜこの新機能を開発するのか」「新機能リリースにより、ユーザーにどのような体験や行動を期待するか」「新機能リリース時の成功の定義は何か」などを、各プロジェクトの ステークホルダー と明確にしています。 最後に サービスの課題や今後の展望、 エニグモ でUXDにジョインする魅力、どういった方がマッチするかについて、マネージャーの山田さんにインタビューしました。 サービスの課題や今後の展望 今期の重点テーマである「 BUYMA サービスをより安心できる体験にする」ことは引き続き進めていきます。安心と一言でいっても様々な側面が存在します。 たとえば、高級ブランドを扱っているため、ユーザーが商品が本物であるか不安に感じることもあります。これに対処するため、来期も偽物の不安を払拭するための施策を推進していきます。 また、ビジネスモデル上、高級品および希少品を扱っていることが強みですが、日用品と比べて購入頻度が低い傾向があります。そのため、サイトに訪れるタイミングが少なくサイトにユーザーが定着しにくく、サービスとユーザーの距離が遠くなることが課題です。 この課題に焦点を当て、ユーザーがサイトに定期的に訪れる動機づけや、サービスとの継続的な関係を築くための取り組みにも注力していきたいと考えています。 UXデザイングループの魅力 プロジェクトごとに各職種のメンバーが アサイ ンされ、小規模チームが結成されます。そのためメンバー、一人ひとりが大きな裁量を持ち、エンジニア、デザイナー、ビジネスメンバーと密に連携し、距離が近く風通しも良く別部門という感じはしません。良い人(互いを尊重し、前向きなメンバー)が多いため、仕事上の人間関係による変なストレスがあまりなく、プロダクト開発方法や体制を共に進化させるマインドが根付いています。 また、グローバル×CtoCサービスという、 BUYMA 独自の仕組みやUXを構築するフェーズに携わることができます。出品者側と購入者側の双方のデータやユーザ―行動に基づくデータドリブンな開発が可能です。プロジェクトの成果を数値(売上、CVRなど)やユーザーの声によって直接実感することができます。 どういった方がマッチするか 経験やスキルも大事ですが、熱意やマインド面、カルチャーへの共感、 エニグモ へのバリューへのマッチを大切にしています。※バリューについての詳細は こちら 業務領域が幅広いため、俯瞰して物事を柔軟にアプローチできる方や、自らイニシアチブを取り推進できる方が活躍できるかと思います。 決まった案件をこなすだけでなく若手から裁量を持って働け、新規機能や新規事業の企画を自ら発案しユーザーに届け、世の中を変える可能性があります。 課題を見つけて積極的にア イデア を提案し進めていく方がマッチすると思います! BUYMA や関連サービスのサービス品質を常に進化させ、国内および世界各国のユーザーがますますサービスを利用して喜びや満足を得られるような機能を、楽しみながら共に考え抜いていただける方にジョインいただけると嬉しいです。
アバター
こんにちは、 エニグモ 嘉松です。 BUYMA のプロモーションや マーケティング を行っている事業部に所属、その中のデータ活用推進室という部署で会社のデータ活用の推進や マーケティング ・オートメーションツール(MAツール)を活用した販促支援、 CRM などを担当しています。 この記事は Enigmo Advent Calendar 2023 の 25 日目の記事です。 はじめに この記事では Google から提供されているBigQueryのオンライマニュアル「関数のベストプ ラク ティス(Best practices for functions)」を試してみた結果を紹介していきます。 「関数のベストプ ラク ティス」(日本語版) https://cloud.google.com/bigquery/docs/best-practices-performance-functions?hl=ja 「関数のベストプ ラク ティス」では、以下の4つのベストプ ラク ティスが紹介されています。 文字列の比較を最適化する 集計関数を最適化する 分位関数を最適化する UDF を最適化する はじめは4つ全てを1つの記事で紹介するつもりでしたが、記事を制作していく中で1つでもそれなりの記事ボリュームがあることが分かったので、読みやすさを重視して1つの記事で1つのベストプ ラク ティスを紹介していくことにしました。 ということで、この記事は「その1」として「文字列の比較を最適化する」を試した結果を紹介していきます。 文字列の比較を最適化する ベストプ ラク ティス 可能であれば、 REGEXP_CONTAINS ではなく LIKE を使用します。 BigQuery では、 REGEXP _CONTAINS 関数または LIKE 演算子 を使用して文字列を比較できます。 REGEXP _CONTAINS は多くの機能を提供しますが、実行に時間がかかります。 REGEXP _CONTAINS ではなく LIKE を使用すると、処理時間が短くなります。 特に、 ワイルドカード 一致など、 REGEXP _CONTAINS が提供する 正規表現 をフルに活用する必要がない場合には処理時間が短くなります。 次の REGEXP _CONTAINS 関数の使用を検討してください。 SELECT dim1 FROM `dataset.table1` WHERE REGEXP_CONTAINS(dim1, '.*test.*'); このクエリは次のように最適化できます。 SELECT dim1 FROM `dataset.table` WHERE dim1 LIKE '%test%'; なるほど。やはり機能の多い関数、複雑なパターンに対応できる関数よりも、単純なことしか出来ない関数の方が処理は軽い(速い)と。言われてみれば当たり前といえば当たり前ではありますが。単純なことしてできない関数が利用できるケースであれば、そちらを使ったほうが良い、 正規表現 による検索が必要ない場合は LIKE を使いましょう、ということですね。そもそも REGEXP_CONTAINS より LIKE を使うことを最初に考えるとは思うけどww( REGEXP_CONTAINS の方が汎用性が高いので常に REGEXP_CONTAINS を使っています、みたいな人は注意が必要です!) 試してみた 検証方法 REGEXP_CONTAINS と LIKE を使ったクエリを実行して比較していきます。 比較する候補となる値は 経過時間 と 消費したスロット時間 の2つ、クエリの「実行の詳細」から取得できます。 それぞれの意味は以下の通りです。 経過時間 クエリが開始されてから完了するまでの時間 サーバの使用状況などによる待機時間も含まれます 消費したスロット時間 「スロットとは、 SQL クエリの実行に必要な演算能力の単位です。」(「BigQueryのコンソールの(?)」より) スロットについての詳細は以下の Google のマニュアル「スロットについて」を参照ください。 https://cloud.google.com/bigquery/docs/slots?hl=ja BigQueryスロットは、BigQueryでSQLクエリを実行するために使用される仮想CPU ということで、ざっくり言うとクエリ( SQL の実行)に使用したCPU使用量です。 今回の検証では 消費したスロット時間 を比較ます。( 経過時間 だと Google のその時のサーバの負荷といった外部要因が加わるため) クエリはそれぞれ5回ずつ実行してその平均値を比較します。 クエリを実行するとクエリの結果がキャッシュされるので、キャッシュを使用しないように設定を行います。 なお、対象のテーブルは約1億件のテーブルを対象にしました。 キャッシュクリアの方法 検証中はキャッシュを使用してしまうと正しい計測ができないので、キャッシュを使用しないように設定を行います。 https://cloud.google.com/bigquery/docs/best-practices-performance-functions?hl=ja#optimize_string_comparison 検証結果 消費したスロット時間(時間:分:秒) 試行回数 REGEXP _CONTAINS LIKE 1 0:03:06 0:02:50 2 0:03:22 0:02:29 3 0:03:02 0:02:36 4 0:03:06 0:02:20 5 0:02:56 0:02:18 平均 0:03:06 0:02:31 REGEXP_CONTAINS を使用した場合の平均の「消費したスロット時間」は 3分6秒 だったのに対して、 LIKE では 2分31秒 と35秒短縮、減少率は 80.79% と約80%に短縮、約20%改善されました。 この20%の改善をどう取るか。かなり短縮できたと取るか、さほど変わらないと取るか。 個人的には、思ったほど変わらないな、という印象でした。 といのも、 SQL ではテーブルの結合方法(JOIN)や、絞り込み条件(WHERE句)をチューニングすると実行時間が半分になったり、場合によっては1/10になったりすることもざら、珍しくないため。 ただ、単純に LIKE を使うだけで20%削減されるのであれば、それは価値ありますよね。 このクエリで検索の対象としているカラムの平均の文字数は33文字と少なかったため大きな差が出なかった、もう少し文字数が多いカラムを対象にしたらもう少し差が出るのでは、と考え文字数の多いカラムを使って検証を実施しました。 追加検証結果(時間:分:秒) 平均文字数が758文字のカラムを使って比較 消費したスロット時間(時間:分:秒) 試行回数 REGEXP _CONTAINS LIKE 1 0:59:53 0:55:26 2 1:02:00 0:53:43 3 1:02:00 0:51:51 4 1:01:00 0:50:43 5 1:04:00 0:52:02 平均 1:01:47 0:52:45 まずカラムの平均文字数が増えたことで「消費したスロット時間」も増加しています。 平均文字数が33文字では約3分だったのに対して、758文字は約60分と約20倍に。 当然ですが検索する文字数が増えればその分として処理の時間も増えますね。 REGEXP_CONTAINS を使用した場合の平均の「消費したスロット時間」は 1時間1分47秒 だったのに対して、 LIKE では 52分45秒 と9分2秒短縮、減少率は 85.39% と約85%に短縮、15%改善されました。 平均文字数が33文字のときは約80%に短縮されたのに対して、758文字では約85%と短縮の率は減少しました。 この辺りの差は文字数のバラツキや検索する文字によっても差が出るのかもしれません。 文字数の多いカラムでは LIKE を使うことでよりパフォーマンスに差が出ると思いましたが、大きな違いはありませんでした。 まとめ REGEXP_CONTAINS と LIKE を使ったクエリを実行して比較してみた結果、LIKEを使った方がクエリのパフォーマンスは確かに改善された! その改善率は今回の検証では約20%だった!! 可能であれば、 REGEXP_CONTAINS ではなく LIKE を使用しましょう!!! 本日の記事は以上になります。 エニグモ Advent Calendar 2023 もこの記事で最後になります。 最後まで読んでいただきありがとうございました。 株式会社 エニグモ すべての求人一覧 hrmos.co
アバター
こんにちは! 今年7月に中途入社しました、コーポレートエンジニア(コーポレートIT[CO-IT]チーム) のフルセです! 今年も終盤(早いですねぇ、、)ということで、 Enigmo Advent Calendar 2023 の季節になりました!! クリスマスイブである 24 日目 を担当する私は入社エントリ・振り返りなど中心に自由に書きたいと思います! なお、この記事が少しでもコーポレートエンジニアに興味がある方や入社を検討してくださっている方の参考になれば幸いです。 Embed from Getty Images window.gie=window.gie||function(c){(gie.q=gie.q||[]).push(c)};gie(function(){gie.widgets.load({id:'eGD2T_vEQIpzxevVRkRvfQ',sig:'E4aHXJwgZtXDaAqpL9J550lu8QN5ONksi1IwL5kG1WY=',w:'556px',h:'311px',items:'531047383',caption: true ,tld:'com',is360: false })}); 簡単な経歴紹介 そもそもコーポレートエンジニアって何? 実際に入社してみて感じたこと 入社してからの仕事とその感想 内容 感想 こんな方に向いているかも(個人見解) 最後に 簡単な経歴紹介 私の経歴についてざっくり下記のようにまとめました。 ① 新卒でシステムインテグレータ企業にSEとして入社 →ITの基礎知識・技術を獲得! and IT業界の厳しさを痛感、、(大袈裟) ② システムインテグレータ企業を退職 →主にシステム相手の業務だったので、より人と接する仕事がしたいと考え決意 ③ 某アパレル商社の情シスへ転職 →培った知識とスキルを活かし新たな業種へチャレンジしたかった! →情シスが意外と性に合っていることに気づくきっかけに! ④ カナダへ留学 →ずっと挑戦したかった海外留学に挑戦! →語学はもちろん人との出会いや経験から人生にプラスになった! ⑤ エニグモ にコーポレートエンジニアとして入社 →帰国後にご縁があり、経験を活かしコーポレートエンジニアとして入社! そもそもコーポレートエンジニアって何? コーポレートエンジニアとは? コーポレートエンジニアとは、企業内のIT活用や運用を担当するエンジニアのことです。 企業のIT環境の変化は著しく、テレワークの普及などによって クラウド サービスの利用も活発になり、 SaaS 型のID管理や統合認証サービスを利用する企業が増えてきました。そのなかでコーポレートエンジニアは、社内ITの構築・運用をはじめとして、社内業務の課題解決のための企画立案や部門間の調整まで幅広い業務を担います。 もっと詳しく知りたい方は、チームメンバーの記事を読んでみてください! tech.enigmo.co.jp 実際に入社してみて感じたこと コーポレートエンジニアとして働く以上、社員の方々とのコミュニケーションをかなり重要だと考えていました *1 。 しかし、 エニグモ はリモートワーク中心の会社のため、Face to Face でのお話しする機会が少なくどのように交流の輪を広げていこうか少し不安(そもそも入社直後というのもあり。。)に感じていました。 そんなときに、2M(Monthly Meet-up) *2 に参加させていただきました。 そこでは色々な部署や役職の方と分け隔てなくフランクにお話しすることができ、一瞬にして不安が和らぎました。 やはり、新しい環境でのスタートはどうしても孤独感や不安がつきものだと思いますが、こういった交流会があると気持ちよくスタート ダッシュ が切れますよね!そもそも、こういった交流会を定期的に行なっている企業というのは少ないと思うので、これぞ エニグモ の良いところだと思っております。 それからというもの、毎月可能なかぎり参加し交流の輪を広げております!(無料で美味しいお酒とご飯が得られることも理由の一つなのですが笑) 2Mについて気になる方は、レポート記事がありますので是非ご覧いただければと! www.wantedly.com 入社してからの仕事とその感想 入社してからのざっくりとした仕事内容とその感想を書かせていただきました! 内容 社内ヘルプデスク対応 内容:PCの故障や不具合、各 種IT サービスのトラブル対応等 対応頻度:1〜2件/日 アカウント管理 内容:アカウントの付与・削除から数量管理・購入 対応頻度:1〜2件/週 IT機器の管理・キッティング 内容:PCやモバイルデ バイス の発注・管理・キッティング 対応頻度:10〜20件/月 社内ナレッジの作成・整備 内容:手順書やルールの新規作成・ブラッシュアップ 対応頻度:2回/週 中途入社者向けのオリエン 内容:PCのセットアップ方法の説明、社内ITサービスルールの共有 対応頻度:1〜2回/月 社内不要OA・IT機器廃棄対応 内容:廃棄物回収対応 対応頻度:2回/年 カオスマップ作成 内容:社内で利用中サービスを整理と今後導入が必要なサービスの洗い出し 対応頻度(見直し):2回/年 感想 一覧にしてみると、今年は色々なことをやらせていただいたと改めて感じています。 裁量を任せていただいているため、日々に マルチタスク をこなしておりますが、 それもまた自分のタスク管理力や遂行力の向上につながっていると感じます。 個人的にやりがいがあった業務としては、「社内不要OA・IT機器廃棄対応」です。 業者選定から始まり、やりとり(契約締結や回収日の調整など)、社内ナレッジ作成、各方面との連携など、これぞコーポレートエンジニアと感じるようなタスクだったため、とてもやりがいがありました。 それと山積みだった廃棄端末類をスッキリさせることができたという達成感が大きかったですね笑 またタスクとして面白かったものとしては、「カオスマップ作成」です。 初めてカオスマップというものを作成したので、そもそもカオスマップとは?からスタートし、他企業のカオスマップの調査、社内ITサービスの整理、今後導入が必要なITサービスの洗い出しを行いました。その後は、カオスマップをパズルのように作成する大変な作業がスタートし...と、ここでは書ききれないので、詳しい作成プロセスなどは機会があればまた書かせていただきたいと思います。(今回は割愛させていただきます。) それからというもの、試行錯誤を繰り返し、レビューを重ねてやっとの思いで完成させることができました。 結果として、時間をかけて取り組んだからこそ、サービスに対する理解や知見を広くできましたし、社内の課題(足りていないサービス)も炙り出すことができました。とはいえ、まだまだ未熟なカオスマップだと思っているので、これからもっと成熟させていきたいと思います。(楽しみ!) こんな方に向いているかも(個人見解) コーポレートエンジニアとしてのキャリアをスタートしたばかりの私ですが、 そんな私だからこそ考えるコーポレートエンジニアにはこんな人が向いているのではないかというのをまとめてみました! 私が思うに下記の3つの要素が肝になってくると思います。 コーポレートエンジニアの必要要素 新しい情報・技術に興味があり探求するのが好きな方(好奇心) 日々進化する IT技術 やサービスをいち早く自社へ展開・導入するため 最新の情報に敏感であれば、サービスなどがアップデートされたとしても柔軟に対応することができるため 人とコミュニケーションするのが得意・好きな方(コミュニケーション力) 業務上(問い合わせ対応・情報共有など)何かとコミュニケーションが必要になるため 問題を切り分けて解決することが得意・好きな方(柔軟性) コーポレートエンジニアは企業の多くの課題を与えられるので、一つ一つ解決し柔軟に対応することが求められるため ここで書かせていただいた3つの要素は、かなり重要なポイントになると考えます。 コーポレートエンジニアの業務は、IT関連の何でも屋のようになりがちだと思っております。 そのためタスクを挙げ出したらキリがないうえ、日々変化します。 そのため、柔軟性はもちろん、社内外連携のためのコミュニケーション力やITサービスに関する知見を広げるための好奇心が重要になってきます。 もちろん、足りてないと感じている要素があったとしても、業務を通じてレベルアップすることは可能だと思います!(私も絶賛レベルアップ中...) ※あくまで個人の見解なのでご了承ください。 最後に 私もまだまだ未熟なので、日々進化する IT技術 ・情報に置いていかれないよう自己研鑽を続けつつ、 社内の課題を一つずつ着実に解決し、より良い社内環境づくりに励みたいと思います! 最後まで読んでいただき、ありがとうございました!! 株式会社 エニグモ すべての求人一覧 hrmos.co *1 : 情シス時代に業務上のコミュニケーションがかなり重要だと理解していたため *2 : 毎月月初に開催される社内交流会
アバター
こんにちは、インフラグループ Kubernetes チームの福田です。 この記事は Enigmo Advent Calendar 2023 の22日目の記事です。 皆さんは Kubernetes のアップグレード、どうしていますか? Kubernetes は4ヶ月に一回、新しいマイナーバージョンがリリースされ、最新の3つのマイナーバージョンのみサポートされます。 つまり、原則は4ヶ月に一度、アップグレードをやらなければなりません。(最新バージョンであれば最大12ヶ月はサボれるという考え方もありますが。。。) 弊社では Kubernetes 環境としてEKSを使っており、 Kubernetes 本体のリリースと微妙に間隔が違いますが、最新を維持するために大体3〜5ヶ月毎にアップグレード作業をやっています。 この Kubernetes (EKS)のアップグレード作業をワークフロー化したのでそれを紹介します。 ワークフローとは 本記事でいうワークフローとはワークフローエンジンを使って自動化された作業のまとまりです。 ワークフローエンジンとは、作業プロセスをいい感じに管理してくれるツールです。 代表的なものとして、 Apache Airflow , digdag , Argo Workflows などがあります。 我々の場合、 Argo Workflows を利用しています。 EKSアップグレードワークフローの概要 上図は実際のワークフローのキャプチャです。 3段階の構造(上から順に実施される)になっていて、以下を実施する内容となっています。 廃止される API を使っていないかチェック EKSアドオンのアップグレードバージョンを特定 CloudFormationテンプレートを更新するPullRequestを出す。 元々、弊社では AWS リソースをCloudFormationで管理する運用になっていたことから、ワークフローのゴールがCloudFormationコードの修正(Pull Request作成)となっています。 ワークフローの入力 上図の通り、ワークフローには入力パラメータがあります。 ticket-id JIRAのチケットIDを指定します。 コード修正時のコミットメッセージやブランチ名の プレフィックス として利用されます。 kubernetes -cluster アップグレードを実施する対象の クラスタ をプルダウン形式で選択します。 eks-version Kubernetes versionをマイナーバージョンまで指定します。例: 1.28 ami EKSノードで使用するAMIを指定します。 EKSアップグレードワークフローの詳細 1. 廃止される API が無いかチェック Kubernetes ではマイナーバージョンの更新に伴って、機能追加や古い機能の削除のために API Versionが更新され、既存のものが廃止されることがあります。 そのため、 Kubernetes アップグレードを実施する前に廃止される API が無いかチェックしています。 pluto という OSS を使って、 クラスタ 上にあるリソースで廃止されるものを使っていないかチェックをしています。 2. EKSアドオンのアップグレードバージョンを特定 EKSでは Kubernetes のマイナーバージョンの更新と併せて、EKSアドオンのアップグレードが必要な場合があります。我々の場合、「EKSアドオンのバージョンはデフォルトバージョン※を指定する」というポリシーで運用しています。 ※ EKSではアドオン毎に Kubernetes のマイナーバージョンに紐づくデフォルトのバージョンを公開しています。 具体的な方法としては aws コマンドで、指定した Kubernetes のマイナーバージョンに対応するEKSアドオンのデフォルトバージョンを取得しています。 aws eks describe-addon-versions --addon-name ADDON_NAME --kubernetes-version K8S_NAME --query 'addons[0].addonVersions[?compatibilities[0].defaultVersion==`true`].addonVersion | [0]' 3. CloudFormationのコードを管理している リポジトリ に Kubernetes アップグレードのためのPull Requestを出す このステップについては前のステップで得られるEKSアドオンのアップグレードバージョンとワークフローへの入力値を元に、CloudFormationのコードを修正して、Pull Requestを出します。 また、Pull Requestに人手で実施する作業内容を記載します。 このステップにおける処理はGoで実装した自前の CLI ツールで行っています。 考慮事項 このステップを実装するにあたり、考慮したことを紹介します。 VPC CNIのアップグレード 前のステップでEKSアドオンのアップグレードバージョンを特定していますが、 VPC CNIについてここで問題があります。 VPC CNIのアップグレードは一度に1つのマイナーバージョンのみアップグレード可能です。 例えば、 Kubernetes 1.27に対応する VPC CNIのデフォルトバージョンが v1.12.6-eksbuild.2 で Kubernetes 1.28に対応する VPC CNIのデフォルトバージョンが v1.14.1-eksbuild.1 の場合、予め VPC CNIのバージョンをv1.13系にアップグレードする必要があります。 実際の実装としては VPC CNIのアップグレードバージョンのマイナーバージョンが元のバージョンより2つ以上離れている場合は、本ワークフローを実施する前に VPC CNIのマイナーバージョンをアップグレードするように促すメッセージを出してワークフローはそこで失敗するようにしています。 手順書 Kubernetes アップグレードの実施自体の手順としてはCloudFormationの実行コマンドだけですが、実際の作業手順はもう少し複雑です。 我々の場合はアップグレードの実施コマンドに加えて以下を盛り込む必要がありました。 作業前 作業通知の開始アナウンス 作業時の監視アラートのサイレント設定 作業後 アップグレードが正しく完了したかの確認 作業時の監視アラートのサイレント設定解除 作業通知の終了アナウンス yaml をGoで編集する際の副作用 CloudFormationのテンプレートは yaml ですが、これをGoで書き換えると発生する副作用があります。 それはGoで yaml を扱うために yaml ファイルを エンコード して、プログラム上で何らかの処理をして、再度 yaml ファイルに戻してやると空行やコメント等が消失してしまうというものです。 yaml としては等価なのだからという理由で切り捨ててしまえばそれまでですが、コメントや空行はリーダビリティの観点で有用なものも多いので、これは維持するようにしたいです。 まず、コメントの維持については yaml.v3 を使うことで解決できます。 具体的には以下のように yaml 中にある書き換えたいプロパティをpathで指定して、それをnewValueで更新するようにしています。 func (e *yamlEditor) updateYamlProperty(node *yaml.Node, path [] string , newValue string ) error { if len (path) == 0 { return fmt.Errorf( "invalid path" ) } for i := 0 ; i < len (node.Content); i += 2 { keyNode := node.Content[i] valueNode := node.Content[i+ 1 ] if keyNode.Value == path[ 0 ] { if len (path) == 1 { valueNode.Value = newValue return nil } else if valueNode.Kind == yaml.MappingNode { return e.updateYamlProperty(valueNode, path[ 1 :], newValue) } } } return fmt.Errorf( "invalid path" ) } 空行についてはdiffコマンドのオプションで空行を無視する差分のdiffを生成して、それをpatchコマンドで元のファイルに修正として適用するようにしています。 ## 元々存在するcloudformation template target.yaml ## プログラムによって書き換えが行われたcloudformation template。空行はなくなってしまっている。 target.changed.yaml diff -U 0 -w -b -B target.yaml target.changed.yaml > target.diff patch -i target.diff target.yaml 参考: https://github.com/mikefarah/yq/issues/515#issuecomment-830380295 まとめ 今回はArgo Workflowsを使った Kubernetes (EKS)のアップグレードについて紹介させていただきました。 紹介したワークフローはKubernetes1.27とKubernetes1.28のリリースの間で作成したもので、Kubernetes1.28のアップグレードはこのワークフローを使ってアップグレードしました。 これのおかげで面倒だったアップグレード作業を大分楽にすることができました。 明日の記事の担当は早野さんです。お楽しみに。 株式会社 エニグモ すべての求人一覧 hrmos.co
アバター
こんにちは。エンジニアの竹田です。 BUYMA の検索システムやMLOps基盤の開発・運用を担当しております。 こちらは Enigmo Advent Calendar 2023 の21日目の記事です 🎄 弊社では2021年頃よりMLOps基盤を Google Cloud PlatformのAI Platform Pipelines上に構築して開発・運用を行っています。 この度、Vertex AI Pipelinesへの移行を全面的に進めることになりましたので、ご紹介も兼ねて記事にしたいと思います。 背景 2024/07にAI Platform Pipelinesが非推奨になるという通知を受けたことがきっかけです。 AI Platform Pipelines deprecations  |  Google Cloud 非推奨の通知が移行を開始するトリガーではあったものの、かねてからGKE クラスタ の運用をどうにかできないかなと考えていました。 Vertex AI Pipelinesへの移行によりフルマネージド化できるのは大きなモチベーションとなっています。 移行における課題 この機会にKubeflow Pipelines SDK (以下、kfp) v1からv2へのアップグレードを進めています。 移行ドキュメント も提供されているため、それほど苦労はしないものと考えておりました。 が、結果としてこの選択が多くの苦労を抱えることになってしまいました 😢 実際に kfp v2 利用してみて、良かった点・苦労している点を交えてご紹介いたします。 ※下位互換で動作させることも可能でしたが、kfp v1での機能拡張は行われない、そのうち書き換えが必要になる、という点を考慮してkfp v2への書き換えに踏み切りました。 kfp v1からv2へ kfpは、 kubernetes 上で 機械学習 パイプラインを動作させるためのツールキットです。 コードベースは Python です。 An introduction to Kubeflow パイプラインを作成してVertex AI Pipelines上で動作させると、動作フローが視覚的に分かりやすく表現されます。 Python で記述したコードを コンパイル して利用する性質上、kfp v1の頃からかなりクセが強いなとは感じていました。 実際に利用してみた上での良い点、苦労している点を列挙し、所感を書いていこうと思います。 kfp v2の利用バージョン # pip list | grep kfp kfp 2.3.0 kfp-pipeline-spec 0.2.2 kfp-server-api 2.0.3 kfp v2の良い点 入出力に利用する Input[xxx] / Output[xxx] が便利 ParallelFor による並列処理の結果を Collected で受け取れるようになった @dsl.component や set_accelerator_type が直感的 入出力に利用する Input[xxx] / Output[xxx] が便利 kfp v2では入出力に利用するオブジェクトがこの形(基本的に Input[Artifact] / Output[Artifact] の利用)にほぼ統一されており、GCS上のパスを意識せず利用できるため非常に便利です。 https://www.kubeflow.org/docs/components/pipelines/v2/data-types/artifacts/ ParallelFor による並列処理の結果を Collected で受け取れるようになった kfp v1でも ParallelFor は利用できましたが、fan-in(複数の入力を一つにまとめること)が厄介でした。 kfp v2では最近になって Collected が利用可能となり、 ParallelFor の後に呼び出すことで結果をリスト形式でfan-inできるため、コードの可読性も飛躍的に向上します。 @dsl.component や set_accelerator_type が直感的 個人的な好みの部類かもしれませんが、kfp v2は定義周りが直感的になった印象があります。 https://www.kubeflow.org/docs/components/pipelines/v2/migration/#create_component_from_func-and-func_to_container_op-support kfp v2で苦労している点 変数展開がおかしくなることがある 型指定の厳密化により、何を渡せばよいのか分からなくなることがある 変数展開がおかしくなることがある kfp v2への移行で最も困っている点です。以下のようなissueも挙がっています。 https://github.com/kubeflow/pipelines/issues/10261 変数内に何らかの文字列や数値を入れているはずが、実際に利用する場合に以下のような展開がされてしまいます。 {{channel:task=;name=g;type=String;}} コンポーネント の出力結果をうまく展開できない場合は以下のような内容です。 {{channel:task=term-calc;name=list_date;type=typing.List[str];}} 機械学習 の初回実行プロセスの多くがBigQueryからのデータ取得を行っており、データ取得期間や特徴量を変数で管理しているため、既存のパイプラインコードでは Python のformatメソッドによる書式変換を多用しています。 この書式変換のほとんどが正常に動かなくなってしまい、試行錯誤を繰り返すことになってしまいました。 以下、期待した変数展開とならずにエラーとなるパターンの一部です。 パイプライン引数や コンポーネント の返却結果をdictに加えて コンポーネント に渡した場合 @ dsl.component def convert_str (tmpl: str , value: dict , output: Output[Artifact]): with open (output.path, "w" ) as f: f.write(tmpl.format(value)) @ dsl.pipeline (name= "test" , description= "test prediction" ) def test_pipeline (table_name: str = "sample" ): value = { "table_name" : table_name } sql = "SELECT * FROM {0[table_name]}" convert_str_op = convert_str(tmpl=sql, value=value) コンパイル 時のエラー内容 ValueError: Value must be one of the following types: str, int, float, bool, dict, and list. Got: "{{channel:task=;name=table_name;type=String;}}" of type "<class 'kfp.dsl.pipeline_channel.PipelineParameterChannel'>". パイプライン引数をパイプライン本体で利用しようとした場合 @ dsl.pipeline (name= "test" , description= "test prediction" ) def test_pipeline (periods: str = "1m" ): target_file = f "periods_{periods}.yaml" with open (target_file, mode= "r" ) as f: periods_conf = yaml.safe_load(f) コンパイル 時のエラー内容 FileNotFoundError: [Errno 2] No such file or directory: 'periods_{{channel:task=;name=periods;type=String;}}.yaml' 型指定の厳密化により、何を渡せばよいのか分からなくなることがある パイプライン引数を利用しようとして以下のようなエラーが出たり TypeError: PipelineParameterChannel is not a parameter type. 特定 コンポーネント にinputを渡した場合に以下のようなエラーが出たり ValueError: Constant argument inputs must be one of type ['String', 'Integer', 'Float', 'Boolean', 'List', 'Dict'] Got: <kfp.dsl.pipeline_task.PipelineTask object at 0x7f8a03f89880> of type <class 'kfp.dsl.pipeline_task.PipelineTask'>. といったことが割と発生します。 自分としては正しい型での引き渡し、および参照をしているつもりのため、どう対処してよいか分からなくなることが多いです。 事前にキャストすることで正常に動作することもあれば、そもそもデータ型の扱いを見直す必要があったりします。 所感 上述の苦労している点での引っ掛かりが多いのが難点で、残念ながら使いやすさは感じられていません。 ですが、一度形としてできてしまえばテンプレート化できると思われるため試行錯誤しながら進めている、というのが現状です。 コンテナイメージ化している コンポーネント 内部の挙動はほぼ変更なしで動作しており、ほとんどが「変数展開がおかしくなる」部分の障壁により思うように進捗していないといった状態です。 具体的にこうすれば良い、といったアプローチが見つけられたら何かしらの形で記事にできればと考えております。 引き続き、Vertex AI Pipelines移行による 機械学習 基盤のフルマネージド化を目指して邁進していく所存です 💪 おわりに 明日の記事の担当はインフラチームの福田さんです。EKS周りのお話です。お楽しみに!! 株式会社 エニグモ すべての求人一覧 hrmos.co
アバター
こんにちは、サービスエンジニアリング本部の平井です。 こちらは Enigmo Advent Calendar 2023 の 20日 目の記事です。 私は、エンジニア部門で取り組んでいる開発生産性分析について紹介します。 開発生産性分析を試みた経緯 現在、 エニグモ では開発組織体制の変更、メンバー増強など様々な組織強化を目指した動きが加速してきています。ただ、そのような施策が開発組織のパフォーマンスを向上させているのか 定量 的な指標で測ることができませんでした。 また、開発組織としては、開発を通して一定のスピードでユーザーに十分な価値を届ける責任を負っているものの、開発スピードを測る良い方法がありませんでした。 このように組織パフォーマンス向上施策の結果を確認するため、開発組織としての開発スピードを図るために開発生産性分析の取り組みが始まりました。 また、この取り組みは開発生産性に興味がある有志のメンバーが集まり進めていきました。 指標選定 分析する指標としては、 Google が提案している Four Keys を参考にしました。 Four Keysを参考にした理由は以下になります。 開発スピードを測る指標が含まれている。 自分達で計測、分析の基盤を整えられそう。 一般的な指標である。 そして、 指標の中で速度に関わる指標であり、計測が容易そうな 変更のリードタイム と デプロイ頻度 を計測することが決まりました。 デプロイ頻度に関しては、営業日や開発メンバーの増減による影響を少なくするために、 @hiroki_daichiさんが紹介されていた d/d/d というやり方で 1日あたりの1開発者あたりのデプロイ回数を計算することにしました。 また、現在 BUYMA は基幹システムと複数のマイクロサービスで構成されていますが、どの開発チームも修正することが多い基幹システムにおけるこれらの指標を計測していくことになりました。 各指標の定義 前提として、 BUYMA の基幹システムはGitlabを ホスティング サービスとして利用しています。 本番環境へのデプロイは内製アプリケーションを通して行われ、開発者が各々デプロイ依頼を作成します。 本番環境へのデプロイプロセスは1日3回実行されるため、各開発者は作成した依頼をそのどれかのプロセスに乗せて本番化します。 Four Keysを参考にしつつ、このような BUYMA の特徴を考慮して各指標の定義をチームで相談して決めました。 その上でリードタイムは以下の定義で計算しています。 MR内の最初のコミットからそのMRが本番環境に反映されるまでの時間 各開発者によって開発手法は異なるため最初のコミットタイミングが微妙にずれる可能性はありますが、 開発 -> レビュー -> QA -> デプロイ という一連の 開発プロセス を計測できるためこのような定義にしました。 d/d/dに関しては、 デプロイ回数/営業日/開発者数 となっていて、それぞれ以下のような定義になっています。 デプロイ回数: 本番化された本番化依頼の数 BUYMA の本番化の特徴として、1デプロイに複数の修正が含まれるためその一つ一つの修正を個別に数えたかったためこのような定義にしました。 営業日: 土日祝日を抜いた平日 営業日は特に一般的に使われているものです。 開発者数: マージされたMRに参加したユニークGitLabユーザー数 エンジニアリング部門に属していても「基幹システムに関わっていない人は含めたくない」、「過去の特定期間の開発者数も出したい」など考慮する点が多く難しかったのですが、「MRに関わった人数は開発に関わる人数と等しいだろう」という考えのもとチームで相談してこのような定義になりました。 データ収集、可視化の仕組み 次に、指標の計測と可視化の仕組みについて説明します。 Airflowにワークフローを構築して、MRに関わる情報をGitLabの API から収集しBigQueryに格納しています。 デプロイに関する情報は内製本番化アプリケーションが利用しているデータベースに永続化されているため、BigQueryに連携してGitLabの情報とJOINできるようにしました。 可視化に関しては、BigQuery上のレコードを SQL で加工してLookerの ダッシュ ボードを使うことで実現しています。 開発性生産性分析システム構成図 リードタイムに必要なデータ収集 リードタイムを計測するために以下のGitLab API を利用しています。 基本的には API のレスポンスから日次の差分データを取得して、そのままBigQueryのテーブルに保存しています。 List all projects API GitLabのプロジェクトのマスターデータを収集するために利用しています。 収集したデータはLookerで可視化する際にプロジェクト名を表示するなどに利用しています。 List group merge requests API MRデータを収集するために利用しています。 マージ済みMRの情報のみ必要なので API パラメータを使ってマージ済みMRの情報のみ収集しています。 Get single merge request commits API MRに紐づくコミットの情報を収集するために利用しています。 ここで取得したデータを利用してMRに紐づく最初のコミットを特定して、リードタイムを計算します。 d/d/d に必要なデータ収集 リードタイムを計測するために以下のGitLab API を利用しています。 こちらも日次の差分データをそのままBigQueryのテーブルに保存しています。 List a Project’s visible events API GitLabのイベント情報を収集するために利用しています。 マージされたMRに参加したユニークGitLabユーザー数を計算する際にここで収集したデータを利用しています。 可視化について 先述したように可視化にはLookerの ダッシュ ボードを利用しています。 LookML で SQL を組み立てて指標を計算しています。 エニグモ では BUYMA をメインの機能で分割して開発チームを組織していて、それらを ドメインチーム と呼んでいます。指標の監視や分析は各 ドメイン チームにやってもらっているため各 ドメイン チーム毎に ダッシュ ボードを作成しています。 可視化したあるチームのリードタイム 運用について 指標の監視や分析は、細かいやり方を指定せずに ドメイン チームに依頼しています。 例えば私が所属しているチームでは毎週の振り返り時にリードタイムとd/d/dを確認して、何か問題があれば改善案を考えるという運用をしています。 また、開発生産性指標の計測にあたり、 開発者にMRへの ラベル 付与を依頼しました。MRに付与されたラベル情報をもとに ドメイン チーム毎のリードタイムを計測しています。 今後の課題 今後の課題としては以下になります。 BUYMA 基幹システム以外でまだデータ収集できていないマイクロサービスがあるため正確に開発組織全体の生産性を測れていない。 要件定義、仕様決定などの ディスカバリー フェーズにかかっている時間を図れていない。 変更障害率、サービス復元時間など安定性の指標を図れてない。 現状だとリードタイムやd/d/dが向上した際に、それが障害発生による細かい修正が増えたのが原因なのかわからない。 終わりに 今回はエンジニア部門で取り組んでいる開発生産性分析について紹介しました。自分自身、データ収集処理の開発、可視化を担当し、自分達の開発活動が 定量 データとして表現される面白さを感じました。 最後までご覧頂きありがとうございました。明日の記事の担当は検索エンジニアの竹田さんです。お楽しみに。 現在、 エニグモ ではこのような開発生産性に関わる取り組みを行っています。 興味のある方は以下の求人をご参照ください!! 株式会社 エニグモ すべての求人一覧 hrmos.co
アバター
こんにちは、エンジニアの川本です。 主に BUYMA の決済・配送を担当しているチームでバックエンドの開発をしています。 この記事は Enigmo Advent Calendar 2023 の 20 日目の記事です。 個人開発でPlanetScaleという MySQL 互換のサーバーレスデータベースを使用しているのですが、特筆すべき仕様として外部キーのサポートがありません。 planetscale.com 外部キー制約はDBレベルで強い整合性を担保できる便利な手段ですが、PlanetScaleではその機能が利用できないので、アプリケーションレベルで整合性を担保する必要があります。 MySQL の外部キーのオプションにはいくつか種類がありますが、これらが使えない場合にアプリケーション側ではどのように担保すればよいのでしょうか? 今回は、 Rails を例にしてアプリケーション側で MySQL の外部キーに相当する機能をどのように担保できるのかを検証してみようと思います。 余談:PlanetScaleについて 最近PlanetScaleはベータ版で外部キーをサポートし始めましたが、残念ながら Hobbyプラン ではまだサポートされておりません。 PlanetScaleの基盤である Vitess はOnline DDL の機能を提供しており、それが原因で外部キーのサポートが長らく難しかったようです。 以下のドキュメントやブログには、PlanetScaleが外部キーをサポートできるようになるまでの背景や課題、そしてその克服方法についての詳細な情報が記載されています。興味がある方はぜひ読んでみてください。 外部キーのサポートが難しかった理由 Operating without foreign key constraints Online DDL: why FOREIGN KEYs are not supported 外部キーをサポートするための取り組み Foreign key constraints beta The challenges of supporting foreign key constraints 親子関係のテーブルを作成 まず親子関係にある、Parent, Childテーブルを作成してサンプルデータを入れる。 -- テーブル作成 mysql> CREATE TABLE parent ( -> id INT NOT NULL , -> PRIMARY KEY (id) -> ) ENGINE=INNODB; mysql> CREATE TABLE child ( -> id INT NOT NULL , -> parent_id INT NOT NULL , -> PRIMARY KEY (id) -> ) ENGINE=INNODB; -- テストデータをインサート mysql> INSERT INTO parent (id) VALUES ( 1 ), ( 2 ); mysql> INSERT INTO child (id, parent_id) VALUES ( 1 , 1 ), ( 2 , 1 ), ( 3 , 2 ); -- データ構造の確認 mysql> SELECT * FROM parent p JOIN child c ON p.id = c.parent_id; + ----+----+-----------+ | id | id | parent_id | + ----+----+-----------+ | 1 | 1 | 1 | | 1 | 2 | 1 | | 2 | 3 | 2 | + ----+----+-----------+ MySQL の外部キー制約 MySQL では以下4つの ON DELETE 副次句で指定できる参照アクションがあります。 ON UPDATE 副次句もありますが、今回は ON DELETE に限定することにします。 dev.mysql.com ON DELETE CASCADE 親テーブルから行を削除し、子テーブル内の一致する行を自動的に削除する。 -- ON DELETE CASCADEを指定して外部キー制約を設定 mysql> ALTER TABLE child -> ADD CONSTRAINT fk_parent -> FOREIGN KEY (parent_id) -> REFERENCES parent(id) -> ON DELETE CASCADE; -- parentのid = 1のレコードを削除する mysql> DELETE FROM parent WHERE id = 1 ; -- parent_id = 1のchildのレコードも削除されていることを確認できる mysql> SELECT * FROM child; + ----+-----------+ | id | parent_id | + ----+-----------+ | 3 | 2 | + ----+-----------+ ON DELETE SET NULL 親テーブルから行を削除し、子テーブルの外部キーカラムを NULL にする。 ※ この設定をするときは、 child の parent_id は NOT NULL にしない。 -- ON DELETE SET NULLを指定して外部キー制約を設定 mysql> ALTER TABLE child -> ADD CONSTRAINT fk_parent -> FOREIGN KEY (parent_id) -> REFERENCES parent(id) -> ON DELETE SET NULL ; -- parentのid = 1のレコードを削除する mysql> DELETE FROM parent WHERE id = 1 ; -- parent_id = 1のchildのレコードのparent_idはNULLになっていることを確認 mysql> SELECT * FROM child; + ----+-----------+ | id | parent_id | + ----+-----------+ | 1 | NULL | | 2 | NULL | | 3 | 2 | + ----+-----------+ ON DELETE RESTRICT or ON DELETE NO ACTION or 指定なし 親テーブルに対する削除操作は拒否されます。また、 ON DELETE RESTRICT or ON DELETE NO ACTION or ON DELETE 指定なし は同じ挙動になります。以下の例では ON DELETE 指定なし で例を示します。 -- ON DELETE指定なしで外部キー制約を設定 mysql> ALTER TABLE child -> ADD CONSTRAINT fk_parent -> FOREIGN KEY (parent_id) -> REFERENCES parent(id) -- parentのid = 1のレコードを削除する -- childにはparent_id = 1のレコードがあるので削除拒否される mysql> DELETE FROM parent WHERE id = 1 ; ERROR 1451 ( 23000 ): Cannot delete or update a parent row : a foreign key constraint fails (`myapp_development`.`child`, CONSTRAINT `fk_parent` FOREIGN KEY (`parent_id`) REFERENCES `parent` (`id`)) -- parentもchildも削除されていない mysql> SELECT * FROM parent p JOIN child c ON p.id = c.parent_id; + ----+----+-----------+ | id | id | parent_id | + ----+----+-----------+ | 1 | 1 | 1 | | 1 | 2 | 1 | | 2 | 3 | 2 | + ----+----+-----------+ Rails 側の実装方法 Rails では、Active Recordの dependentオプション を使用して、 MySQL の外部キー制約に相当する機能を実現できます。 dependentオプション は親レコードに対して ActiveRecord::Persistence#destroy が実行されたときに、紐ずいている子レコードに対して実行されるメソッドのことです。 ON DELETE CASCADE ON DELETE CASCADE に相当することは、 delete_all , destory , destory_async のいずれかで実現することができます。これら3つは全て最終的に実現できることは同じですが、それぞれで以下のように挙動の違いがあります。 delete_all delete_allは、parentに関連付けられたchildが一括で1つの SQL で削除します。 また、childに対して ActiveRecord::Persistence#delete が実行されるので、 ActiveRecord::Persistence#destroy 実行時に作用するbefore_destroyやafter_destroyといったコールバックや孫クラスのdependentオプションが実行されません。 そのため、単純に削除 SQL を実行するだけなので関連するchildが多い場合にはdestroyよりパフォーマンスが向上する可能性があリます。 class Parent < ApplicationRecord self .table_name = ' parent ' has_many :child , dependent : :delete_all end irb(main): 002 > parent = Parent .find( 1 ) irb(main): 054 > parent.destroy TRANSACTION ( 0 .7ms) BEGIN Child Delete All ( 1 .2ms) DELETE FROM ` child ` WHERE ` child ` . ` parent_id ` = 1 Parent Destroy ( 0 .7ms) DELETE FROM ` parent ` WHERE ` parent ` . ` id ` = 1 TRANSACTION ( 1 .9ms) COMMIT => #<Parent:0x0000ffffaf032050 id: 1> destroy destroyは、parentに紐づくchildを全て取得して1件ずつ削除します。 ActiveRecord::Persistence#destroy が実行されるため、before_destroyやafter_destroyなどのコールバックも実行され、孫クラスにあるdependentオプションも実行されます。 そのため、関連するchildが多いと発行される SQL も増え、コールバックの実行や孫クラスのdependentオプションの実行が多くなり、delete_allよりもパフォーマンスが低下する可能性があります。 class Parent < ApplicationRecord self .table_name = ' parent ' has_many :child , dependent : :destroy end irb(main): 002 > parent = Parent .find( 1 ) irb(main): 062 > parent.destroy TRANSACTION ( 0 .3ms) BEGIN Child Load ( 1 .0ms) SELECT ` child ` .* FROM ` child ` WHERE ` child ` . ` parent_id ` = 1 Child Destroy ( 1 .3ms) DELETE FROM ` child ` WHERE ` child ` . ` id ` = 1 Child Destroy ( 1 .1ms) DELETE FROM ` child ` WHERE ` child ` . ` id ` = 2 Parent Destroy ( 0 .9ms) DELETE FROM ` parent ` WHERE ` parent ` . ` id ` = 1 TRANSACTION ( 1 .2ms) COMMIT => #<Parent:0x0000ffffafdad888 id: 1> destroy_async destroy_asyncは、parentに関連する全てのchildを非同期で1件ずつ削除します。 紐づくchildが非常に多く、即時での削除を求められない場合に有効です。紐づくchildが多いと処理が最悪の場合は タイムアウト する可能性もあります。そのような場合、まずparentを削除してクライアントにレスポンスを速やかに返し、残りの紐づくchildは非同期で削除することで問題を解決できます。 class Parent < ApplicationRecord self .table_name = ' parent ' has_many :child , dependent : :destroy_async end irb(main): 002 > parent = Parent .find( 1 ) irb(main): 070 > parent.destroy TRANSACTION ( 0 .3ms) BEGIN Child Load ( 0 .8ms) SELECT ` child ` .* FROM ` child ` WHERE ` child ` . ` parent_id ` = 1 Parent Destroy ( 0 .8ms) DELETE FROM ` parent ` WHERE ` parent ` . ` id ` = 1 TRANSACTION ( 2 .0ms) COMMIT Enqueued ActiveRecord ::DestroyAssociationAsyncJob ( Job ID : 63fc4528-934a-405c- 9311 -7bee9fb706b1) to Async(default) with arguments : { :owner_model_name => " Parent " , :owner_id => 1 , :association_class => " Child " , :association_ids =>[ 1 , 2 ], :association_primary_key_column => :id , :ensuring_owner_was_method => nil } => #<Parent:0x0000ffffae761700 id: 1> irb(main): 071 > Performing ActiveRecord ::DestroyAssociationAsyncJob ( Job ID : 63fc4528-934a-405c- 9311 -7bee9fb706b1) from Async(default) enqueued at 2023 - 12 -16T09: 16 :43Z with arguments : { :owner_model_name => " Parent " , :owner_id => 1 , :association_class => " Child " , :association_ids =>[ 1 , 2 ], :association_primary_key_column => :id , :ensuring_owner_was_method => nil } Parent Load ( 3 .0ms) SELECT ` parent ` .* FROM ` parent ` WHERE ` parent ` . ` id ` = 1 LIMIT 1 Child Load ( 5 .1ms) SELECT ` child ` .* FROM ` child ` WHERE ` child ` . ` id ` IN ( 1 , 2 ) ORDER BY ` child ` . ` id ` ASC LIMIT 1000 TRANSACTION ( 0 .3ms) BEGIN Child Destroy ( 0 .9ms) DELETE FROM ` child ` WHERE ` child ` . ` id ` = 1 TRANSACTION ( 2 .0ms) COMMIT TRANSACTION ( 0 .3ms) BEGIN Child Destroy ( 1 .0ms) DELETE FROM ` child ` WHERE ` child ` . ` id ` = 2 TRANSACTION ( 1 .7ms) COMMIT Performed ActiveRecord ::DestroyAssociationAsyncJob ( Job ID : 63fc4528-934a-405c- 9311 -7bee9fb706b1) from Async(default) in 63 .81ms ON DELETE SET NULL nullify ON DELETE SET NULL に相当することは nullify で実現できます。 parentに紐づくchildのparent_idをnullに更新して、parentを削除しています。 class Parent < ApplicationRecord self .table_name = ' parent ' has_many :child , dependent : :nullify end irb(main):088> parent = Parent .find( 1 ) irb(main):090> parent.destroy TRANSACTION ( 0 .3ms) BEGIN Child Update All ( 5 .0ms) UPDATE ` child ` SET ` child ` . ` parent_id ` = NULL WHERE ` child ` . ` parent_id ` = 1 Parent Destroy ( 3 .3ms) DELETE FROM ` parent ` WHERE ` parent ` . ` id ` = 1 TRANSACTION ( 1 .3ms) COMMIT => #<Parent:0x0000ffffae66f9a0 id: 1> ON DELETE RESTRICT or ON DELETE NO ACTION or 指定なし ON DELETE RESTRICT または ON DELETE NO ACTION に相当することは、 restrict_with_exception または restrict_with_error のいずれかで実現することができます。これら2つは全て最終的に実現できることは同じですが、それぞれで以下のように挙動の違いがあります。 restrict_with_exception parentに紐づくchildが存在することを確認して、処理を ロールバック して ActiveRecord::DeleteRestrictionError という例外を発生させます。 class Parent < ApplicationRecord self .table_name = ' parent ' has_many :child , dependent : :restrict_with_exception end irb(main):088> parent = Parent .find( 1 ) irb(main):094> parent.destroy TRANSACTION ( 0 .6ms) BEGIN Child Exists ? ( 1 .0ms) SELECT 1 AS one FROM ` child ` WHERE ` child ` . ` parent_id ` = 1 LIMIT 1 TRANSACTION ( 0 .5ms) ROLLBACK / usr / local/bundle/gems/activerecord- 7.0 . 8 /lib/active_record/associations/has_many_association.rb: 16:in ` handle_dependency': Cannot delete record because of dependent child (ActiveRecord::DeleteRestrictionError) restrict_with_error parentに紐づくchildが存在することを確認して、処理を ロールバック してfalseを返します。 class Parent < ApplicationRecord self .table_name = ' parent ' has_many :child , dependent : :restrict_with_error end irb(main):088> parent = Parent .find( 1 ) irb(main):098> parent.destroy TRANSACTION ( 0 .5ms) BEGIN Child Exists ? ( 0 .6ms) SELECT 1 AS one FROM ` child ` WHERE ` child ` . ` parent_id ` = 1 LIMIT 1 TRANSACTION ( 0 .4ms) ROLLBACK => false 最後に ここまでの紹介で、 Rails アプリケーションで MySQL の外部キー制約の参照アクションを実現する手段が理解できました。 ただし、データ整合性が担保されるのは、外部キー制約に準拠したアプリケーションからの実行時に限られます。もし、同じDBを参照するが外部キー制約に準拠していないアプリケーションが存在する場合、どのような影響が生じますでしょうか? 外部キー制約のないアプリケーションからの実行により、データ整合性が維持されなくなる可能性があります。このような事態を避けるためには、できるだけDBレベルで整合性を担保する方が望ましいです。 Planet Scaleのような外部キー制約をサポートしていないDBでは、今回紹介したようなアプリケーションの実装が有効であるかもしれません。しかし、外部キー制約がサポートされているDBでは、DBレベルでの制御が安全であると言えるでしょう。 明日の記事担当はデータエンジニアリングチームです!お楽しみに! 株式会社 エニグモ すべての求人一覧 hrmos.co
アバター
.blog-figure{ width: 70%; margin-left: auto; margin-right: auto; display: block; } .blog-footer{ background: rgb(193,54,47); } .footer-icon{ width: 2rem; display: inline-block; } .figure-caption{ text-align: center; color: goldenrod; font-size: 0.8rem; } .figure-source{ opacity: 0.7; font-size: 0.6rem; color: gray; } .greetings { font-size: 0.8rem; padding: 2rem 1rem; text-align: center; display: block; margin-top: 2rem; background: rgb(250,248,236) -webkit-gradient(linear, 100% 0, 0 0, from(#acacac), color-stop(0.5, #ffffff), to(#acacac)); background-position: -4rem top; /*50px*/ background-repeat: no-repeat; -webkit-background-clip: text; -webkit-text-fill-color: transparent; -webkit-animation-name: greetings; -webkit-animation-duration: 2.2s; -webkit-animation-iteration-count: infinite; -webkit-background-size: 4rem 100%; /*50px*/ } @-webkit-keyframes greetings { 0% { background-position: -4rem top; } 70% { background-position: 30rem top; } 100% { background-position: 12.5rem top; } } .blog-emphasis{ font-weight: 600; color: goldenrod; } .blog-parts{ margin: 1.2em 0 1.5em 0; color: #3c3c3c; text-align: justify; } .blog-notes{ font-size: 0.8rem; } .entry-categories > a{ color: #888888; } こんにちは、グローバルチームのエンジニアのFernandです。 この記事は Enigmo Advent Calendar 2023 の19日目の記事です。私は、15年近く フルスタ ックエンジニアとして働いてきました。さまざまなプロジェクトに関わってきましたが、、新しいチームに参加するたびに、技術的なスキルだけでなく、特にソフトスキルについても新しく学ぶことが多くあります。今回は、職場のミーティングで外国人として直面した課題を共有したいと思います。 (jump to English version) ある日、ITエンジニアの妻が彼に頼んで、「スーパーで牛乳を1パック買ってきて。もし卵があれば1ダース買ってきて」と言いました。そこでエンジニアは買い物に行き、言われた通りに行動し、帰宅しました。手には牛乳パックが12個ありました。 妻は驚愕しました 。何が間違いだったのでしょうか? プロジェクト・マネジメント協会(PMI)の報告によると、ビジネス目標を達成しないプロジェクトの半数以上は、効果的なコミュニケーションの欠如によるものとされています。実際に、不十分なコミュニケーションにより、1500億円を投じたプロジェクトの場合には 約100億円がリスクとなる のです。ビジネスでは、12個の牛乳パックが大きな損害を生み出してしまうかもしれないのです。 コミュニケーションの齟齬による経済的リスク ソース: ©2013 Project Management Institute, Inc. Pulse of the Profession In-Depth Report: The High Cost of Low Performance: The Essential Role of Communications, May 2013. PMI.org/Pulse 私が日本の企業で働き始めた当初、さまざまなチームとのコミュニケーションに苦労しました。私と同じように、日本に住む外国人の多くが直面する経験です。 外国人が日本で働くうえでぶつかる壁(言語の壁が半数以上) ソース: NikkeiAsia 私が最初に入社したスタートアップ企業では、お客様の満足度が最も重要であり、彼らを 神様のように対応すべき ということを教わりました。そのため、ミーティングに参加する際、私は静かに座り、より理解するために一層耳を傾ける必要がありました。私は日本語に自信がなかったため、質問をすることや間違いを犯すことを恐れていました。自分が話すことで、クライアントから私たちの会社がネガティブに評価されるのではないかという不安がありました。 しかしそれでも、各ミーティングの後に反省点を見直して、気づいたことを改善してから自信を持って質問をするようにしました。そして、 効果的なコミュニケーションを大事 にする日本のチームに参加する機会を得たおかげで、私はミーティング中に質問をする勇気を持ち、プロジェクトやクライアントの要件をより理解できるようになりました。日本人の同僚は忍耐と理解力をもって複雑な概念を簡素化するために時間をかけて説明を繰り返してくれました。 しかし、言語の壁は、誤解を招く障害となることもあることも私は理解しています。それにより、会話の流れが乱れ、自分の意見を正確に表現することが難しくなります。思いを正確に表現することへの取り組みは、話す人と聞く人の双方にとっても苦悩の源となっています。できるだけこれを避けるために、事前に読み込んで準備し、質問をまとめて整理することが大事です。 外国人のメンバーがミーティングをする場合、一人だけに発言の責任が押し付けられないように、協力し合いながら取り組みます。通常、そこでは、生産的に結果を出すために、 話の途中で割り込むことが歓迎され、許容されます 。曖昧さを解消するために、ア イデア を効果的に共有して質問をするこの戦略は時に混沌としたものになりますが、コミュニケーションの理解を促進し、より協力的で生産的な働き方を促進するのに非常に効果的だと感じています。誤解や心配事が生じた際に即座にそれらを明確化することは、コミュニケーションの質を向上させ、より協力的かつ生産的な職場環境を生み出します。参加者全員が議論に貢献することを促すことで、創造性が高まり、ア イデア が高められ、より豊かでダイナミックな成果が生まれます。 しかし、文化的な習慣やエチケットにも配慮することが重要です、特に話し手が日本人の場合です。このような場合、 割り込むことは失礼とされ 、質問はミーティングの最後まで取っておくことが通例です。そういったミーティングでは、私はより受動的なアプローチを取る傾向があります。ただし、質問や懸念事項を早急に解決させたい場合、日本語の能力に自信がなくても話すように努めています。日本語の能力に自信がなくても、ただ聞くだけに頼るのではなく、積極的な参加こそが助けを求めて理解を深める唯一の方法であることを心に留めています。言語の壁を乗り越え、異文化間のコミュニケーションの複雑さをより効果的に乗り越えるためには、 日本語の勉強を続けることが重要 です。 私は現在、さまざまな国のメンバーから成るチームに所属しています。私たちの主要なコミュニケーション手段は日本語ですが、必要に応じて英語も使用しています。さまざまな時差の課題にも取り組んでおり、私たちのマネージャーはその違いがミーティングのスケジュールに影響しないよう柔軟に対応してくださいます。彼女は流暢な英語と日本語を活かし、私たちの質問や懸念事項をサポートし、チーム内の円滑かつ効果的なコミュニケーションを容易にしてくれます。私は定期的に他のチームとも交流しなければなりません。日本語を使ってのコミュニケーションは難しいですが、貴重な機会であり、コミュニケーションスキルを実践し向上させるチャンスでもあります。しかし、通常業務に追われて、学習に費やす時間の確保がなかなか難しくなっています。プライベートと仕事、そして 言語学 習のバランスを取ることは大変な努力を要します。まるでジャグリングをするようなもので、集中して言語の練習だけに時間を割くことはなかなか難しいものです。 言語の不完全さや課題があっても、ミーティングでの同僚たちはそれを判断することはありません。むしろ、言語の壁にとらわれず、積極的に参加して貢献する姿勢は評価されるでしょう。しかし、質問をする際には、ただ質問を投げる、自己アピールのためにするということは避けるべきです。そのような行動は、ビジネス上での評判に悪影響を及ぼすだけでなく、私たちが維持しようとしている協力的な雰囲気にも支障をきたす可能性があります。 さらに、会議を進行する際は、コミュニケーション能力や自信のレベルに関係なく、全参加者が積極的に参加できる包括的で支援的な雰囲気を育むことも重要です。間違った質問や関連性のない質問をする人がいても批判するべきではないでしょう。そのような場合に他者を批判すると、プロジェクトが始まる前から失敗したも同然です。前述したように、効果的なコミュニケーションが生産性を向上させるという認識はとても重要です。研究によれば、効果的なコミュニケーションは生産性を最大25%向上させることができます。最終的には、プロジェクトの成功は効果的かつ効率的なコミュニケーション能力にかかっています。卵と牛乳の個数を間違えることもないことでしょう。不確実な点が生じた場合には、積極的に話して明確化することが重要です。積極的なリスナーであり、共感を持って接することを心がけましょう。英語や日本語の流暢さだけに焦点を合わせるのではなく、彼らの質問やア イデア の価値がプロジェクトを前進させ成功させる上でとても重要です。 (English version) Communication in Workplace An IT engineer was asked by his wife to go to the store and buy a carton of milk, and if there are eggs, buy a dozen. So the engineer goes shopping, does as she says, and returns home with 12 milk cartons. Oh boy , But what went wrong? According to the Project Management Institute (PMI) report, over half of projects that fail to meet business goals can be attributed to ineffective communication. In fact, poor communication puts approximately $75 million at risk for every $1 billion spent on projects. Well, it seems that one dozen cartons of milk can potentially result in a substantial financial loss. The amount at risk for every US$1 billion spent on a project. Source: ©2013 Project Management Institute, Inc. Pulse of the Profession In-Depth Report: The High Cost of Low Performance: The Essential Role of Communications, May 2013. PMI.org/Pulse When I first started my journey working at Japanese companies, I often struggled to communicate effectively with various teams. Unfortunately, this burden is not unique to me but a shared experience among many foreign residents in Japan. Barriers foreigners encounter when working in Japan. Language barrier is the most prominent obstacle. Source: NikkeiAsia Working in startup companies, I quickly learned that client satisfaction was held in the highest regard, often considering them as king or god (o-kyaku-sama wa kamisama desu) whose every request must be fulfilled. Therefore, when I joined meetings, I had to sit quietly and listen harder to understand the discussions. I feared asking questions and making mistakes because of my Japanese language proficiency. My company might be judged by the client whenever I attempt to speak up. I was able to manage this by researching sample implementations after each meeting and creating prototypes before confidently raising a question. Then, I had the opportunity to join a Japanese team that fostered effective communication . Encouraged by my supportive environment, I mustered the courage to ask questions during meetings, improving my comprehension of projects and clients' requirements. My Japanese colleagues displayed immense patience and understanding, taking the time to reiterate their explanations and simplify complex concepts, ensuring everyone understood the information. But don't get me wrong, despite the positive environment, the language barrier often presents obstacles that could lead to misunderstandings and misinterpretations. It disrupts the conversation flow and makes it challenging to express oneself fully. The struggle to articulate thoughts accurately has become a source of frustration for both the speaker and the listener. In order to avoid this as much as possible, I read and prepared beforehand, collating and organizing my questions. In situations where the speaker is a non-Japanese teammate, I collaborated, ensuring that the onus of speaking does not solely rest on one person. Usually, in meetings that consist of non-Japanese participants, interruptions are welcomed and accepted to foster a productive environment where ideas can be effectively shared, questions can be asked, and clarification can be provided. While this strategy may seem chaotic at times, I find that it is incredibly effective in facilitating comprehension and generating deeper insights. Obtaining immediate clarity on any misunderstandings or concerns enhances the quality of communication and promotes a more collaborative and productive work environment. By establishing a culture where all participants are encouraged to contribute to the discussion, creativity surges, and ideas are elevated, creating a richer and more dynamic outcome. However, I am also mindful of cultural norms and etiquette, particularly when the speaker is Japanese. In these situations, it would be considered rude to interrupt , and it is customary to reserve questions until the end of the meeting. I tend to adopt a more passive approach during such meetings. Nevertheless, if I do have pressing clarifications or concerns, I try to speak up regardless of my lack of confidence in my Japanese proficiency. I remind myself that the only way to seek assistance and gain a better understanding is by actively participating in the conversation rather than solely relying on attentive listening. However, continuing to study Japanese remains paramount in overcoming these language barriers and navigating the intricacies of cross-cultural communication more effectively. Currently, I am fortunate to be a part of a diverse team with members from different parts of the world. Although our primary mode of communication is Japanese, we also embrace the use of English when needed. Despite the challenges of various time zones, our manager has done an exceptional job ensuring that these differences do not impede our meeting schedules. With her fluency in English and Japanese, she adeptly accommodates our questions and concerns, facilitating smooth and effective communication within the team. I also have to interact with other teams periodically. Communicating using the Japanese language is challenging, but it also gives me valuable exposure and the chance to practice and enhance my communication skills. Though I have to admit that the demanding nature of daily life and work commitments has restricted the amount of time available for study. Balancing work, personal responsibilities and language learning can be a juggling act, making it quite challenging to allocate uninterrupted time for focused language practice. Ultimately, the success of our projects hinges on our ability to communicate efficiently and effectively. If uncertainty arises, it is crucial to speak up and seek clarification. Rest assured that in meetings, colleagues will not judge any language imperfections or challenges you may encounter while expressing yourself in Japanese or English. Instead, your willingness to participate and contribute despite any linguistic limitations will be appreciated and acknowledged. However, it is also essential to avoid asking questions solely for the sake of asking or to showcase your skills. Engaging in such behavior may have a detrimental impact on your professional reputation and hinder the collaborative atmosphere we strive to maintain. In addition, it is equally important for the meeting host to take accountability in fostering an inclusive and supportive meeting environment that encourages active participation from all attendees, regardless of their communication skills or confidence levels. It is critical to refrain from criticizing individuals who may ask questions that might seem incorrect or irrelevant. Criticizing others in such instances, the project had already failed even before it started. Be an active listener and practice empathy. Fluency in English or Japanese should not be the sole focus; instead, the value of their questions and ideas truly matters in moving the project forward and achieving success. May the spirit of the holidays fill your home with love and peace. 明日の記事の担当はSellチームの平井さんです。お楽しみに。 株式会社 エニグモ すべての求人一覧 hrmos.co
アバター
こんにちは、 iOS エンジニアの池田です。 この記事は Enigmo Advent Calendar 2023 の 18 日目の記事です。 この記事では担当のプロジェクトにおいて日々の開発を快適にするために実施している細々したことについて紹介します。 私は BUYMA の iOS アプリ開発 を担当しています。 iOS アプリ開発 に限らずプロジェクトを進める中では、コアタスク(ここでは iOS アプリの開発作業と定義)とノンコアタスク(コアタスク以外の作業)が発生します。 開発の効率を上げるため、開発者の開発の快適性を上げるためにノンコアタスクは極力削っていきたいものです。 ここではノンコアタスクの中でもチケット管理、Pull Requestの管理に関わる部分について触れていければと思います。 現在のプロジェクト環境 現在の担当プロジェクト内では、チケット管理にJIRA、開発用のプラットフォームには GitHub を利用しているので、それらに関わる内容について触れていきます。 また、コミュニケーションツールとしてSlackを利用しているため、こちらとの連携も少し触れます。 JIRA 自動化 JIRAのチケット管理では、各チケットの開始日や終了日、担当者、チケットの作業状況など様々な情報の日々の更新が必要になってきます。 一つ一つの作業は作業時間も短く単純作業ではあるのですが、数が多くなると作業時間ももちろん累積されますし、何にも考えずにできるわけではないため、じわじわ地味に体力を削られます。 そういった作業を減らし、少しでも快適性を上げるためにはJIRAの「自動化」が有効です。 以下は私が担当するプロジェクトで設定している「自動化」のルール一覧です。 様々設定されていますが、この中でも「プルリク エス トがマージされる → 課題を完了にする」が結構気に入っています。 Pull Request のコードレビューを受けた後自分でマージする運用をしている場合、このルールが設定されていないと、以下のような手順が必要になります。 ブラウザで GitHub を開く GitHub の画面上でPull Requestの一覧を開く 該当のPull Requestを開く Pull Requestをマージする ブラウザでJIRAを開く JIRAのボード等でチケットを完了に トランジション させる これらの手順の中で、4.から 5.では別サービスへの切り替えが必要で、6.の作業では ドラッグ&ドロップ かオプションメニューからの選択が必要だったりで、キーボードからマウスへの操作の切り替えも余分に必要になります。 4.で作業に満足して 5.以降忘れることもあり、JIRAのカンバン管理者から完了/未完了の問い合わせが来たとしたらコミュニケーションコストもかかります。 これだけでも意外と積み重ねるとコストになるので、設定することによる快適度は上がっている感じがします。 Gitブランチ作成 もう一つJIRAの機能で細かいけどよく使っている機能がチケットのブランチ作成機能です。 以下チケットの画面キャプチャです。 チケットの「開発 > ブランチを作成 > GITで新しいブランチを作成してチェックアウト」の欄で、チケットIDが入ったブランチ名を自動生成してくれます。 また、入力欄の右側のボタンで クリップボード にコピーできるため、 CLI でブランチ作成の際にコピー&ペーストでブランチを作成することができます。 こちらを利用することでブランチ名を考える手間や、 CLI で入力する手間を省くことができます。 名前を考えるのって決めの問題なのですが、意外と考えるコストがかかる作業だと思っているので個人的にはすごく助かっています。 GitHub PULL_REQUEST_TEMPLATE.md Pull Request を作成して開発されている場合、設定している リポジトリ が多いかと思います。 本プロジェクトでは、以下のようなトピックをフォーマット化して記載するようになっており、記載時の手間を少し減らしています。 関連チケット Pull Request でやったこと 懸念点・注意点 相談事項 また、JIRAと GitHub が連携されるようになっているため、チケットのURLを記載するとJIRAのチケットに GitHub のPull Requestが連動するようになっています。 Slack SlackとJIRA、Slackと GitHub も連携されており、チケットやPull Requestの条件に応じた更新があったタイミングで通知が来るようになっています。 JIRAの通知 JIRAの通知設定は以下のようになっており、コメントやステータスの更新等通知が来るように設定されています。 GitHub の通知 GitHub の通知設定は以下のようになっており、コメントの見落としが少なくなるように comments:'channel' の設定をしているところが個別に設定した部分だったかと思います。 まとめ チケット管理、Pull Requestの管理に関わる部分についてノンコアタスクを削減するため細々やっていることをご紹介しました。 プロジェクトの環境によって合う、合わない部分があったりしますが、見て頂いた方の何かのご参考になれば幸いです。 日々の変化している状況や、気づいたちょっとした不要な手間など、アンテナを高くしつつさらなる改善、日々の開発の快適度を上げていければと思います。 株式会社 エニグモ すべての求人一覧 hrmos.co
アバター
こんにちは、コーポレートエンジニア(コーポレートIT[CO-IT]チーム) の 横川 です。 この記事は Enigmo Advent Calendar 2023 の 17 日目の記事です。 この記事では社内ITサービスを支えるチームの組織作りをテーマにどのような観点でチーム運営を行っているかをご紹介したいと思います。 コーポレートIT領域は、年々取り扱うサービスや技術が広くなり、様々なスキルセットが要求されてきていますが、本記事は特定技術の話ではなく組織運営にフォーカスした内容となります。 これから社内SEを目指される方や現在コーポレートエンジニアとして活躍されている方々に少しでも参考にしていただける内容となっていれば幸いです。 はじめに 自己紹介とCO-ITチームの紹介 コーポレートエンジニア・社内SE・情シスについて 1.組織運営の前提を整理する ミッションを理解する 会社の方針と現在のフェーズをすり合わせる プロダクト、スコープ、役割を整理する メンバー構成(チームの総リソース)を整理する 裁量と責任、働き方 2.現状を俯瞰する 課題を整理する サービスを整理する コストを整理する 手続き・運用を整理する 会議コストを考え、会議体を整理する 情報共有・ナレッジ化を意識する タスクを整理する、バックログ化する CO-ITチームの課題 3.将来を想像する 見える化、定量化に向けた(KPI・KGI策定を見据えた)足固め 業務改善の話(SaaSのインテグレーションだけが全てじゃない!) サービス導入・施策を開始することについて考える チームの安定した運営を考える 終わりに はじめに 自己紹介とCO-ITチームの紹介 私はITベンダーでネットワークエンジニアとしてキャリアをスタートし、その後セキュリティ製品の代理店にてプリセールスエンジニアを担当してきました。その後、キャリ アチェ ンジし、 デベロッパ ーでの社内SE(情シス)、 SaaS 提供企業でのコーポレートIT担当を経て、昨年10月よりenigmoにjoinしました。 コーポレートITという大きな領域全体を俯瞰して自社の最適なコーポレートITを自由に作ることができるという環境に惹かれて入社を決断しており、実際にそのミッションに日々向き合える環境で働いています。 CO-ITチームはコーポレートオペレーション本部の人事総務グループに属し、社内ITサービス全般を担当しています。 ブログ執筆時点(2023年12月)では、マネージャ[人事総務グループ部長]を除くとチームメンバーは私を含め3名で現在4人目のエンジニアを募集している状態です。 最終的な意思決定はマネージャが担いますが、当社のコーポレートIT領域の対応は現在私がリードする役割を担っています。 コーポレートエンジニア・社内SE・情シスについて 近年、コーポレートエンジニアやコーポレートITといったワードを見かけることも多くなってきており、従来の社内SEや情シスという表現との明確な違いを説明するWebページ等も増えてきているかと思います。 ざっくり両者の違いは、「見る範囲・視点の違い」とされていることが多く、コーポレートエンジニアは従来の社内SEや情シスのスコープよりも広範囲に及ぶ(企業全体を視野に入れ、ITスキルを活用して提案・企画を行う)と定義されています。 *1 コーポレートエンジニアはバックオフィス部門に所属し、会社の売上に直接貢献することはできません(プロダクト部門メンバーの業務効率向上に資することで間接的に貢献する)。企業によっては、プロダクト側に強い優遇(力が強い傾向)があったり、バックオフィス側は利益を生まない部門として不遇な環境になっている場合も見聞きしますが、プロダクト側・バックオフィス側どちらかに優劣があってはならないし、どちらも(会社の成長に貢献する部門として)リスペクトし合える企業風土が醸成できていることが望ましいと感じます。 *2 そして、シンプルにコーポレートIT部門が会社から求められることを表現すれば、 自社に最適なコーポレートITをいかに低コスト・最速で実現し、安定稼働を継続できるか になります。 予算が無限であれば、何も考えず最新で優れたサービスをフルライセンスで導入し、外部の専門ベンダーに要件定義〜運用まで全て外注して、トラブル対応や今後のアップデートも含めて24h365dでフルサポートされるオプションを付帯すれば他社を圧倒するコーポレートITの運用が可能となります。 *3 しかしながら、自社の社内ITサービスに実プロダクト以上の予算を割ける企業がどれだけあるでしょうか?この点にコーポレートエンジニアの存在意義があるものと考えられます。コーポレートエンジニアは、しっかり自社の状況を俯瞰して会社のフェーズに合わせて、必要十分なサービス提供に日々向き合うことが求められる職種です。 1.組織運営の前提を整理する 私自身が実際に実施していることを含め、組織運営にあたりクリアすべきと考えていることについて記載していきます。 ミッションを理解する 以下にMVVを掲載します。当社のミッションは  こちら  の通りですが、CompanyのMVVに応じてCO-ITチームでもMVVを定義しています。 MVV(ミッション・ビジョン・バリュー) このMVVは、私が入社前から定義されていたものですが、現時点で変更の必要性はないと判断し、そのまま継続利用しています。 近年、特にスタートアップ企業では、MVVを掲げている企業が多くなっている印象を受けますが、会社側がただ掲げているというよりは、従業員がそのMVVを自分事として意識するようにMVVに向き合う時間を業務時間の中に組み込んでいる企業も増えてきているのではないでしょうか。MVVというのは、非常に重要であり、各自の担当タスクは全てMVVに紐づいたものと考えることができます。 会社の方針と現在のフェーズをすり合わせる ワーディングは違っても、多くの企業のコーポレートIT部門のMVVは当社のものと重なる部分が多いのではないでしょうか。 それは企業活動における基本的な役割が同じであることに起因しますが、MVVが同じでも各企業・担当者の業務内容は例えば下記のような理由によって大きく異なります。 会社の規模や方針によって対応範囲やチーム構成が様々である 企業規模の拡大や事業成長に伴って縦割りが進み、サービスや製品毎にチームや担当者が限定されている 要件定義だけ・設計構築だけ・運用だけのようにスコープが限定されている したがって、具体的なチームビルディングに移る前にまず現在のフェーズを整理し、組織運営方針についての合意形成が必要になります。その大前提として、社内ITサービスを外注するのか内製するのかの判断を行います。一昔前までは専門のエンジニアが担当せず総務担当者が兼務や片手間で社内システム(情シス)を担当することも多かったように感じますが、昨今では求められるスキルや要件の複雑化によってそういった体制では対応しきれなくなってきているのではないでしょうか。事業規模の比較的小さな企業やスタートアップ企業などプロダクト側にフルコミットする必要がある場合は、社内ITサービスの外注化を行う場合もあるかと思いますが、以下のようなリスクがあるため慎重な判断が求められます。 顧客伴走型と謳っているサービスであっても顧客視点の小回りがきくサービスにはなりにくく、他企業共通のテンプレ利用による対応となるリスク システム化や仕組み化のベースとなるプラットフォームを顧客側ではなく発注先ベンダー側環境で構築され、顧客側では関知できないリスク 設計や設定、ナレッジ自体が顧客側に蓄積されず基本的に ブラックボックス 化してしまうリスク 各種リスクを解決するような契約を試みた場合、自社で専門のエンジニアを採用するよりコストが高くなるリスク 発注先都合で突然サービス終了となり、自社の社内ITサービスが停止してしまうリスク また、社内ITサービスというのは、自社独自の要件を年々積み重ねて構築していく要素も大きいため、例えばIDaaS製品の設計だけ、社内NWの構築だけを切り出すのではなく、社内ITサービス全体のバランスや連携も視野に入れた高い視座で最適なサービス運営を行う必要があります。加えて、各種サービスの発注先ベンダー等との価格交渉や要件すり合わせについても一定の知識が必要になりますので、その意味でも自社で専門のエンジニアを採用して運営していくことは重要であると言えます。 CO-ITチームは、私が入社する直前まで暫定的にマネージャ自身がプレイヤーを兼務し、多くの実務は(ヘルプデスク業務を外注できるような)外部サービスを利用することで社内ITサービスを維持管理している状態でしたが、私は業務状況を確認した上でチーム運営を再設計するフェーズ(初期フェーズ)と判断しました。なお、マネージャとは定期的に「As is」/「To be」について大枠の目線合わせを行っています。 *4 プロダクト、スコープ、役割を整理する 大枠の方針がフィックスした後は、少しずつ具体化していきます。まず、プロダクトとスコープの定義です。 一般的には、事業活動において顧客に提供するものをプロダクト(当社の場合、 BUYMA というサービス)と呼び、コーポレートITがプロダクト?と違和感を感じられるかもしれませんが、顧客(従業員)に対してサービスを提供する部門である以上当然プロダクトがあって良いと思いますし、意識すべきと考えています。そして、そのプロダクトを提供するためのスコープの定義も併せて必要です。 プロダクト/Job Titleとスコープの マッピング 上記がCO-ITチームのプロダクトと大枠のスコープになります。スコープは大項目のみ表記していますが、例えば今回のような「techブログへの投稿」業務は「 ブランディング 貢献」の中に含まれるという感じです。 また、スコープは視座や技術的な難易度等々によってピラミッド型になるので、メンバーの役割の大雑把なプロットも行っています。 *5 メンバー構成(チームの総リソース)を整理する ここまででチームが目指す方向性が見えてきたので、次に具体的なメンバー構成や必要リソースの整理に移ります。 なお、この項目については、各企業の人員計画・予算・業績・既存のメンバー構成等々複雑な要素を踏まえた 経営判断 になるかと思います。 結果だけの記載となりますが、CO-ITチームは、4名体制を目指すべく現在4人目のエンジニアを募集している状態です。 まとめますと、CO-ITチームは以下のように年間の投下リソースで表現される Value (アウトプット)が会社から求められる期待値をアウトパフォームすることを意識しなければなりません。 チーム(総リソース[年間約7,680h *6 ])の Value  > 会社からCO-ITチームへの期待値 裁量と責任、働き方 採用業務を行っているとよく質問を受ける項目ですので、ここで触れたいと思います。 CO-ITチームは、各担当者毎にスコープを限定するようなことはなく、基本的に各自のミッションサイズ(会社が各自に期待すること)に応じてコーポレートIT領域全てに携わる方針としております。 *7 ミッションサイズの中で最大限の裁量と責任を持っていただき、自由に業務時間を使うことで最大の Value を発揮いただくのが最良という考えに基づいています。 また、当社はオフィスワーク・リモートワークの選択も一定の裁量が許容されていますので、CO-ITチームでもその範囲内で自由選択としています。 基本的にオフィスワークが必要な業務(デ バイス のキッティング等)を中心に行うメンバーは出社頻度が高くなりますが、リモートワークすることでオフィスワークする以上のアウトプットが出せるのであればリモートワークの頻度が高くても良いという方針です。なお、業務スコープの特性上(ITサービスデスクを担当しているため)、必ず一人以上のチームメンバーがオフィスワーク(本社に出社)することで物理的なインシデントやユーザサポート対応を可能にしています。 2.現状を俯瞰する チーム運営の前提の整理が終わった後は、現状の把握に移ります。 課題を整理する サービスを整理する プロダクトを理解した上で顧客に提供するサービスの整理が必要です。近年のコーポレートIT領域はオンプレから クラウド 化の流れが急速に進んでいるため、 SaaS のことばかりに目が行きがちですが、従来の社内NW運用業務をはじめ、企業によってはオフィスファシリティやセキュリティ領域も含まれます。 また、 SaaS によっては他のバックオフィス部門( 経理 等)やプロダクト側が慣例的に運用しているサービスもあり、各サービス毎で主管している部門は企業によって差異があり、正確な把握が必要です。 コストを整理する 主管するサービスが洗い出せた後は、具体的なコスト感の把握です。 自チームが提供するサービスのコスト感がパッと答えられないというのは問題外であり、そのコスト感に見合うメリットを会社にもたらしているのかを常に意識する必要があります。 また、 SaaS であったとしても、単純にライセンス費用や月額利用料といった費用だけでなく、導入から日々の維持管理に必要な人的リソースのコスト(1人日の単価を具体的に設定した上で年間何人日、何人月の 工数 が掛かりどのような金額になるのかまで想定)を含めて該当サービスのコスト妥当性が判断される必要があります。 *8 CO-ITチームでは、下記のような主管サービスを網羅した一覧を作成・運用しており、提供サービスの総合計を従業員数で割ったサー ビスコ ストの算出を行っています。 この一覧には、サービスのコストをはじめ、サポート窓口の情報や稟議・請求書対応、更新のタイミング等々を含め、サービス別に串刺しして比較でき、これだけを確認すれば運用に必要な情報が取得できるように一元管理しています。 提供サービス一覧 なお、現時点では 見える化 したところまでしか到達しておらず、コストの最適値はどのくらいなのか、今のコストは妥当なのかという分析や評価を実施するフェーズには至っておりません。 近い将来、このコストを一つのパラメータとして適切なサービス導入の ガイドライン を策定することを見据えております。 手続き・運用を整理する 次に見積・稟議・発注・請求のような一連のサービス導入手続き、導入後の実運用について把握します。実現したいことは各企業大体同じであるにも関わらず、企業によって利用しているワークフローは様々であり、実現するまでに理解すべきことのボリュームに大きな違いがあります。 会議コストを考え、会議体を整理する ファシリテーション や会議に関するビジネス書で数多く述べられていますが、「会議コストは非常に大きなもので意味のある会議をすべき(無駄な会議は廃止すべき)である」といった主旨のトピックが記載されています。 例えば、4人のエンジニアが1hの MTG をする場合、そのコストは超ざっくり¥25,000程度になります。 *9 ですので、その1hの MTG のアウトプットに会社はその金額を払うことになるので、その期待値を超える Value が必要になります。特に会議は複数人で行うものなので、各自が単独で行うTodoよりも大きな人件費が計上されてしまうことになります。また、とりあえず参加するだけで仕事をした気になってしまうので注意が必要です(会議に参加すること自体に Value はなく、その会議でどのようなアウトプットを出したかが重要)。 ということで、CO-ITチームでは必要な会議体を整理し、チームのフェーズに合わせた会議運営を行っています。 情報共有・ナレッジ化を意識する まだまだ続きますが、続いてはチームレベルの ナレッジマネジメント です。一人情シスのような状態でなければ、基本的に業務を進めていくとチームメンバーへの具体的な手順共有は当然として、各種設計指針やそもそもの要件定義内容等々、チーム独自のナレッジを複数人で共有するシーンが多々あるかと思います。ナレッジ共有を全く行わない(何もかも属人化)のような組織はないと思いますが、共有の仕方に課題感が残っている組織は少なくないかと思います。CO-ITチームでは以下のポイントを実行し運用しています。 共有の仕方を定義する 共有すべき粒度を定義する ナレッジ化に惜しみなくリソースを投下する タスクを整理する、 バックログ 化する そして、最後にタスクの整理です。PJ管理やタスク管理を実施するためのツールや方法は様々なので省略しますが、当社では私が入社前の段階から Asana を利用していたため一旦そのまま継続利用しています。タスク整理にあたり、ポイントとしたことは、以下の通りです。 必ず各タスクの目的を明確化する 不明瞭な(誰も自分事としていない)タスクが管理ツール上に表示されていない状態 マネージャは意思決定のみを担当(タスク担当者にマネージャをプロットしない) 全体を同じ箱の中に含め他メンバーの業務状況も 見える化 する *10 スポット、割り込みタスクが入ってもチーム全体のタスク一覧に起票する 個人のローカルTodoで処理しない、オープンにする 基本的に起票する(よほど数分でクローズするようなタスクは例外) 本来であれば、 アジャイル の考え方に沿って、チーム全体で スクラム を用いてスプリント毎に同じMissionに向かって組織運営( バックログ 消化)を行いたいのですが、CO-ITチームではまだまだそういったフェーズにないと判断し、現状は バックログ 化することまでに留まっています。 CO-ITチームの課題 以上の課題整理を行った結果、CO-ITチームでは以下のような課題が見えてきました。 既にある程度クリアできてきているものもありますが、日々最適化すべく現在も業務推進中です。 何をどのように解決したかの話は、また機会があればご紹介させていただきます。 資産管理の精度が低い、管理できていないものが存在する サービスのスコープ、 責任分界点 が不明瞭 ナレッジが点在している、不足している、陳腐化している ブラックボックス 化(実務の外注サービス依存にも起因)、属人化している 業務分析できる基盤が整っていない( 定量 化できていない) 意思決定した背景・経緯・ポイントが体系化されていない 中長期的な視点で要件定義・設計・設定が行われていない 暫定対応でタスククローズした内容のまま形骸化している ユーザ要望を単純に解決することにフォーカスされている 3.将来を想像する 前項の通り、課題は山積みであったため、各課題に対してできる範囲(新規サービスの導入やシステム化・インテグレーションは後回し)で将来を見据えた改善を行う方針としました。 見える化 、 定量 化に向けた(KPI・KGI策定を見据えた)足固め 冒頭で記載したようにコーポレートIT領域というのは取り扱うサービスや技術が広く、様々なスキルセットが求められると言えますが、個々のPJやタスクサイズは小さいことが多々あります。 しかしながら、タスクサイズが小さいからと言って、個々のタスクを全て点で捉えて個別にクローズし続けるといつまで経っても組織運営が最適化されません。 この観点において、CO-ITチームでは将来の対応リソースの削減も見据え、タスクを 定量 化することに注力しています。 見える化 や 定量 化は多くの企業で実施されているかと思いますが、その粒度や精度については様々かと思います。 CO-ITチームも私が入社する前から全くできていないということではなく、粒度や精度に課題があったという話になります。 私が特に重要視しているのは提供サービス全体での体系化と最小限のリソースを目指す運用サイクルの構築になります。 この観点で言えば、CO-ITチームではまだまだ足固めが必要な状態であり、新規サービスの導入や SaaS 間のインテグレーションを考えるフェーズにないと判断しました。 ですので、CO-ITチームではこの一年立ち止まって当たり前のことを実現できる最低限の準備を進めてきました(導入済みサービスはリプレース検討フェーズに移らず基本的に一旦継続利用して精査する)。その中のほんの一例が下記になります。 資産管理の精緻化 問い合わせ対応のワークフロー改善、 見える化 ・ 定量 化 提供デ バイス のラインナップ整理とユーザへの ガイドライン 提供 キッティング業務のワークフロー化 備品貸与 ガイドライン の策定 デ バイス 提供スピードの向上、提供コストの削減 入退社復職休職のような人事イベントに伴うアカウントフローの整理 社内手続き・稟議の整理 サービスの保守期限の統一、サポート窓口の整理 ナレッジ共有方法の統一、ナレッジの質・量の向上 業務改善の話( SaaS のインテグレーションだけが全てじゃない!) 採用業務で様々なエンジニアの方とお話する機会があるのですが、コーポレートエンジニアを目指されている方には「業務改善」にやりがいや面白さを強く感じられていて、プログラミングを行って自動化処理の仕組みを作ることが全て(若干誇張していますが・・・)という志向をお持ちである方(そういった印象を受ける方)にお会いすることがあります。確かにコーポレートエンジニアの業務の中で SaaS のインテグレーションというのは、より技術的な要素が強く自身の満足につながる、かつ他の方(特に非エンジニア職の方)からの評価が高くなる傾向がありますので合理的ではあります。 しかしながら、一度立ち止まって以下の質問に向き合うことも必要ではないでしょうか。 本当にシステム化、自動化することだけが正なのでしょうか? 本当に自動化することでコストは下がっていますでしょうか? 参考: 運用自動化、不都合な真実 CO-ITチームでは、 SaaS 運用だけでなく全てのコーポレートIT領域に含まれるサービスにおいて業務改善を行うことが必要であり、実現する方法(コードを書くことが全てではない)に優劣はなく、アウトプットに対する評価は常にフラットでありたいと考えています。例えば、CO-ITチーム内の一例を紹介させていただきますが、以下のような対応も十分一つの業務改善であり、同等に評価されるものと考えています。 業務改善の一例 左側の写真:会議室のモニタスタンドを壁寄せモニタで可動式に変更 レイアウト変更を容易にするという要件の充足と机上スペースの拡張を実現 右側の写真:管理する サーバル ーム内の棚に共通の収納ボックスを導入 備品管理を最適化(必要な備品に最短でアクセスできる導線の確保とコストの最適化) サービス導入・施策を開始することについて考える CO-ITチームではこの一年立ち止まって足固めをしているため主だった新規サービスの導入は見送っている(他にもっとやるべきことが多い)のですが、新規サービスの導入(リプレース含む)業務に魅力を感じるエンジニアの方は多いのではないでしょうか。逆に既存の ブラックボックス ・複雑化した環境を考慮して体系的に再整理するような業務は(やりたくないとは口には出さないものの)敬遠される傾向が多いように感じます。 この傾向は、自身の直接的な スキルアップ や評価へのつながりやすさに起因するものと思います。加えて、ゼロベースでの設計難易度に比べ、複雑な既存環境の再設計は難易度が非常に高く、ボリュームが大きくなる割にはアウトプットが(新規サービス導入に比較すれば)地味になります。また、実際に稼働しているサービスの場合が多く、放置してもスポットでは課題感が多少あるかもしれないが何とかやり過ごせることが多いことも要因の一つにあるように感じます。 ビジネス書( amazonのすごい会議 )の中にもまさに同じような視点のコラムが掲載されていましたのでご紹介させていただきます。 PDCAサイクル において Amazon 社ではPDだけの人(立ち上げ屋[企画業務])は重視されないが、日本企業ではPDが重視されがちというものです。まさにコーポレートIT領域でも同じように感じます(下記のような状態です)。 サービス導入することにだけ大きなコスト・アテンションが払われる *11 導入担当者の主観・感覚のままローンチされることがある 肝心の設計・設定・運用はレビューが薄い *12 問題が顕在化した後に再設計を検討する(再設計の中心人物がヒーローになる。。。)  *13 CO-ITチームでは、近い将来新規サービスの導入を必要に応じて行いますが、上記のような課題が残らないようなPJ運営を行うつもりですし、一部の声の大きなメンバー *14 だけが新規サービス導入に携わるような組織にならないよう最適な仕組みづくりを日々模索していきます。 チームの安定した運営を考える チーム運営を安定化するポイントとしては、システム化(人に依存しない運用を構築する)やリソースの安定化(メンバーの頻繁な入れ替わりを防ぐ)といったことが挙げられます。前者については各所で述べられているように感じますが、後者はそれに比較すると情報量が少ない印象です。ここでは後者について深掘りします。 各種サービスのシステム化・自動化が非常に重要であることは自明ですが、それ以上にリソースの安定化は非常に重要です。以下にチームメンバー入れ替わりに関するデメリットを整理してみます。 一時的にリソース不足に陥る 新規メンバーの採用コストが必要となる 新メンバーの立ち上がりに1ヶ月〜半年程度を見込む必要がある では、逆にメリットがないかと言えばそうではなく、以下のようなチームに新陳 代謝 が起こるメリットはあります。 旧メンバーより更に良い人材がチームに加入するかもしれない これまで慢性化や形骸化していた業務に良い影響を与えるかもしれない しかしながら、安定したリソースでの運用を目指す方がチームとしての総合戦闘力は高いと感じますし、各自の スキルアップ やチームの新陳 代謝 はチーム運営方法によりカバーできるものと考えますので、メンバーの不要な入れ替わりは避けたいものです。メンバーが離脱する理由を考えると、 待遇面(給与や ワークライフバランス [会社の規定])の問題 会社や部署方針との根本的な価値観の不一致 会社の業績、業種の市場動向 ジョブチェンジ を目指す、またはプライベートな問題 部署内でのコミュニケーションの問題 志向の問題(自社では スキルアップ が見込めないと感じる) 足元の担当業務の問題(同じ業務ばかり、丸投げばかりされる、不公平感を感じる) といったところでしょうか。 この中で1-4まではチーム運営ではどうすることもできないものになりますが、5-7を理由にメンバーの入れ替わりが発生するというのは、当該チームのマネージャやリード担当者に責任があると感じますし、こういった理由が発生しないよう常に改善を続ける必要があります。CO-ITチームでは、将来的なチームの安定運営を目指すために日々  カイゼン  に向き合っています(具体的な話は機会があれば・・・)。 終わりに 今回は具体的なサービスの設計や設定内容、サンプルコード等のご紹介にフォーカスできませんでしたが、CO-ITチームの将来に向けた足固めは着々と進みつつあります。 来年以降ようやく新規サービス導入や施策などのコーポレートITサービス拡充をスコープとしていくフェーズを見込んでおりますので、(個人的には)ようやく楽しい業務にリソースを大幅に割けるタイミングとなります。 2023年12月現在、一緒にコーポレートITを推進いただける方を募集中です。 上記チーム運営に共感いただける方や同じような視座で物事を捉えられる方とともに一緒に当社のコーポレートITを作っていけることを楽しみにしています♪ 今回の記事は以上になります。 最後まで読んでいただき、ありがとうございました。 株式会社 エニグモ すべての求人一覧 hrmos.co *1 : 当社でも数年前まで「情シス」というワードが各所で用いられていましたが、少なくとも私が入社後は意図的に情シスというワードは使用せず、コーポレートITという表現を使用するようにしています。 *2 : 私個人の視点ですが当社はフラットな環境に感じます。 *3 : 当然ベンダーには自社の独自要件を踏まえたオーダーメイドの最適な設計でチューニングを行うように追加コストを支払う *4 : CO-ITチームの場合、マネージャと私の方向性が元々大枠で一致していたことと、経営陣とマネージャが既に同様のコミュニケーションを行っていたことで非常にスムーズにすり合わせが完了しています。企業によっては、このすり合わせが非常に難航し、そもそもすり合わせせずに実務に移ることがありますが、後々重大な課題につながるのでオススメできません。 *5 : あくまで目安であり、ピラミッドのベースにある項目が頂点にある項目に劣るというような優劣は全くありません。全ての項目がコーポレートIT領域には重要です。また、これは一般的に定義されたものではなく、私自身の経験その他から表現したものとなりますので一意見としてご理解ください。 *6 : 8時間 × 20営業日 × 12ヶ月 × 4名 *7 : ミッションサイズは、全社共通の指標があり、入社時を含め半期毎に行われる評価によって適宜決定されます *8 : あくまで私の個人的な意見ですが、業務として責任を負っている(報酬(給与)を獲得している)以上は合理的な説明責任があります( 定量 化することは必要です)。 *9 : SE費¥50,000/人日 × 0.5人日[0.125人日 × 4名] *10 : チームは同じMissionに向き合っているため、各タスクを項目分けすることはあっても ブラックボックス 化しない(隠さない)。各メンバーの Value をそれぞれが参考にしたり、評価するために必須。 *11 : 導入すること自体にバリューはなく、そのサービスで何を実現するかにバリューがある。 *12 : マネージャやリードエンジニアは、実担当でなくても高い視座でシステムの重要トピックや仕様を明確化してレビューを行う必要がある *13 : そもそも新規導入時の考慮不足・レビュー不足に起因して問題が発生する場合が多い。問題が発生する前にチームで再設計が議論され、 バックログ 化されているのが正しい運営であり、ヒーロー(救世主)のような特定個人のスキルセット頼みになるのは問題です。 *14 : 誤解を恐れずに記載すると、いいとこ取りだけして面倒事は他メンバーにぶん投げるような自分本位のようなメンバーのことを指しますが、業務への慣れ等によって知らず知らずの内に意図せずそのような振る舞いになることもあるかもしれません。お互いリスペクトし合える組織を目指したいものです。
アバター