株式会社エニグモのブログ - TECH PLAY

TECH PLAY

株式会社エニグモ

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

252

こんにちは、データエンジニアの中村です。 新卒で入社してから、気づけば2年が経とうとしています。時間の流れは本当に早いものですね。 今回は、私がこれまで取り組んできたデータ基盤におけるアクセス制御に関する技術と取り組みについてお話ししようと思います。 昨日に引き続きデータ基盤関連の記事になりますが、ぜひ最後までご覧ください! この記事は Enigmo Advent Calendar 2024 の 20日 目の記事です。 目次 背景 ポリシータグとは ポリシータグの利用イメージ ポリシータグによるアクセス制御の仕組み 使ってみた感想 まとめ 最後に 背景 弊社のデータ基盤はBigQueryを利用しており、社内の個人ユーザーやサービスアカウント経由での各種サービスからアクセスがあります。 データの種類によってはユーザー・グループ毎にカラムレベルでアクセスを制御したいケースが発生することがありました。 以前は、通常のviewと特定のグループに見せたくないカラムをexceptしたviewの二つを作成し管理していました。 図1: アクセス制御方法(旧) シンプルな構成で直感的に理解しやすいのでこのままでも良さそうでしたが、特定のカラムが閲覧不可のグループから要望があるたびにViewを用意する必要があるので、要望が多いと対応が大変になってしまいます。 その課題を解決するために、BigQueryの ポリシータグ という機能を利用してアクセス制御を行おうと考えました。 ポリシータグとは ポリシータグは、BigQuery上のテーブルで、列レベルでのアクセス制御を可能にする タグ機能 のことです。 以下はBigQuery上で実際にポリシータグを設定した時の画像です。アクセス制御したいカラムに対して、作成したポリシータグを紐づけることでカラム単位でのアクセス制御が可能となります。 図2: ポリシータグ ポリシータグの利用イメージ イメージを以下に示しました。 図3: ポリシータグによるアクセス制御方法 ざっくり言うと ポリシータグを作成 → ポリシータグに対して権限周りの設定をゴニョゴニョ → アクセス制御したい列に対してポリシータグを設定 → アクセス制御したい列の見え方がユーザーによって変わる といった具合です。 ここで重要なのが、 「同じテーブルを見ているのにユーザーによって見え方が変わる」 ということです。 これまでは閲覧権限のないカラムをexceptしたviewを作って権限管理していましたが、ポリシータグによって1つのテーブルでそれぞれの閲覧権限に応じて閲覧可能なカラムのみ見せられるようになりました。 図4: アクセス制御方法(新) 現時点ではまだ移行途中ですが、最終的には図1から図4へ完全移行を考えております。 それでは、どのようにして見え方を変えているのかを説明していこうと思います。 ポリシータグによるアクセス制御の仕組み ポリシータグの挙動を理解する上で重要なのが以下のロールです。 1. roles/datacatalog.categoryFineGrainedReader (きめ細かい読み取り) 2. roles/bigquerydatapolicy.maskedReader (マスクされたデータの読み取り) 名前からある程度想像がつきますね。 1は 「ポリシータグが付与されていても、データをそのまま見れる」 ロールで、2は 「ポリシータグが付与されている場合、データをマスクされた状態で閲覧できる」 ロールです。 この二つのロールを利用してアクセス制御を行います。 ポリシータグを作成 図3① して、ポリシータグをテーブルへ設定 図3⑤ するだけだと、どちらのグループも権限エラーでクエリすらできません。 まずは閲覧可能グループの場合ですが、1のロールを付与したIAMポリシーをポリシータグへbinding 図3② します。そうすると、閲覧可能グループがクエリしてもポリシータグが付与されているカラムはそのままの状態で閲覧できる状態になります。 次に、閲覧不可グループの場合ですが、2のロールを付与したIAMポリシーをポリシータグへではなくデータポリシーにbinding 図3④ します。 データポリシーというのはポリシータグに紐づいているデータの マスキングルール を決定するリソースであり、それに対してポリシーbindingを行います。マスキングルールは複数あるので、ケースに応じて適切なルールを選ぶと良いでしょう。 ここまで行うと、閲覧不可グループがポリシータグを設定した列を含むクエリを実行した場合、ポリシータグを設定した列がマスキングルールに従った状態で返ってくる状態になります。 まとめると以下のような挙動になります。 図5: ポリシータグの挙動 ③の状態になってしまうと、そもそもクエリが実行できなくなってしまうので、そうならないように全てのグループ・ユーザーが①or②の状態になることが理想です。 使ってみた感想 約1年半利用しましたが、正直なところ当初の課題であった「特定のカラムが閲覧不可のグループから要望があるたびにViewを用意する必要がある」というケースがあまり発生しなくなったので、利用前後でそこまで恩恵を感じておりません。 ですが、アクセス制御方法を移行することによって将来的に以下のメリットがあると思いました。 組織が拡大してさらに細かいアクセス制御が必要になった場合、ポリシータグの方が対応が容易 セキュリティポリシー の一元管理が可能になり、管理コストが削減される デメリットももちろんあり、以下のような状況に陥りました。 テーブルへのポリシータグ付与をAirflowで行なっており、そこで利用している API が タイムアウト になり連携処理が失敗・遅延してしまい後続処理へ影響が出てしまった。 新規で作成したSAが図5③の状況になり、各所からこれまで以上に権限付与依頼が届くようになった。 API の タイムアウト の件は、暫定でリトライ回数を増やして対応しました。 権限付与依頼の増加は、デメリットというよりメリット寄りかもしれません。というのも新規ユーザーがデータ基盤にアクセスする場合はどのような権限を持つべきかを考えるべきだと考えているので、その状態が作れていることはプラスであると思います。 (あまりに多くなってきたらもっと良いやり方がないか考えます。。) まとめ 現段階では総じてポリシータグによるアクセス制御への移行は個人的に微差でプラス寄りな気がします。 最終的に移行してよかったと思えるように日々運用方法は改善していこうと思います。 最後に 最後までご覧いただきありがとうございました! 明日は佐伯さんよりAppSheetに関する記事をお届けします。お楽しみに! 株式会社 エニグモ すべての求人一覧 hrmos.co
こんにちは、データエンジニアの中村です。 弊社ではBIツールとして Google Cloudから提供されている Looker を利用しています。 Lookerの利用者も徐々に増えており、日々データ活用が進んでいることは嬉しいですが、それと比例して気になるのは ダッシュ ボードの表示速度やクエリコスト等のパフォーマンスです。 最近 AggregateAwareness という機能を利用してパフォーマンスを改善することができたので、その機能を利用するに至った背景と、その改善効果を紹介したいと思います。 AggregateAwarenessの詳細な使い方は説明しないので、その点はご了承ください。 この記事は Enigmo Advent Calendar 2024 の19日目の記事です。 目次 背景 AggregateAwarenessとは 利用した結果 メリデメ まとめ 最後に 背景 弊社のデータ基盤はBigQueryを採用しており、LookerからBigQueryへのクエリでどのくらいのコストが発生しているかをモニタリングしています。 今年の8月ごろからクエリコストが増加していることが判明し、原因調査から始めました。 Lookerのコスト推移 原因はアクセス量の多い ダッシュ ボードにスキャン量の大きい Look を追加したことによるものでした。 対策としては、Lookに対応した軽量のデータマートを用意してあげることでコストを抑えるといった方法があると思いますが、パネル毎にデータマートを用意するのは大変ですし、メンテナンスコストも高くなります。 かといって、DWH層を一から整備していくのは時間がかかりそうです。 そこで、両者のメリットを兼ね備えた方法としてAggregateAwarenessという機能に辿り着きました。 AggregateAwarenessとは AggregateAwareness とは、 データマートに相当する、事前に集計されたテーブルを自動で作成し、クエリの際にそのテーブルを自動で認識してくれる機能 です。 ざっくりとした手順を以下に記載しますが、本題ではないので詳細は割愛します。利用方法はクラスメソッドさんの こちら の記事が参考になりました。 ①AggregateAwarenessを適用したいLookが参照しているExplore画面を表示 ↓ ②Explore画面より、AggregateAwareness用に自動生成されたLookMLをコピー ↓ ③コピーしたLookMLを、対象のexploreが定義されているmodelファイルのexploreブロックへコピペ ↓ ④Exploreからクエリを実行すると、BigQueryにLookMLで指定した条件で集計されたテーブルが作成される ↓ ⑤次回以降は④で作成された集計テーブルへクエリしに行ってくれるので、クエリパフォーマンスが向上する この方法だと、テーブルの作成をLooker側で行なってくれるので、 工数 をかけずにマートの作成が行えてよかったです。 また、パネルが不要になったら、コピペしたLookMLと自動で作成されたテーブルを削除すればクリーニングが済むので、後片付けが楽そうだと感じました。 紹介はここまでにして、AggregateAwarenessを利用した結果の方を説明していこうと思います。 利用した結果 AggregateAwareness適用後のコスト推移 グラフを見ると一目瞭然ですが、適用前後で大幅にコストを削減することができています。 修正した日付が11月14日なので、11/10の週から減少傾向になり、11/17の週から一気に削減効果が現れていますね。 クエリ一回あたりのコストも連動して減少しているので、クエリ回数が減ったことによる効果ではなさそうです。 コスト 期間① ※1 期間② ※2 減少率 クエリ全体のコスト(円) (省略) (省略) 71.8% クエリ1回あたりのコスト(円/クエリ) 13.75 3.53 74.3% ※1期間①: 10/14~11/13(修正日前日から直近1ヶ月) ※2期間②: 11/14~12/15(修正日当日から1ヶ月後まで) 具体的な数値だと、適用前後1ヶ月で全体のクエリコストを約71%、クエリ一回あたりのコストを約74%削減できました! また、今回の対応を行うきっかけとなったパネル以外にもAggregateAwarenessを適用したことで、コスト増加前の期間よりも改善されていました。 コスト 期間③ ※3 期間② ※2 減少率 クエリ全体のコスト(円) (省略) (省略) 34.0% クエリ1回あたりのコスト(円/クエリ) 7.17 3.53 50.8% ※3期間③: 7/1~7/31(コスト増加前) 平常時と比較しても約50%のコスト削減効果を得られたので、年間を通してかなり効きそうですね。 なお、クエリパフォーマンスも改善されており、平常時の平均実行時間はは4秒程度だったものが、適用後は3秒程度まで減少していました。 クエリの平均実行時間の推移 コスト削減と同時に ユーザビリティ を向上させることもできました! メリデメ AggregateAwarenessを使用してみて、以下のメリットがあると感じました。 メリット 簡単にデータマートを作成でき、削除する際もクリーニングが簡単 クエリのパフォーマンスが課題になっている場合、改善効果が大きい ただし、上記のメリットはあくまで短期的な視点で見た時のものであり、中・長期的な視点で考えると以下のようなデメリットもあると感じました。 デメリット パフォーマンスが低下するたびに対応が必要で、イタチごっごになってしまう。(スケーラビリティの低下) AggregateAwarenessの利用が膨大になると、メンテナンスコストが増加しそう。(このLookMLってどのLookに対応してるんだっけ?とかが起こる) これらのメリデメを加味してバランスよく利用していけばかなり有用な機能だと思いました。 メリットが大きすぎるので、個人的にはどんどん利用していきたいです。 まとめ AggregateAwareness を利用することで、Lookerのコストを約70%削減することができました。 平均クエリ実行時間も減少し、 ユーザビリティ の向上も同時に行えました。 「まだ利用していない&パフォーマンスを改善したい」場合は利用をお勧め! 最後に 最後までご覧いただきありがとうございました! 明日の記事の担当は、本日と同じくデータエンジニアの中村です。お楽しみに! 株式会社 エニグモ すべての求人一覧 hrmos.co
こんにちは。エンジニアの竹田です。 BUYMA の検索システムやMLOps基盤の開発・運用を担当しております。 こちらは Enigmo Advent Calendar 2024 の18日目の記事です 🎄 はじめに 2024年もいよいよ年の瀬ですね!寒さが増すこの季節、みなさまいかがお過ごしでしょうか? 早速ですが本記事の主題のシステムリプレイスについてです。 ここ言うシステムリプレイスとは、老朽化したシステムの刷新、管理目的での移設など、既存システムがあり、それを何かしらの方法で置き換えることを指しています。 例えば、古くなったオンプレミス環境を クラウド に移行したり、データベースをより新しいものに入れ替えるといった作業も含まれます。 サーバサイドから下位のレイヤを担当している方々は、システムリプレイスを行う機会が割と多いのではないでしょうか。 実際に自分も エニグモ に入社してからもそうですが、それ以前からも新規システムを構築するよりはシステムリプレイスを行うことが多いと感じています。 今までの実績 各種検索システムのリプレイス MLOpsシステムのkubeflow pipelines→vertex ai pipelinesへのリプレイス アカウント検知システムのリプレイス どんなシステムでも老朽化が進むため、定期的なリプレイスが必要です。 気が付くとサポート期限間近であったりということは少なくないのではないでしょうか。 ただ、新しいもの作りという訳ではないので、なかなかモチベーションを上げ辛い作業ではないかなと思います。 また、旧システムとインプットやアウトプットは変えない、というのが命題となるので、結果比較に時間を要したりとナーバスになる作業も多いです。 そんな中でも、モチベーションを高く保つために自分が意識していることを紹介したいと思います。 システムリプレイスのモチベーションを高めるために 改善のチャンスと捉える:システムの価値を高める! 何か付加価値を提供できないかを考えます。 システムコストや学習コストの兼ね合いもありますが、思い切って足回りや ミドルウェア の置き換えなど検討しても良いと思います。 コスト削減 新たなマネージドサービスの利用 多めに見積もって構築されていたリソースの削ぎ落とし システム強化 少し気になっていたコードの修正 構成周りの定義での一工夫 マイクロサービス化 機能の一部を切り出して影響を局所化 監視強化 迅速な障害対応、柔軟な運用 学びのチャンスと捉える:自身のスキル底上げ! 少なくとも現状を一切変更しないリプレイスはあまり存在しないと思います。 例えば、旧システムのデータベースを調査中に、想定外のデータ構造や運用上の工夫を発見することもあります。それが新システム設計のヒントになることもしばしばです。 そう考えると、以下の点で新しく学びを得られる機会があります。 各種ライブラリのバージョン最新化 動作差異の確認、理解 あまり深く知らない言語の理解 どの言語でも固有の特徴がある 旧システムの構成や思想の理解 wiki 等が残っていれば確認、当時の思想が全く分からないこともある... とにかく旧システムを把握しないことには作業を進めづらい アウトプットのチャンスと捉える:学びを共有して価値を広げる! どういった方法でシステムを刷新したか、その意図など、技術ブログなどで外部発信する機会を得られます。 技術ブログの他にも、社内勉強会やカンファレンスでのLT(ライトニング トーク )なども該当するかと思います。 自分もこの点はあまり実践できていないので、自戒の意味も込めて記載しています。 自身の技術棚卸し こういった機会を作ることで、振り返りの機会を設ける 外部向け文章作成の機会 企業 ブランディング への貢献 より気持ちを高めるために 関わったことのなかった方と関わりを持てる可能性がある 過去担当していた方と関わりを持ち、人間関係を広げる機会になる可能性があります。 怯むことなく関わりを持てるようにしましょう。 今どきな言い方をしてみる slack等でやりとりする際、リプレイスではなく モダナイズ という言葉を使ってみたりしています。 ただし、 クラウド の新機能を利用したり、インフラやアプリの構成を大幅に見直すような場合でないと名前負けした感じになるので注意が必要です。 最後に 以上のようなことを意識して取り組んでいるため、比較的楽しくシステムリプレイスを行っていると思っています。 システム規模が大きくなればなるほどリプレイスは大変になるので、立ち止まって特定の機能を切り出せないかを考えたりするのも良いと思います。 みなさんもシステムリプレイスを楽しむ方法を見つけて、エンジニアとしての成長を一緒に楽しんでいきましょう! 明日の記事の担当は同グループの中村さんです。お楽しみに!! 株式会社 エニグモ すべての求人一覧 hrmos.co
こんにちは! WEBアプリケーションエンジニア の小松です! この記事は[ Enigmo Advent Calendar 2024 ]の17日目の記事です。   Railsの場合: 自動的に日付オブジェクトとして認識 サンプルコード(Rails) Laravelの場合: 明示的な型変換が必要 サンプルコード(Laravel) RailsとLaravelの比較表 開発者の観点からの結論 Railsのメリット Laravelのメリット Rails の場合: 自動的に日付オブジェクトとして認識 Rails は ActiveRecord のORMを利用する場合だけでなく、 rawクエリを用いた場合でも date 型は Ruby の Date オブジェクトに変換されます 。そのため、日付フォーマットの変換を簡単に記述できます。 サンプルコード( Rails ) テーブル定義 # migration create_table :events do |t| t.date :event_date t.timestamps end rawクエリの利用 result = ActiveRecord::Base.connection.execute("SELECT event_date FROM events LIMIT 1") raw_date = result.first["event_date"] # => #<Date: 2023-12-13> puts raw_date.strftime('%Y%m%d') # => "20231213" ポイント event_date は Ruby の Date オブジェクトとして取得される。 .strftime メソッドを直接利用してフォーマットを変更可能。 Laravelの場合: 明示的な型変換が必要 Laravelでrawクエリを使用した場合、 デフォルトでは date 型は文字列として取得されます 。そのため、フォーマットを変更する際は、明示的に Carbon オブジェクトなどに変換する必要があります。 サンプルコード(Laravel) テーブル定義 // migration Schema::create('events', function (Blueprint $table) { $table->date('event_date'); $table->timestamps(); }); rawクエリの利用 use Illuminate\Support\Facades\DB; use Carbon\Carbon; $result = DB::select("SELECT event_date FROM events LIMIT 1"); $rawDate = $result[0]->event_date; // => "2023-12-13" (文字列) $carbonDate = Carbon::parse($rawDate); echo $carbonDate->format('Ymd'); // => "20231213" ポイント event_date は文字列として取得される。 PHP の Carbon ライブラリを利用して明示的に日付オブジェクトに変換する必要がある。 Rails とLaravelの比較表 特徴 Rails Laravel rawクエリでの date 型の扱い 自動的に Date オブジェクトとして認識される 文字列として取得される 日付フォーマットの変更 .strftime('%Y%m%d') が直接利用可能 Carbon::parse($date)->format('Ymd') が必要 追加の変換処理の必要性 不要 必要 デフォルトの開発者体験 日付操作が非常に簡潔で直感的 明示的な型変換が必要で、柔軟性が高い 適用範囲 ActiveRecord でもrawクエリでも同じ Eloquentとrawクエリで挙動が異なる 開発者の観点からの結論 Rails のメリット 開発効率が高い : 日付型は自動的に Date オブジェクトとして認識され、変換処理を意識する必要がありません。 統一性がある : ActiveRecord でもrawクエリでも同じように扱えます。 時間をかけてハマる前に、まず .to_sql でクエリを確認 する習慣をつけると、 デバッグ がスムーズになります! Laravelのメリット 柔軟性が高い : Carbon を使えば、細かい日付操作や タイムゾーン 操作が可能です。 設計の自由度 : 明示的な変換により、モデルや ユースケース に応じた日付処理を柔軟に適用できます。 Rails は「標準的な処理が簡潔」、Laravelは「柔軟性を重視」といった フレームワーク の哲学の違いが、日付型の扱い方にも表れています。どちらを選ぶかはプロジェクトの要件やチームの好みに依存しますが、簡潔さを求める場合は Rails が有利です。
こんにちは! WEBアプリケーションエンジニア の小松です! この記事は[ Enigmo Advent Calendar 2024 ]の16日目の記事です。     コンソールを使った デバッグ は開発において非常に重要です。 フレームワーク や言語ごとに特性が異なるため、それぞれの仕組みに慣れる必要があります。以下に PHP や Rails を例に、 デバッグ 手法や注意点を詳しく解説します。 1. PHPのデバッグ方法 2. Railsのコンソール機能 3. RSpecでのデバッグ 結論 1. PHP の デバッグ 方法 PHP には Ruby on Rails のような標準的なコンソール機能(例: rails console )が存在しません。そのため、 デバッグ の際には以下の手法を使うことが一般的です。 var_dump() の利用 PHP では主に var_dump() が使われます。これは CLI からの実行でも WEBブラウザ 経由のアクセスでも有効です。 例 : $data = ['name' => 'John', 'age' => 30]; var_dump($data); CLI での PHP スクリプト 実行 テスト スクリプト を作成して CLI から直接実行する方法もあります。これにより、サーバーを介さずに デバッグ が可能です。 2. Rails のコンソール機能 Rails には標準で rails console が用意されており、 デバッグ やテストに非常に便利です。ただし、注意すべき点もいくつかあります。 関数のテストが容易 Rails のコンソールでは、関数やクラスを直接呼び出してテストできます。 例 : def greet(name) "Hello, #{name}!" end puts greet("Alice") #=> Hello, Alice! デバッグ における注意点 Rails コンソールでの デバッグ には p を使用しますが、 PHP の var_dump() と異なるため、切り替えを忘れることがあります。 例 : data = { name: "John", age: 30 } p data #=> {:name=>"John", :age=>30} コード変更時の再起動 コードを変更した場合、 Rails コンソールに入り直す必要があります。これを忘れると、古いコードのまま デバッグ を続けることになり、時間を無駄にする原因になります。 3. RSpec での デバッグ RSpec の デバッグ でもコンソールと似ています。以下に注意点と手法を挙げます。 p を利用した デバッグ RSpec では、ログを確認するよりも p を使う方が効率的な場合があります。しかし、つい忘れてしまうことも多いです。 例 : it "tests addition" do result = 2 + 2 p result #=> 4 expect(result).to eq(4) end コード変更時の挙動 RSpec コードそのものを変更する場合、通常は何もせずにテストを実行できます。しかし、テスト対象のロジックを変更した場合、特定の環境では注意が必要です。 コード変更と反映 テスト用のコード変更は即時反映されますが、今の開発環境では実際のアプリケーションコードを変更した場合はDockerコンテナを再起動する必要がある場合があります。この手順を忘れると、古いコードで デバッグ を進めてしまう可能性があります。 手順 : コードを変更する。 以下のコマンドでコンテナを再起動する。 結論 PHP や Rails 、 RSpec 環境それぞれに特有の デバッグ 手法と注意点があります。特に Rails やDocker環境ではコード変更時の再起動を忘れがちになります。また、各環境での デバッグ 手法に慣れることで、無駄な時間を減らし、生産性を向上させることができます。
こんにちは! WEBアプリケーションエンジニア の小松です! この記事は[ Enigmo Advent Calendar 2024 ]の15日目の記事です。   PHPの場合 呼び方の具体例 文字列キーを使ったハッシュアクセス 文字列キーアクセスを使う場面 シンボルキーを使ったハッシュアクセス オブジェクト形式(OpenStruct)のアクセス 確認方法 クラス内のインスタンス変数の確認方法 シンボルはRubyのみ存在? PHP の場合 model['full_name'] こういう変数の持ち方を 連想配列 と呼んでいただけなので、シンボルキーという言葉と発想がなかったです。 呼び出し方でも model['full_name'], model[:full_name], model.full_name などがあり、 <%= debug model %> やログでオブジェクト全体の値が取れてもキーの持ち方が違って取得するのに時間がかかることがありました。 なので、呼び方の具体例や呼び出し方を記載しました。 model['full_name'] のような形式は、文字列キーを持つハッシュから値を取得する方法です。このような形式でのアクセスは、 Ruby の基本的なハッシュオブジェクトの操作です。 呼び方の具体例 文字列キーを使ったハッシュアクセス Ruby では、ハッシュのキーとして文字列(String)を使う場合に、この形式でアクセスします。 model = { 'full_name' => ' AIR JORDAN 13(エアジョーダン13)' } puts model['full_name']  # => " AIR JORDAN 13(エアジョーダン13)" 文字列キーアクセスを使う場面 JSON パーサーからのデータ: JSON を Ruby でパースした場合、デフォルトではハッシュのキーが文字列になります。 シンボルキーを使ったハッシュアクセス Ruby では、ハッシュのキーとしてシンボル(Symbol)を使うことも一般的です。この場合は、:full_name のようにしてアクセスします。 model = { full_name: ' AIR JORDAN 13(エアジョーダン13)' } puts model[:full_name]  # => " AIR JORDAN 13(エアジョーダン13)" オブジェクト形式(OpenStruct)のアクセス OpenStruct を使うと、ドット 演算子 (.)でプロパティにアクセスできます。 require 'ostruct' model = OpenStruct.new(full_name: ' AIR JORDAN 13(エアジョーダン13)') puts model.full_name  # => " AIR JORDAN 13(エアジョーダン13)"   確認方法 model.class を実行することで、オブジェクトがハッシュなのか、OpenStruct なのか、あるいは別の型なのかを確認できます。その型に応じたアクセス方法を選びましょう。   クラス内の インスタンス 変数の確認方法 インスタンス 変数の取得方法も初めは時間がかかりました。 docs.ruby-lang.org p obj.instance_variable_get(:@foo)  このように取得するみたいです。   シンボルは Ruby のみ存在? Python 、 PHP 、C、Go、 JAVA などには存在せず、 Javascript には存在するけど、挙動が少し違うようです。 qiita.com 慣れればメリットもあるとの事ですが、他の言語にない概念なので、 Ruby 使い始めは正直紛らわしかったです。  
こんにちは、インフラグループの片桐です。 この記事は Enigmo Advent Calendar 2024 の 14 日目の記事として、サーバ機器管理台帳を 表計算 ツールから OSS の「NetBox」に移行した取り組みについて紹介します。 はじめに サーバ機器やネットワーク機器、各機器に付随する IPアドレス の情報など、サーバ機器や関連情報をまとめる為に「機器管理台帳」は欠かせないものになっています。その中で、機器管理台帳は Excel や Google スプレッドシート 等の 表計算 ツールで管理されているケースもよく見られます。 表計算 ツールを台帳として機器管理している場合、台帳の規模が拡大するにつれて、管理すべき各種情報が複数のシートやファイルに分散しがちです。これにより管理コストが増大し、整合性の維持も難しくなります。こうした状況に心当たりのある方も多いのではないでしょうか。 弊部署も同様に、 表計算 ツールを用いた煩雑な機器管理に悩まされていました。そこで、データセンターのインフラ管理(DCIM)と IPアドレス 管理(IPAM)を兼ねるツールであるNetBoxを導入し、機器情報管理の一元化・整合性維持を試みました。本記事では、機器管理台帳の 表計算 ツール管理からNetBox移行について、取り組み内容、移行に際する知見等を紹介します。  用語の整理 DCIM DCIM(Data Center Infrastructure Management)は、データセンターに関連するサーバ機器やネットワーク機器、ラック、配電や配線、設備など、さまざまなリソースを管理・可視化するためのツールや手法のことです。DCIMおよびDCIMツールを導入することで、ハードウェア配置や容量、ネットワーク接続状況などを一元管理することが可能となります。 IPAM IPAM(IP Address Management)は、ネットワーク内で使用される IPアドレス を一元的に管理し、その割り当てや利用状況、変更履歴などを可視化・追跡するためのツールや手法のことです。IPAM及びIPAMツールを導入することで、利用可能な アドレス空間 のシステム的な管理と、管理に伴ったIP重複割り当ての予防等が可能となります。 NetBox NetBoxは、DCIMとIPAMの機能を併せ持つ OSS です。サーバ機器やラック構成、 IPアドレス 、ケーブル接続など、データセンターまわりの情報を一元的に、かつわかりやすく管理することに特化しています。 Webインターフェースや REST API を通して各機器やネットワーク関連のデータを登録・編集・参照できるため、機器情報の管理をするだけでなく、管理の簡略化・自動化も可能です。 お試しで触りたい方向けの公式デモ版サイトも公開されています。 github.com 移行の背景と課題 これまで、弊部署の機器管理台帳には複数ファイル/複数シートを跨ぐ Google スプレッドシート を使用しており、以下をはじめとする各種情報を管理していました。 ハードウェア関連:機材情報、シリアル番号、ラック配置、納品日/保守期限、各機材のLAN接続状況 ホスト関連:ホスト名、各 NIC ごとの IPアドレス 割り当て状況 etc... 台帳上の各機器について、あるシートではホスト名と IPアドレス の紐づけ、あるシートではホスト名とラック上の設置位置の紐づけ...等、1箇所のデータを更新した際に関連するデータを複数シート上で合わせて更新する運用でした。 一方、この管理方式には以下の様な課題もあります。 台帳更新の煩雑さ: 1つの機器について複数ファイル/複数シートを跨いで情報が登録されている場合。 シート上のある箇所を変更した際、別シートや別セルの依存箇所も特定して修正する必要がある。 依存箇所を漏れなく特定できる運用フローや仕組み構築が必要になる。 整合性管理の困難さ: 台帳更新の煩雑さ にも関連。 依存箇所の修正漏れが起きやすい状況下にて、依存データ間で整合性の取れていないデータが発生した場合に事実確認の手間が発生する。 外部連携の困難さ: 機器管理台帳に限らず、 表計算 ツールで作成された各種データファイルが完全な CSV 形式で構築されていない場合。 台帳上に存在する情報を読み込んで プログラマブル に利用したい場合や、外部のシステムにデータを取り込む際、各環境が扱える状態に一度変換する手間がある。 これらの課題に対してNetBoxを導入し、対応を試みました。 NetBoxのデプロイ NetBoxのデプロイ先として、社内向けツール用に構築しているEKS( Amazon Elastic Kubernetes Service) クラスタ 上に構築しました。 インストールには、Netbox Communityの GitHub 上で公開されている Helmチャート(netbox-chart) を使用しました。 github.com helmを利用する場合は 利用ガイド を読みながら、ご自分の環境に合わせて values.yaml の各値を変更してセットアップする流れとなります。 特に 管理者ユーザ周りの設定値 はログイン時に使用するため、ご利用の環境に合わせて事前設定することをおすすめします。 NetBoxへのデータ登録 NetBoxに登録できる項目について データの登録を始める前に、既存の機器管理台帳では何の情報を管理しているのか、NetBoxには何の情報を登録できるのかを一通り把握しておくことを推奨します。 ドキュメントをご参照いただくと分かる通り、NetBoxに登録できる項目は多岐にわたり、かつきめ細かい情報を登録であるため、最初から全ての情報を入れ込むことが難しい場合も想定されます。 (以下NetBox ドキュメントの models 配下の大項目、並びに大項目配下の詳細情報を各機器/各グループ毎に入力可能となっています。) github.com もしスモールスタートで「データセンタ内の機器情報、ラックへの機器マウント情報、各機器へのLAN配線、 VM ホストとゲスト VM の紐づけ、各 NIC への IPアドレス アサイ ン」程度で運用を開始したい場合は、以下図のmodel項目を参考に入力していくことで簡素版な環境を構築可能です。 データの登録 NetBoxの導入を検討した当初は スプレッドシート 上に散在していた情報を1件ずつ確認しながら手動で登録することも考えていましたが、登録すべき情報を整理していく中で、数百台以上の機器や IPアドレス 、接続情報等をすべて手動で移行するのはあまりにも現実的ではない事に気付きました。 一方、NetBoxには機器追加/変更/削除等のデータ操作を楽にする為の仕組みが複数整備されており、一括でのデータ操作が可能となっています。 CSV / YAML 形式での一括インポート: NetBoxのWEBインターフェースの機能として、 CSV / YAML 形式でのデータの一括インポートに対応しています。 登録するデータを各形式に整形して事前に整備出来ている場合、各model毎のWEB UI上から楽にデータをインポート可能です。 1行目に各modelに対応するヘッダを、2行目以降に登録するデータを入力する形で定義します。 CSV 形式インポートの入力例 REST API でのデータ操作: NetBoxに実装されている REST API を活用することで、各種データ操作を プログラマブル に行う事も可能です。 CSV でのインポートはデータの登録のみ可能ですが、 API を利用することで CRUD 各処理を複雑な条件式を絡めて実行することも可能となります。 Netbox API Document 上記2種類の一括操作方法を実施して、既存の機器管理台帳上のデータを移行していきました。 スプレッドシート から移行する際の知見 移行前の情報を整理した上で、完全に正規化された CSV を作成するべき 移行前の スプレッドシート には現状との乖離として、例えば以下の様な不整合が発生していました。 新しいホストに付け替えた IPアドレス が古い機器に付随されたままになっている。 ホスト名を変更したが、旧ホスト名のまま台帳に登録されている。 不整合を一つ一つ修正するのは非常に手間の掛かる作業となる上、独自管理の形式から完全な状態の CSV 形式に落とし込むことは、手間と時間の掛かる作業となります。 一方、NetBoxには強力な CSV インポート機能が備わっているため、一度完全な状態の CSV を作成してしまえば、NetBoxの運用は直ぐにでも開始可能です。 私は不整合の解消作業は実施したものの、完全な状態の CSV を作成する前に移行を始めてしまった為、変更が必要な所は REST API を使用して適宜修正する形での対応となりました。 再度移行作業を実施する機会のある際には、完全に正規化された CSV を作成してから実施すべきと実感しました。 NetBoxに登録する情報の精査と入力先カラムの確認 前述の完全に正規化された CSV を作成した後は、 CSV 上のカラムをNetBox上のどのモデルに入れるかを予め検討しておくべきでした。 ラックの情報は dcim > racks に、機器のモデル名やシリアルは dcim > devices に、IPは ipam > ip-addresses に登録出来る、あるいはこの項目について入力できそうな項目が存在しない、等を先行して把握しておくべきでした。 例えば、NetBoxには機器の納品日や保守期限といった項目を入力出来る箇所が標準項目としては存在しません。 そのため、機器情報の自由記述欄となる Comment カラムに入力するか、 カスタムフィールド を追加することで、標準機能外の情報管理もNetBox上で可能になります。 一括登録を実施する前に、整理された CSV の項目とNetBoxの入力項目を比較して、このデータは何処に入るのか?を明確にしておくとスムーズなデータの投入が可能でした。 カスタムフィールドで機材納品日の入力欄を追加する例 NetBox導入によって感じたメリット 情報の一元管理と可視化: NetBox移行後は IPアドレス 、機器モデル、シリアルナンバー、物理配置、接続関係など、あらゆる情報をNetBoxで一元的な管理が可能となりました。 スプレッドシート を利用していた頃は、ある箇所を修正した際依存する情報の修正を、複数シート/複数ファイルに跨って手動で行う必要がありました。 NetBox移行後には依存情報の修正も行われるため、整合性保持にも寄与します。 エクスポート/インポートの柔軟性: 前述の通り、NetBoxにデータを登録する際は CSV / YAML 形式でのインポートが可能です。 また、NetBoxに登録されている情報は、 CSV / YAML 形式でのエクスポートも可能であるため、将来的に別の管理基盤へ移行する際にもデータ移行が容易になることが見込まれます。 拡張性と自動化: NetBoxには REST API や各種WebHookが実装されています。 API による台帳上のデータの一括操作が可能になった他、WebHookによって台帳に新規機材が追加された際にSlackなどチャットツールに通知する等の対応が可能となりました。別途仕組み作りを実施することで柔軟な対応が可能となるため、今後の拡張性として期待が高まりました。 サー バラック の情報可視化例。ラックの色のついている部分には登録した機器が紐づいている まとめ スプレッドシート からNetBoxへと台帳管理を移行した結果、情報一元化や更新作業の効率化が期待できる基盤が整いました。初期構築やデータ整備に手間はかかりましたが、その分、長期的な整合性維持と運用自動化の可能性が大きく広がりました。 一方、現時点では、NetBoxの基本的な機能を用いて、ハードウェアと IPアドレス 、簡易な接続情報を一元管理する段階にとどまっています。今後は運用を通じて API やWebHookの活用や NetBoxのプラグイン導入 など、さらなる効率化と可視化を進めていきたいと考えています。 明日の記事の担当はWEBアプリケーションエンジニアの小松さんです。お楽しみに。 株式会社 エニグモ すべての求人一覧 hrmos.co
こんにちは!インフラエンジニアの森田です! この記事は Enigmo Advent Calendar 2024 の12日目の記事です。 また、11日目の Googleスプレッドシート編 の続きであるため未読であればそちらからご一読いただければと思います。 前回の記事ではZapierで扱うデータのインプットとなる スプレッドシート の解説を行いましたので、今回は処理とアウトプットを行うZapの解説を行います。 現在以下のZapが稼働していますが、 毎月22日に翌月の発表者へまとめてリマインド 毎週月曜日に発表者へリマインドし、発表予定がなければ募集する 発表があれば開始のアナウンスをし、発表予定がなければスキップのアナウンスをする 今回は翌月の発表者へまとめてリマインドするZapの解説を行いたいと思います。 毎月22日に翌月の発表者へまとめてリマインド 1.毎月22日にトリガー まず、翌月頭の発表者の準備期間が取れるように毎月22日にトリガーするようにしています。 キャプチャと同じように設定すれば毎月のトリガーとなります。 2.翌月が何月か計算 次の工程で使用するために翌月が何月なのかを取得しています。 具体的には1のトリガーが次に動く日付(翌月の22日)から 正規表現 で何月かを抽出しています。 import re print (input_data[ 'next' ]) result = re.findall( r'-\d{2}-' , input_data[ 'next' ]) return { 'result' : result[ 0 ].replace( '-' , '' )} 3.翌月の行を取得 翌月発表の行をまとめて取得してきています。 具体的には「リマインド管理シート」に開催月という列があったと思いますが、開催月が2で取得したものと同値かつ開催フラグがTRUEの行を取得しています。 注意が必要なのがEventの設定で、「Lookup Spreadsheet Rows」と「Lookup Spreadsheet Rows (Advanced)」という似たイベントが存在しますが、「Lookup Spreadsheet Rows」では取得できる行が1行のため、翌月の発表全てというような複数行取得したい場合は「Lookup Spreadsheet Rows (Advanced)」を選択する必要があります。 4.Slack送信メッセージ作成 3で取得した要素をSlackのメッセージへ整形するため python で実装しています。 date_list=input_data[ 'date' ].split( ',' ) title_list=input_data[ 'title' ].split( ',' ) SlackID1_list=input_data[ 'SlackID1' ].split( ',' ) result = f ''' 【{input_data['month']}月 Hacker’s Delightスケジュールのお知らせ】 {input_data['month']}月のスケジュールの予定はこちらです。発表者の方は準備をよろしくお願いします。 何かあれば @devrel へご連絡ください ''' for i in range ( len (date_list)): string = f " \n {date_list[i]}:{title_list[i]} <@{SlackID1_list[i]}>" result = result + string print (result) return { 'result' : result} それぞれの発表の部分はリストの要素分ループで回して戻り値へ加算(追記)していく形になっています。 5.Slackへ送信 4で整形したメッセージ(戻り値)をシンプルにSlackのチャンネルに送信しています。 実際に以下のようなメッセージが送信されます。 Zapの解説は以上になります。 おわりに 今回は1つのZapを用いて解説を行いましたが、11日目の記事で解説したインプットがあれば 毎週月曜日に発表者へリマインドし、発表予定がなければ募集する 発表があれば開始のアナウンスをし、発表予定がなければスキップのアナウンスをする 上記のようなリマインドも実装することができます。 また、今回 エニグモ で行っている アドベントカレンダー のリマインドも同じ要領で自動化しています。 スケジュールを把握して色々なメンバーにリマインドをするというのは思いの外意識を割かれてしまうため、 Google スプレッドシート とZapierが利用できる環境にあれば是非自動化にチャレンジしていただければと思います。 2記事に分かれて長かったですが、お読みいただきありがとうございました。 明日の担当はRDチームの 廣島 さんです。お楽しみに。 株式会社 エニグモ すべての求人一覧 hrmos.co
こんにちは!インフラエンジニアの森田です! この記事は Enigmo Advent Calendar 2024 の11日目の記事です。 私は社内の勉強会チーム(DeveloperRelationsチーム)としても活動しており、 人力で行なっていたリマインドをZapierで自動化したのでそのご紹介をしたいと思います。 今回は毎週金曜日に実施している軽い勉強会( Hacker'sDelight )のリマインドの自動化を例に記載します。 元々は以下のようなスケジュール管理シートがあり、こちらを人の目で見て翌月の発表者やその週の発表者にSlackでリマインドを送るという運用をしていました。 ただずっと人力でリマインドを送るというのはツラいため、社内で自動化ツールのZapierを契約しているため折角なら自動化してしまおうということで自動化に着手しました。 調べながらZapを作成する中で意外にZapierの日本語のドキュメントが少なかったため、この記事が備忘録かつ同じような自動化をやりたい方の参考になればと思います。 今回の自動化を構成しているのは大きく分けて以下の3要素になります。 スケジュール管理シート(パブリック) リマインド管理シート(クローズド) Zap 全ての要素の解説を1記事で行うと長くなってしまうため今回は「スケジュール管理シート」と「リマインド管理シート」の解説を行います。 Zapの解説は12日目の記事で解説したいと思います。 スケジュール管理シート(パブリック) 以下が「スケジュール管理シート」です。 基本的に上で貼ったスケジュール管理シートと同様ですが、「リマインド管理シート」でSlackIDをメールアドレスから自動検索するために列が追加されています。 こちらのシートは基本的にパブリックになっており、一般メンバーもHacker'sDelightのカレンダーとして見ることができます。 全て手動で管理しているので特に言及するところはありませんが、この後の解説で関連してくる列は以下となります。 内容 発表者1,2(敬称略) 発表者1,2メアド 日程 リマインド管理シート(クローズド) 「リマインド管理シート」は「Zapier通知用」と「Slackメンション用IDリスト」の2つのシートからなります。 Zapier通知用 このシートは「スケジュール管理シート」から参照した値から計算して自動的に埋められるため、手動での管理は不要になっています。 それぞれの列の解説をします。 日程 発表内容 発表者1,2 発表者1,2メアド これらの列は全て「スケジュール管理シート」からそのまま参照しています。 別の スプレッドシート から値を参照してくるには IMPORTRANGE関数 を使用します。 例) =IMPORTRANGE("https://docs.google.com/spreadsheets/d/xxxx","2025!G4") また、発表者の部分は敬称を スプレッドシート 側で入れておきたいので カスタム数値形式 で「@さん」と入れることで自動で敬称を付与するようになっています。 開催月 MONTH関数 を用いて何月に開催されるのかを抽出しています。 これは翌月の発表をまとめてリマインドするのに使用します。 例) =MONTH(A2) 当日までの日数 日程のセルから TODAY関数 で今日の日付をマイナスすることで発表当日まで何日なのかを算出することができます。 例) =A2-TODAY() メンション用ID VLOOKUP関数 で「Slackメンション用IDリスト」から発表者メアドに対応した IDを取得してきています。 元々人力でメンション用IDを探してコピペしていましたが辛すぎるうえミスが分かりずらいため、IDは自動で取得されるようにした方が良いと思います。 例) =VLOOKUP(F2,'Slackメンション用IDリスト'!$A$3:$B$1435,2,FALSE) 開催フラグ 発表があるのかないのかを判別するために ISBLANK関数 で発表内容のセルを空白判定しています。 また、そのままだと発表がある(セルが埋まっている)とFALSEと判定され違和感があるため、 NOT関数 で逆の論理値が返されるようにしています。 例) =NOT(ISBLANK(D2)) Slackメンション用IDリスト 以下のドキュメントを参考にメンバーリストを取得すると、ユーザーのメールアドレスとUserIDが記載された csv ファイルが取得できます。 ワークスペースのメンバーリストをダウンロードする こちらのシートは csv の内容をそのままコピペしたものです。 Zapierを使ってユーザーにメンションをする際にはこのUserIDが必要になります。 注意が必要なのはこのメンバーリストがSlackのオーナーまたは管理者権限を持っていないと取得できない点です。 不特定多数が見られる場所に書かれるのは好ましくないため、「リマインド管理シート」の参照権限はクローズドで必要のあるメンバーのみが見られるようにするべきです。 Zapierでリマインドを行うためのインプットとなる スプレッドシート の解説は以上となります。 明日の記事では実際にリマインドを行う部分であるZapの解説を行います。 株式会社 エニグモ すべての求人一覧 hrmos.co
この記事は 私は、株式会社 エニグモ でチームのマネージャをしている後藤です。 マネージャ業務の傍ら開発作業を行うこともあります。 この記事は Enigmo Advent Calendar 2024 の10日目の記事です。 この記事では多くの Ruby を使っている人が、使っているであろう ruby-lsp を少し便利にする ruby-lsp-addons について紹介しながら、 VSCode でどのように活用できるのかについて紹介したいと思います。 ruby -lspとは ruby-lsp とは、 Ruby 言語向けに Microsoft が定義している、 Language Server Protocol を実装したサーバになります。 Language Server Protocol (以下 LSP ) は、プログラム中に定義されたクラス名、関数名、変数名などを取得できる API とデータ型を定義したものになります。プログラム中のデータを取得する API を定義しておくことで、エディタから対象言語に対応した LSP サーバーを通して 関数定義情報を表示、関数/変数の定義箇所へのジャンプ、関数名/変数名を使った入力補間ができるようになります。 LSP の登場以前は universal-ctags + GNU Global などを用いて同様の機能を実現することが多かったように思いますが、 VSCode の登場と合わせて LSP の提案と普及が進みました。その結果、多くのエディタで LSP がサポートされるようになり、最近は補間や関数ジャンプのための機能が LSP に集約されつつあるように思います。 Visual Studio Code ( VSCode ) の場合は、 ruby-lsp プラグイン を VSCode に追加することで ruby ソース編集次に ruby -lsp を使った関数ジャンプや入力補間が使えるようになります。 ruby -lsp-addons ruby -lsp ですが、当然ながら ruby の文法しかサポートしていません。ただ、 Ruby on Rails の場合はモデルの has_many、blongs_to、validates などなど多数の DSL がありこれらもOUTLINEなどに表示して欲しいところですが、その機能を ruby -lsp に実装するのも違っている気がします。 そんな問題を解決しようとしているのが、 ruby-lsp-addons 機構になります。 公式のドキュメントは Add-ons | Ruby LSP にあります。ただ、公式ドキュメントにもあるように実験的な取り組みのようで、 ruby-lsp のメジャーバージョンが変わると、 インターフェイス が変わって addons が動かなくなることもありますし、将来a addons のサポートが終わってしまう可能性があることを心にとめて置いてください。 それでは、 ruby-lsp-rails ruby-lsp-rspec について紹介していきたいと思います。 ruby-lsp-rails こちらの add-on を使えるようにするには、 ruby-lsp-rails gem を追加します。 名前からわかる通り、 rails 固有の機能サポートを追加するadd-onになります。 今回は、 gitlabhq/gitlabhq のソースの一部を表示しながら、 公式ドキュメントからいくつかの機能を抜粋して紹介したいと思います。 modelの DSL サポート # frozen_string_literal: true module Achievements class Achievement < ApplicationRecord include Avatarable include StripAttribute belongs_to :namespace , inverse_of : :achievements , optional : false has_many :user_achievements , inverse_of : :achievement has_many :users , through : :user_achievements , inverse_of : :achievements strip_attributes! :name , :description validates :name , presence : true , length : { maximum : 255 }, uniqueness : { case_sensitive : false , scope : [ :namespace_id ] } validates :description , length : { maximum : 1024 } def uploads_sharding_key { namespace_id : namespace_id } end end end のようなコードがあると、 VSCode の OUTLINE には以下のように Rails 固有の DSL で定義された項目も表示されます。 コントローラーメソッドからviewへのジャンプ コントローラのメソッド画面にviewを開くリンクが追加されます。 上の絵の赤枠で囲ったリンクが表示されるようになります。 この、 Jump to view をクリックすることで view が表示されます。 地味に、便利です。 その他の機能 公式ドキュメントを読むと、モデルの belongs_to のアソシエーション先にジャンプできる機能、コントローラーのメソッド定義箇所から ルーター の定義にジャンプできる機能などがあることになっていますが、上手く機能していないようです。この辺は実験的な取り組みということを温かく見守ることしましょう。 ruby-lsp-rspec 次は、 ruby -lsp- rspec です。 こちらも add-on を使えるようにするには、 ruby-lsp-rspec gem を追加します。 こちらも名前から想像できる通り、 rspec 向けの機能拡張を行う add-on になります。 以下のような specファイルを開くと、 # frozen_string_literal: true require " spec_helper " RSpec .describe Diffs :: StatsComponent , type : :component do include RepoHelpers subject( :component ) do described_class.new( diff_files : diff_files) end let_it_be( :project ) { create( :project , :repository ) } let_it_be( :repository ) { project.repository } let_it_be( :commit ) { project.commit(sample_commit.id) } let_it_be( :diffs ) { commit.raw_diffs } let_it_be( :diff ) { diffs.first } let_it_be( :diff_refs ) { commit.diff_refs } let_it_be( :diff_file ) { Gitlab :: Diff :: File .new(diff, diff_refs : diff_refs, repository : repository) } let_it_be( :diff_files ) { [diff_file] } describe " rendered component " do subject { page } let( :element ) { page.find( " .js-diff-stats-dropdown " ) } before do render_inline component end ... VSCode の OUTLINE に以下のように、 examples のテキストが表示されるため、 spec ファイルの流れを追うのも、修正をするのも楽になります。 また、以下のようにspecファイル上に Run | Run In Terminal | Debug リンクが追加されます。 このRunなどをクリックすることで、 rspec のテストを実行できます。また、これらのテスト機構が、 VSCode の テスト機能に統合されているため、 以下のような VSCode 標準の、 Test: Run Test at Curosr などの機能が利用できるようになります。 また、テスト結果についても GUI で表示られるようになります。 テスト結果がエラーになっているのは、DBなどを構築せずテストだけ実行したためで、gitlab のバグではありません。 使いたいんだけど、プロジェクトの Gemfiles に組み込めないんだけど VSCode の ruby -lsp ですが、インストール時の設定では、プロジェクトに bundle install されているものを使う設定になっていると思います。そのため、プロジェクトの他のgemとの依存関係の問題で使いたくても最新の ruby-lsp が使えない、なんて問題に出会う事があると思います。 そのような場合は、 ruby-lsp などの ruby 開発サポート系ツールだけをインストールした リポジトリ を準備し、bundler でインストールした bin ディレクト リへ PATH を通しておいて、 VSCode 上の設定を変更することで、プロジェクトは独立した形で、 ruby-lsp を使うことができます。 手順は以下のようなGemfileを準備し # frozen_string_literal: true source ' http://rubygems.org ' gem ' ruby-lsp ' gem ' ruby-lsp-rails ' gem ' ruby-lsp-rspec ' 対象 ディレクト リで、 bundle config path vendor bundle install しておき、 VSCode の settings.json にいかの項目を追加します。 " rubyLsp.bundleGemfile ": "$ { インストールディレクトリ } / Gemfile " 私の場合は、 asdf を使って ruby をいれているのもあってrubocop、erblint などをひとまとめにしたチェック系ツールをまとめた リポジトリ を作り、homebrewと asdf 用の環境セットアップをするラッパー スクリプト を通して、bundle exec する シェルスクリプト から各種ツールを起動する リポジトリ を作って使っています。 また、PCが変わっても VSCode の設定が変わらないよう上記 リポジトリ を /opt/ruby_tool へcloneしています。 参考のために、 ruby_tool へのリンクも貼っておきます。頻繁にバージョンを上げたり、ツールを入れ替えたりしているので、ご利用される際は、forkしてお使いください。 最後に 最後までお読みくださりありがとうございました。 明日の記事の担当は 森田 さんです。お楽しみに。 また、株式会社 エニグモ では、ツールやエディタのカスタマイズを 愛する人 エンジニアもそうでないエンジニアも広く募集しております。興味のある方は、以下の求人一覧をご覧ください。 株式会社 エニグモ すべての求人一覧 hrmos.co
こんにちは、データアナリストの井原です。 この記事は Enigmo Advent Calendar 2024 の9日目の記事です。 私は、普段データアナリストとして エニグモ で働いています。データからビジネスの意思決定を行うための示唆を出し、関係者に正しく伝えることが主要な業務です。 最近、チームの勉強会でも話題になったのですが、データアナリストは伝え方って大事だよねという話がありました。その場ではさらっと話が進んだのですが、確かに、データアナリストはいつもポジティブな結果ばかりを伝えるわけではありません。企画者が苦労をしてリリースにこぎつけた施策で、結果も期待されていてという状況で、数字がよくなかった、または、懸念点が上がってきたということも多いです。 この記事では個人のこれまでの経験を振り返って、ネガティブなことを伝えなければいけない時に、どのようなことに気をつけたらよいかを考えてみたいと思います。 この1年を振り返ってみても、結果を伝えにくいと思う案件はいくつかありました。「事前検証ではそれなりに効果が出そうであると(自分が)押した案件が、蓋を開けてみると効果はかなり微量だった。」「これまで重点的に企画が行われてきた施策について、利益性が低そうだと伝えなければいけなかった。」などなど。 そんな自分の経験を通して、どんなことに気を付けるとよさそうか、5つ洗い出してみました。挙げてみた要素はあくまでも個人の意見ではありますし、自分も常にうまくやれているわけではないのですが、自戒もこめて書いていきたいと思います。 1.言いやすい関係性を作っておく いきなり、そもそもといった内容にはなりますが、元々の関係性はかなり重要な要素だと思います。 これは、言う側だけでなく言われる側の身を想像しても、それまで関係性の薄い第 三者 から言われるよりは、自身の取り組みを知ってくれている人の方が受け入れやすくなると考えられます。 そのためには、可能な限り、企画の段階でデータアナリストも企画に入っていけるようにするとよいと思います。また、分析の時点から入らざるを得ないとしても、こまめに質問や進捗を伝えるなどして、コミュニケーションを増やしておくといざという時には伝えやすくなります。 巨大な組織だと難しいところもあるかもしれませんが、 エニグモ は比較的少人数の組織なので、その辺りはやりやすい気はしています。 2.言葉遣いに気を付ける 当たり前ではあるのですが、伝える時の言葉遣いは気を付ける必要があります。特に意識をしていないと、さらっと否定的な言葉が(そこまで否定的だと意識せず)つい出てしまう場合があります。 例えば、「意味がない」とか「ダメですね」のような全否定の言葉は使わないように自分は気をつけます。「この数字だとちょっと厳しいかもしれません」や「想定と違うかもしれませんが、どうですか?」のような、相手と会話していく姿勢を出すだけでも建設的に話せるのではないかと思います。 前項とは反対になりますが、言いやすい関係を作りすぎてしまうと、ぽろっと否定的な言葉も出やすくなってしまう(気軽に話せるので言葉を意識しなくなりやすい)側面もあるので、この辺りはバランスを考えてうまく調整出来るとよいのではないかと思います。 3.最初に「言いづらい結果なのですが」と言ってしまう これはテクニック的な話ではありますが、会議の最初に言ってしまうと、言う方も聞く方も受け入れる準備が出来るので楽になるのではないかと思います。あるいはタイミングがあれば、「今分析しているのですが、厳しい結果が出るかもしれません」と雑談的に話しておくのもよいでしょう。 根本的にはもう少し他の項目の方が大事ではないかとも思いますが、相手に結果を受け入れてもらうということも、アナリストの重要な仕事の一つです。こういった地味なテクニックを意識しておいて損はないと思います。 4.相手に労いと尊敬の気持ちを持つ 言葉遣いなどに繋がる話ですが、施策を進めているメンバーはその企画に対してかけた労力や思いがあります。その労力や思いに対しては、労いと尊敬の気持ちを持つことが大事です。 相手の企画に対しての思いを知る過程で関係性も作れると思いますし、言葉遣いも下手な言葉遣いはしにくくなると思います。例えば、相手が大切に思っていることに対して、「意味ないよ」などとは、ある程度の良識があれば言いにくいですよね。 5.それでも遠慮はしない、自分の分析結果には自信を持つ 相手を思いやることは大事だと思いますが、一方で、データアナリストとして伝えるべきことは伝えなければなりません。 そのため、最終的には自分の分析結果に対して自信を持つことも大事です。 伝えづらい分析結果が出てきた時、多くの分析者は分析内容を振り返ったり、よさそうな点を見つけようとデータをさらに詳細に区切って細かい分析を行ったりなど、よくあるのではないかなと思っています。しかし、持てる知識をフル活用して分析を行った結果なのであれば、下した結論がネガティブな内容であっても、それはしっかりと伝える必要があります。 伝え方自体は上で書いたように気を付ける必要がありますが、結論はしっかりと相手に伝わるように、準備できればよいと思っています。 おわりに 自分の一年間を振り返りつつ、ネガティブなことを伝えなければならない時に気を付けていること(きたこと)を洗い出してみました。 世の中には、データから新しい知見を発見して、それを活用して成功しました!というポジティブな話が多いと思いますが、現実のデータ分析では、新しい発見やポジティブな結果が確認できることばかりではありません。むしろ、特に目新しくない結果、ネガティブな結果が出てくる方が多いと思われます。 データアナリストという立ち位置は、時に第 三者 的な視点での意見が求められる職種ですので、データの解釈は冷静に客観的に行わなければなりません。しかし、それを伝えるところまで責任を持つという点では、合理性や客観性以外の人情的なコミュニケーションも必要になってくると思います。 少しでも、今回の話がデータを取り扱う方々の参考になったら幸いです。お読みいただきありがとうございました。 明日の記事の担当はエンジニアの後藤さんです。お楽しみに。 株式会社 エニグモ すべての求人一覧 hrmos.co
こんにちは!Webアプリケーションエンジニアの レミー です! この記事は Enigmo Advent Calendar 2024 の 8日目の記事です。 Ruby on Rails 8が新しくリリースされ、Kamalという迅速かつ便利なデプロイツールが統合されました。私はこれまで Ruby on Rails アプリケーションのデプロイに Capistrano を使用していましたが、Kamalを試してみると、その便利さと簡単さに魅了されました。 この記事では、Kamalを使用して Ruby on Rails 8アプリケーションを AWS EC2サーバーにデプロイする手順を詳しく説明します。 Kamalとは? Kamalは 37Signals のチームによって開発された新しいデプロイメントツールです。このツールを使用すると、デプロイメントプロセスを1つのファイルで定義でき、複雑な手順を簡素化できます。ほとんどのアプリケーションで考慮する必要がない複雑な部分を省略することが可能です。Kamalは、DockerコンテナやTraefikなどのソフトウェアを組み合わせて動作します。セットアップ段階から包括的なソリューションを提供し、 Rails アプリケーションを最短時間で本番環境にリリースできます。 Kamalはどのように動作するのか? Kamalは、サーバー上でDockerコンテナ内にWebアプリケーションを実行し、Traefikを組み合わせてネットワーク トラフィック を処理します。新しいバージョンをデプロイする際、Kamalは以下の手順を実行します: 新しいDockerイメージをビルドする。 新しいイメージからコンテナを起動する。 新しいコンテナが正常に動作しているかを検証する。 Traefikを更新して、 トラフィック を新しいコンテナにルーティングする。 古いコンテナを停止する。 特に注目すべき点は、ダウンタイムなしのデプロイメントを実現し、blue/greenデプロイメントをサポートしていることです。 Ruby on Rails アプリケーションのサーバーの準備 ここでは、 AWS EC2でサーバーを準備します。 EC2 インスタンス の作成 Ubuntu Server 24.04 を選択します。 Keyペアは SSH とサーバーへのアクセスに使用します。 セキュリティグループでは、以下のポートを開放してください: 22 : SSH 用 80 : HTTP用 443 : HTTPS 用 SSH 権限の設定 まず、ローカルの公開鍵をコピーします。 cat ~/.ssh/id_rsa.pub インスタンス を起動したら、先ほど作成したKeyペアを使って、 ubuntu ユーザーでサーバーに SSH 接続します。 IPアドレス は新しく作成した インスタンス のものを使用します。 ssh ubuntu@ 18.182 . 197.19 -i /path/to/key.pem 次に、ローカルでコピーした公開鍵をサーバーの ~/. ssh /authorized_keys ファイルに貼り付けます。 ssh-rsa AAAABEAAAADAQABAAABgQ...AAAAB3Nzac2EAAAADAQABA Yuto MacBook Pro これでサーバーの準備は完了です。以降は以下のコマンドでサーバーに SSH 接続できます。 ssh ubuntu@ 18.182 . 197.19 Ruby on Rails 8アプリケーションの作成 まず、kamaltest という名前で Ruby on Rails 8アプリケーションを作成します。 rails new kamaltest プロジェクト ディレクト リに移動します。 cd kamaltest このアプリケーションは、タイトルと内容を持つ簡単なブログになります。そのため、scaffoldを使用して構築します。 rails g scaffold article title content:text データベースを作成します。 rake db:migrate routes.rb ファイルでホームページを設定します。 root "articles#index" アプリケーションを起動します。 bin/dev その後、 http://localhost:3000 にアクセスすると、アプリケーションが起動していることが確認できます。 deploy.ymlファイルでKamalを設定する Ruby on Rails 8ではKamalが既に統合されています。それ以前のプロジェクトにKamalを追加する場合は、Gemfileに kamal を追加し、以下のコマンドを実行します。 kamal init このコマンドを実行すると、Kamalがアプリケーションをデプロイするための設定を含む config/deploy.yml ファイルが生成されます。 Kamalの基本設定 デプロイのための設定は以下のようになります。 image: yutoyasunaga/kamaltest servers: web: - 18.182.197.19 # server IP proxy: ssl: true host: kamaltest.sampleapp.net registry: username: yutoyasunaga password: - KAMAL_REGISTRY_PASSWORD ssh: user: ubuntu image : Docker Hub上に保存されるDockerイメージの場所 servers web : サーバーの IPv4 アドレス proxy ssl : true に設定すると SSL が自動的に設定されます host : 使用する ドメイン registry username : Dockerアカウントのユーザー名 password : Dockerアカウントのログ インパス ワード。セキュリティのため、 KAMAL_REGISTRY_PASSWORD シークレットを通じて取得します。 ssh user : サーバーに SSH 接続する際のユーザー Kamalのシークレット 機密情報は .kamal/secrets ファイルに配置します。以下はその例です。 # 環境変数からレジストリパスワードを取得 KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD シークレットは 環境変数 から取得します。例えば、Dockerのパスワードが hogehoge の場合、以下のように設定します。 export KAMAL_REGISTRY_PASSWORD= 'hogehoge' 以下のコマンドで値を確認できます。 echo $KAMAL_REGISTRY_PASSWORD Kamalでデプロイを開始 config/deploy.yml の設定が完了したら、以下のコマンドを使用して初回のデプロイを開始します。 kamal setup このコマンドは以下の処理を行います: SSH キーを使ってサーバーに接続。 サーバーにDockerがインストールされていない場合は、get.docker.com を使ってDockerをインストール( SSH 経由でrootアクセスが必要)。 レジストリ にローカルおよびリモートでログイン。 アプリケーションのルートにある標準的なDockerfileを使用してDockerイメージをビルド。 イメージを レジストリ にプッシュ。 レジストリ からイメージをサーバーにプル。 kamal-proxy がポート80および443で トラフィック を受け入れることを確認。 現在のGitバージョンのハッシュに一致するアプリケーションバージョンで新しいコンテナを起動。 新しいコンテナが GET /up リク エス トに対して200 OKを返す場合、kamal-proxy に トラフィック を新しいコンテナにルーティングさせる。 前バージョンのアプリケーションを実行している古いコンテナを停止。 使用されていないイメージや停止したコンテナを削除して、サーバーの容量を確保。 デプロイが成功すると、次のような結果が表示されます。 Finished all in 70.0 seconds 初回のデプロイは kamal setup を使用しますが、2回目以降のデプロイでは kamal deploy のコマンドを使用します。 permission denied エラーの解決方法 初回のデプロイで以下のようなDockerに関連するエラーが発生した場合: Releasing the deploy lock... Finished all in 47.9 seconds ERROR (SSHKit::Command::Failed): Exception while executing on host 54.250 . 243.158 : docker exit status: 1 docker stdout: Nothing written docker stderr: permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post "http://%2Fvar%2Frun%2Fdocker.sock/v1.47/images/create?fromImage=yutoyasunaga%2Fkamaltest&tag=4985d03bae739286203ce1185efdd4b2c71f90a9" : dial unix /var/run/docker.sock: connect: permission denied 次の手順で解決できます。 サーバーに SSH 接続します。 ssh ubuntu@ 18.182 . 197.19 以下のコマンドを実行して、現在のユーザーをDockerグループに追加します。 sudo usermod -aG docker $USER && newgrp docker Dockerが正常に動作するか確認します。 docker ps 以下のような出力が表示されれば成功です。 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES デプロイ後の結果を確認 まず、 https://hub.docker.com にアクセスして、イメージがプッシュされていることを確認します。 Route53を使用して ドメイン を管理している場合、以下のようにホストゾーン内でレコードを作成し、 ドメイン をサーバーのIPに向けます。 設定が正しい場合、 https://kamaltest.sampleapp.net にアクセスすると、アプリケーションの画面が表示されます。 このように、1分ほどで Ruby on Rails アプリケーションをサーバーにデプロイし、 ドメイン と SSL の設定まで完了しました。便利ですね! 🎉 よく使われるKamalコマンド aliases: console: app exec --interactive --reuse "bin/rails console" shell: app exec --interactive --reuse "bash" logs: app logs -f dbc: app exec --interactive --reuse "bin/rails dbconsole" デフォルトの deploy.yml ファイルでは、以下の4つの便利なコマンドが定義されています。 kamal console : Rails コンソールにアクセス kamal shell : サーバー上のコンテナにアクセス kamal logs : サーバーログを確認 kamal dbc : データベースコンソールにアクセス Kamalでのアセットのデプロイ RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile Dockerfile にはすでにアセットをプリ コンパイル するコマンドが定義されています。そのため、デプロイ時にアセットも自動的に処理されます。 Kamalでの 環境変数 (Environment Variable) 開発環境の 環境変数 の設定 開発環境では、 dotenv gem を使用します。 公式 リポジトリ : https://github.com/bkeepers/dotenv Gemfileに dotenv を追加 以下のコードを Gemfile に記載します。 group :development, :test do gem 'dotenv' end 環境変数 を管理する .env ファイルを作成し、例は以下のように記載します。 TEST_ENV_CLEAR=env_clear_local TEST_ENV_SECRET=env_secret_local 環境変数 が正しくロードされているかを Rails コンソールで確認します。 ENV .select { |key, _| key.start_with?( " TEST_ENV " ) } => { " TEST_ENV_CLEAR " => " env_clear_local " , " TEST_ENV_SECRET " => " env_secret_local " } 本番環境の 環境変数 の設定 現時点では以下の方法を使用していますが、より良い方法が見つかれば変更する予定です。 本番環境用の 環境変数 を管理する .env.production ファイルを作成します。 TEST_ENV_SECRET=env_secret_prod deploy.yml ファイルに 環境変数 を設定します。 env: secret: - TEST_ENV_SECRET clear: TEST_ENV_CLEAR: env_clear_prod clear : deploy.yml ファイルに直接記載される 環境変数 。 secret : 機密情報を含む 環境変数 で、 .kamal/secrets ファイルから読み込まれます。 .kamal/secrets ファイルで 環境変数 を以下のように設定します。 TEST_ENV_SECRET=$(cat .env.production | grep TEST_ENV_SECRET | cut -d '=' -f 2) 上記の設定は、 .env.production ファイルから情報を抽出します。 例えば、 .env.production に以下の行が含まれている場合: TEST_ENV_SECRET=env_secret_prod コマンド cut -d '=' -f 2 によって env_secret_prod が抽出されます。 デプロイ後、 Rails コンソールにアクセスして 環境変数 を確認できます。 kamal console ENV .select { |key, _| key.start_with?( " TEST_ENV " ) } => { " TEST_ENV_SECRET " => " env_secret_prod " , " TEST_ENV_CLEAR " => " env_clear_prod " } Kamalは、 Ruby on Rails アプリケーションのデプロイを大幅に簡素化し、効率化する強力なツールです。ぜひKamalを使ったデプロイに挑戦してみてください! 株式会社 エニグモ すべての求人一覧 hrmos.co
こんにちは!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