パフォーマンスを95%改善!Sidekiqで実現する効率的なジョブ並列化と状態管理

こんにちは!WEBアプリケーションエンジニアの川本です!

この記事はEnigmo Advent Calendar 2024の1日目の記事です。

弊社の運営する BUYMA では、社内システムよりタイムセールを毎週 約100万商品 に設定しています。しかし従来のシステムでは、この処理に 約100時間 もかかっており、運用負荷が大きな課題となっていました。本記事では、パフォーマンス改善によって処理時間を 約5時間 に短縮し、運用効率を向上させた事例をご紹介します。

タイムセール設定機能について

処理フロー

タイムセール設定機能はざっくり以下のような処理フローになっています。

  1. CSVファイルのアップロード
    タイムセール設定依頼情報を記載したCSVファイルを S3 にアップロードします。
  2. SQSへのエンキュー
    S3にファイルがアップロードされると、SQSにメッセージがエンキューされます。
  3. Redisに保存
    常駐しているデーモンがSQSキューをポーリングし、SQSから取得したメッセージをRedisに保存します。
  4. 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商品)で分割し、各バッチを複数スレッドで並列処理する方式に変更しました。これにより、処理の効率化と運用負荷軽減を同時に実現することが可能になりました。

改善後の処理の流れ

  1. 親ジョブCSVデータをバッチ単位に分割し 子ジョブを作成・エンキューする。
  2. 子ジョブが実際のタイムセール設定処理を実行する。
  3. 監視ジョブ子ジョブの進捗を追跡し、全て完了したら設定完了の処理を実行する。

登場してきたジョブについて↓

親ジョブ

  • 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つに分けて紹介します。今回は前者の機械学習部分についての記事となります(後者のシステム開発部分の記事はこちら)。

内製化の目的

弊社が運営している 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 とは以下のように画像を数値の列に変換したものです。

画像をEmbedding(数値の列)に変換

似た画像同士が何らかの類似度計算関数(cos 類似度など)で高い値を持つようにトレーニングされた機械学習モデルを利用することで、画像同士の類似度を計算することが出来ます。

各ステップの詳細について説明します。

商品画像内の物体存在箇所をセグメンテーションし切り抜き

課題でも述べた通り、BUYMA の商品画像はコラージュされている場合が多く、例えば以下のような同じ商品であるが2つの異なる出品者の画像をそのまま Embedding 化し類似度を計算すると 0.496 となり、低い類似度となってしまいました。

コラージュされている商品画像の例

そこで、SAM というモデルを利用して商品画像内から商品を含む物体存在箇所をセグメンテーションし切り抜きました。SAMとは、あらゆる物体をセグメンテーションするための汎用的なモデルであり、さまざまな画像に対して物体を抽出することが可能です。大量のデータで訓練されており、Fine-Tuning なしでも高い精度を発揮します。

実際にSAM を利用してそれぞれの画像から物体部分を切り抜くと以下のような画像群が作成されました。この中で黒の財布部分を切り抜けている画像をそれぞれ Embedding し類似度を計算すると0.975 と非常に高い値となりました。

2色の財布がコラージュされた画像の切り抜き結果

財布や文字がコラージュされた画像の切り抜き結果

切り抜いた画像の Embedding 計算

前ステップで商品画像から切り抜かれた各画像について、機械学習モデルを利用し Embedding を計算します。ここでもラベル作成やトレーニングの手間を削減するために、事前学習済みの深層学習モデルを利用しました。
事前学習済みモデルとしては、 Japanese-CLIP(Contrastive Language-Image Pre-Training) を利用しました。CLIP とは、大量の画像とテキストの対応関係を学習することにより、画像と自然言語の両方での検索や類似度計算が可能なモデルです。このモデルを選んだ理由は、PoC で実際の BUYMA 画像で精度を検証してみて問題なさそうであったのと、日本語での自然言語による画像検索の拡張性も考慮してのことです(CLIP ベースのモデルであるため、自然言語と画像の類似度も計算可能です)。

しかし、ある程度開発が進んだ段階で open_clip のいくつかの学習済みモデルと比較した結果、より精度が良いモデルがあり場合によってはそちらを採用しても良かったかもしれないです。今後、さらに精度向上が必要な場合には再度検証を行う予定です。

Embedding により画像同士の類似度計算

前ステップにより1商品画像に対して複数の Embedding が作成されます。画像同士の類似度を1つの値として算出するために、商品内の切り抜かれた部分の Embedding の全ての組み合わせの類似度を計算しその中の最大の値を画像同士の類似度としました。

前の説明で用いた商品画像を例とすると、以下のように切り抜かれた部分同士の全ての組み合わせの類似度を計算し、その中の最大の値(ここでは黒の傾いていない財布部分同士の類似度が 0.975 で最大であった)をもとの商品画像同士の類似度としました。

切り抜かれた部分同士の類似度計算の例1

切り抜かれた部分同士の類似度計算の例2

この方法を試してみた時点で、既存の他社製の類似画像検索システムに近い精度が出せましたがやや及ばない状態でした。そこで後述する画像ハッシュによる類似度計算も組み合わせることとしました。

画像ハッシュにより画像同士の類似度計算

前述したように 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

INSERT SELECTやCREATE TABLE ASでWITH句を使う時の注意点

こんにちは、エニグモ 嘉松です。

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 SELECTCREATE TABLE ASの後にはSELECT文を記載する必要があるので、そのSQLの一部であるWITH句も同じくINSERT SELECTCREATE TABLE ASの後に記載する必要がある。

とうことでしょうか。

以上です。

株式会社エニグモ すべての求人一覧

hrmos.co

OAuth2Proxyは便利だよって話

こんにちは、インフラグループ 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を作成します。

  1. Sign-in methodはOIDCを選択し、Application typeとしてWebを選択します。
  2. Grant typeではAuthorization CodeRefresh Tokenを選択します。
  3. Login Redirect URIsはお使いの環境に合わせて設定してください。(ここではhttps://corp.example.com/oauth2/callbackとしておきます。)
  4. また、作成後に生成されたClient IDClient Secretをメモしておきます。
  5. 最後にユーザのアクセス許可設定をします。

シークレット情報の作成

シークレット情報をSecretとして保存します。

kind: Secret
apiVersion: v1
metadata:
  name: my-credential
type: Opaque
data:
  client_id: **********
  client_secret: **********
  cookie_secret: **********

client_idclient_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にアクセスできます。

参考リンク

まとめ

今回はOAuth2Proxyについて紹介させていただきました。
本記事を通して構築がとても簡単であることが分かったと思います。

OAuth2Proxyのようなツールを自前で社内開発しているところも多いのではないでしょうか。
メンテナンスコストの理由からそういった社内開発のアプリをOSSへ移行することを検討している場合はOAuth2Proxyは選択肢の1つとしてありかと思います。


株式会社エニグモ すべての求人一覧

hrmos.co

Global Buyma Glimpse Into the Future: Two-Day AI Conference Journey in Istanbul

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の定期バックアップ

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
コードプッシュ

コンテナアプリは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

BUYMAのプロダクトマネージャー/ディレクターの役割とは?

「安心して何度も利用したくなるマーケットプレイス」を作る!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での実装などはエンジニア部署のフロントエンドエンジニアが行いますが、インタラクションデザインなど実際に画面上で動くCSSJavaScriptの実装は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や関連サービスのサービス品質を常に進化させ、国内および世界各国のユーザーがますますサービスを利用して喜びや満足を得られるような機能を、楽しみながら共に考え抜いていただける方にジョインいただけると嬉しいです。