はじめに こんにちは、計測プラットフォーム部バックエンドチームの高木( @TAKAyuki_atkwsk )です。 ZOZOMAT システムではAPIのリバースプロキシとして Envoy および付随するgRPC-JSON transcoderを導入しています。これらによって依存するサービスにレガシーなサーバーが存在していても部分的に gRPC を採用しモダンなアーキテクチャを広めようとしていることについて紹介します。 なお、本記事は以下記事のシリーズとなっておりますので合わせてご覧いただけるとZOZOSUITやZOZOMATなど計測プラットフォーム部の取り組みについてより深くご理解頂けるかと思います。 techblog.zozo.com ZOZOMATシステムとEnvoyについて ZOZOMATシステム構成におけるEnvoyの役割について簡単に説明しておきます。クライアント(ZOZOTOWNアプリ等)とZOZOMAT APIサーバー(以下ZOZOMATサーバー)の間にリバースプロキシとしてEnvoyを配置し主に以下の役割を担っています。 TLSの終端 ルーティング gRPCリクエストの負荷分散 HTTP JSON APIとgRPCの相互変換 gRPCをシステムに導入する際、負荷分散の観点でZOZOMATサーバーの前段にEnvoyを置く構成を想定していました。以下の記事がZOZOMATシステムの構成として似ているものだったので参考にさせていただきました。 Envoy プロキシを使用して GKE 上で gRPC サービスの負荷分散を行う | ソリューション | Google Cloud Amazon EKSでgRPCサーバを運用する - 一休.com Developers Blog 先ほど挙げたEnvoyが担当する役割の中の「HTTP JSON APIとgRPCの相互変換」機能、これがgRPC-JSON transcoderなのですが詳しくは記事の後半で触れていきます。ちなみにリバースプロキシとしてはnginxも候補として有力ではありますが、上記の事例に後押しされてEnvoyを使うことにしたという経緯がありました。 また、ZOZOMATのシステム構成についてはこちらの記事で詳しく説明されていますので合わせてご覧ください。 techblog.zozo.com ZOZOSUITを振り返って ZOZOMATでの取り組みを紹介する前に ZOZOSUIT での課題について触れておきます。ZOZOSUITはZOZOMAT以前に発表された体のサイズを測るツールおよびシステムで、このシステムはZOZOTOWNアプリ、ZOZOTOWNサーバーと連携します。 ZOZOSUITで扱うデータの中に3Dデータというものがあります。これは、計測時にZOZOTOWNアプリで生成されてZOZOSUITサーバーに送られます。また計測結果を参照する際にもZOZOSUITサーバーからZOZOTOWNサーバーを経由し最終的にZOZOTOWNアプリに送られます。 この3Dデータはサイズが大きいためデータ送信で少なからず時間がかかってしまうことが課題でした。 また、データのシリアライズ方法に関してはZOZOTOWNアプリとZOZOSUITサーバー間ではmsgpack、ZOZOTOWNサーバーとZOZOSUITサーバー間ではJSONを利用していました。このため、サーバー側の開発者はmsgpackで扱うための実装やJSONで扱うための実装をエンドポイントごとに意識する必要がありました。 以下の図では各コンポーネントとプロトコル・シリアライズ方法の関連を示しています。 ZOZOMATでの解決策 ZOZOMATでもZOZOSUIT同様の3Dデータを利用するため、前述の問題を解決しておく必要がありました。そこで、ZOZOMATではgRPC + protocol buffersを利用することにしました。 大きなサイズのデータ送信に関して検証するためシリアライズ方式によるデータサイズの比較を簡単に行いました。今回は3Dデータを表す以下のようなデータを利用し、それぞれの方式でシリアライズを行いました。 { " faces ": [ [ 1444 , 1453 , 1435 ] , [ 1444 , 1435 , 1416 ] , ... ] , " verticies ": [ [ 0.19088252916100196 , -0.025855744239442646 , 0.0743707061820768 ] , [ 0.1902995247773912 , -0.029133848767236424 , 0.07462623400745416 ] , ... ] , " rings ": [ [ [ 0.06820038723992675 , 0.01848489483093979 , -0.37634277191524135 ] , [ 0.06906485745721565 , 0.02409594156229787 , -0.3780734263629579 ] , ... ] , ... ] } 結果は以下の通りで、JSONと比較してmsgpackとprotocol buffersはそれぞれ約半分のサイズになることが分かりました。 方式 サイズ(bytes) JSON 431,413 msgpack 210,388 protocol buffers 211,895 msgpackとprotocol buffersではサイズに大きな違いは見られませんでした。ただ、gRPCを利用することでHTTP/2のヘッダー圧縮など効率的な通信が見込まれるため全体としてはデータ送信について更なる改善が期待できるという判断になりました。 また、protocol buffersのメッセージとサービスを定義するprotoファイルをバックエンドおよびアプリ開発者の間で共有しAPI定義の連携漏れを防ぐことができる利点もありました。さらに、protocコマンドによってprotoファイルからバックエンド・iOS/Androidアプリ開発用言語に対応したインタフェースのソースコードが自動生成されます。そのためスキーマ変更によるメンテナンスコストも低くなった印象でした。 gRPC導入にあたっての課題 gRPCを導入してめでたしめでたし。とはいかないもので、ZOZOTOWNアプリではgRPC実装ができる見込みだったのですが連携するZOZOTOWNサーバーではgRPCの実装が厳しい見込みでした。ZOZOTOWNサーバーは現在VBScriptで構成されるレガシーシステムであり、リプレイスを進めているためです。結果、ZOZOTOWNサーバーとZOZOMATサーバーの通信方式については課題が残った状態でした。 しかし、これらの検討を行いながら調査していた時にEnvoyの機能の1つに gRPC-JSON transcoder というものがあることに気がつきました。これは、HTTP JSON APIクライアントがEnvoyにリクエストを送るとgRPCサービスにプロキシしてくれるものです。 ZOZOTOWNアプリからはgRPCで、ZOZOTOWNサーバーからはJSON APIでリクエストされます。途中にあるEnvoyでJSONのリクエストをgRPCに変換してくれるのでZOZOMATサーバーはリクエストをgRPCで統一して扱うことができるようになります。以下の図は各コンポーネントとそれぞれの間のリクエスト形式の関係になります。 似たようなアプローチのツールの1つに grpc-gateway があります。こちらもHTTP JSON APIリクエストをgRPCに変換して背後にあるgRPCサーバーへプロキシするものです。以下の図にgrpc-gatewayを利用する構成の例を示します。gRPCとHTTP JSON APIでリクエストの経路を分離する場合やEnvoyではないコンポーネントを使う場合は良いかもしれません。一方ZOZOMATシステムの場合、両者とも同じ経路となるためEnvoyを利用、かつgRPC-JSON transcoderを有効にした構成となっています。 gRPC-JSON transcoderの利用方法 HTTP JSON互換にするためにはprotoファイルに設定を追加する必要があります。ここではZOZOMATで足のサイズを測ると見れるようになるシューズの相性度 1 を取得するAPIを例として紹介します。以下にシューズの相性度を取得する疑似rpcを定義したprotoファイルを示します。 // シューズの相性度を取得するrpcのサンプル定義 syntax = "proto3" ; package shoes; // importする必要がある import "google/api/annotations.proto" ; service Shoes { rpc ListMatchingRates (ListMatchingRatesRequest) returns (MatchingRates) { // HTTP JSONでやり取りしたい場合はここを追加する option (google.api.http) = { get: "/shoes/{item_id}/sessions/{session_id}/matching-rates" }; } } message ListMatchingRatesRequest { string item_id = 1 ; string session_id = 2 ; } message MatchingRates { string item_id = 1 ; string session_id = 2 ; repeated MatchingRate matching_rates = 3 ; } message MatchingRate { string size_label = 1 ; float rate = 2 ; } ここでのポイントはrpcに対して option (google.api.http) を追加してHTTP JSON APIエンドポイントの定義を行うことです。上記の例ですと https://[domain]/shoes/123abc​/sessions/abc456/matching-rates というエンドポイントに対してGETでリクエストできるようになります。gRPC-JSON transcoderを経由したレスポンスは以下のように MatchingRates のデータ構造がJSON化されたものになります。 { " itemId ": " 123abc ", " sessionId ": " abc456 ", " matchingRates ": [ { " sizeLabel ": " 25 ", " rate ": 0.9543 } , { " sizeLabel ": " 24.5 ", " rate ": 0.8561 } ] } これらの設定をgRPC-JSON transcoderに渡すためには、protocでdescriptor setというものを生成しEnvoyの設定で参照します。詳しくはこちらの ドキュメント を参考にしてください。 gRPC-JSON transcoderを採用しての振り返り 採用してみて良かったポイントを挙げてみます。 gRPCを使って通信する前提で設計や実装が可能になった ZOZOTOWNサーバーのようなgRPC未対応のクライアントからでもHTTP JSON APIでアクセス可能になった HTTP JSON API用のエンドポイントをZOZOMATサーバー側に実装する方法ではリクエストボディを処理で扱うデータ型に変換する部分がgRPCとJSONの2種類考える必要があります。一方、リクエストがgRPCのみになるとその部分については考えなくてよくなるため実装がよりシンプルになります。 さらに、HTTP JSON APIにおいてもprotocol buffersによってインタフェースの定義を管理できる恩恵を受けられます。 また、将来的にZOZOTOWNサーバーがgRPC対応するという場合を考えると、gRPC-JSON transcoderの設定やprotoファイル上から google.api.http オプションを削除する必要がありますが、ZOZOMATサーバーの実装およびEnvoyの設定は何一つ変える必要がないため移行もスムーズになると考えられます。 一方、注意すべきポイントとして int64 で扱う値はJSONのレスポンスで数値を表す文字列の値に変換される点です。こちらの ドキュメント にprotocol buffersとJSONの対応表が載っており、JSONのレスポンスはこれに沿う形で出力されます。 2 さいごに 今回はEnvoyのgRPC-JSON transcoderを利用して部分的にHTTP JSON APIを生かしながらgRPCでコンポーネント間通信を実現することを紹介しました。個人的にはEnvoyを導入するのは初めてだったので、今のところZOZOMATシステムの構成でうまく機能していてホッとしています。何らかの制約により一部のコンポーネントでgRPCが導入できない場合でもこのような相互変換する層があると全体としてうまくいくというのは、gRPCに限らず他の技術においても応用できそうだなと思います。 計測プラットフォーム部バックエンドチームでは、ZOZOMATでより精度の高いサイズを推奨するバックエンドエンジニアを募集しています。ご興味のある方は、以下のリンクからぜひご応募ください! www.wantedly.com 相性度とは、サイズ感に満足できる確率です。足のサイズが同じ人でも、シューズを履いた場合に、どのサイズがピッタリと感じるかは、シューズの形、足型、締め付け感や、ゆとりの好み等により異なります。そこでZOZOではZOZOMATの計測データ、シューズデータ、お客様にお答えいただいたアンケートデータなどを元に、お客様がそのシューズを履いた場合に、サイズ感に満足いただける確率を「相性度」として、サイズ別に表示しています。(ZOZOTOWN >【ZOZOMAT】相性度の高いシューズ ページの説明より引用) ↩ When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the proto to JSON conversion must follow the proto3 specification. ( https://github.com/googleapis/googleapis/blob/master/google/api/http.proto#L288-L290 より引用) ↩
こんにちは、ZOZOテクノロジーズ CTO室の池田( @ikenyal )です。 ZOZOテクノロジーズでは、7/22に ZOZO×一休×PayPay AWS Night を開催しました。 zozotech-inc.connpass.com ZOZO×一休×PayPay AWS Nightは、ZOZOテクノロジーズ・一休・PayPayの3社による合同イベントです。 登壇内容 まとめ 弊社のエンジニア2名、一休とPayPayからもエンジニアが1名ずつ登壇し、各社での日頃のAWSの活用事例の紹介を行いました。 Fulfillment by ZOZOと中国版ZOZOTOWNでのAWS活用事例 (ZOZOテクノロジーズ:岡元 政大) AWS Single Sign-Onを用いた、セキュアでより良いログイン体験への取り組み (ZOZOテクノロジーズ:光野 達朗 / @kotatsu360 ) 一休のリアルタイム施策を支えるサーバレスログ基盤 (一休:清水 一輝) PayPayでのAWS活用事例について (PayPay:西中 智樹 / @_tomoki_n ) Fulfillment by ZOZOと中国版ZOZOTOWNでのAWS活用事例 AWS Single Sign-Onを用いた、セキュアでより良いログイン体験への取り組み 一休のリアルタイム施策を支えるサーバレスログ基盤 PayPayでのAWS活用事例について 最後に ZOZOテクノロジーズ、一休、PayPayの各社で、AWSを用いたプロダクト開発を行うエンジニアを募集しています。 一緒にサービスを作り上げてくれる方はもちろん、エンジニアの技術力向上や外部発信にも興味のある方を募集中です。 ご興味のある方は、以下のリンクからぜひご応募ください! tech.zozo.com www.ikyu.co.jp about.paypay.ne.jp
はじめに こんにちは。ZOZOテクノロジーズBtoB開発部の三好です。 今回はServerlessなシステムの監視、保守、運用を行う中で、これまでやってきたことや、その反省点などを紹介します。マネージドサービスでの開発・運用で同じ苦悩をされている、もしくはこれからマネージドサービスを利用して構築する方々の参考になれば幸いです。 サービス紹介 私の所属するBtoB開発部は、 Fulfillment by ZOZO (以下FBZ)を提供しています。FBZはZOZOBASEと自社EC、店舗の在庫を一元化し、在庫切れの無い世界を実現するためのサービスです。 その中で、ZOZOBASEと自社ECをつなげるためのデータ連携API開発を担っています。 なぜ監視をするのか 私たちは、日々APIが正常に稼働しているか、不正なデータが発生していないかを監視してます。マネージドなサービスを使って、Serverlessなシステムと聞くと、保守は不要と思われる方もいらっしゃるかもしれません。 しかし、想定していない運用によるイレギュラーデータへのリカバリ対応や、プログラムのバグはありえます。さらに、高負荷による処理遅延の発生などシステムの監視、保守は必要になります。 安定したユーザー体験を実現するために私たちは日々システム監視を続けています。 監視の基本的な仕組み 私たちはAPIサービスを開発し運用をしておりますが、FBZのAPIは、AWSのLambda + API Gateway + その他マネージドサービスにて構成されています。その中でも今回はLambdaのログ監視について紹介させていただきたいと思います。 Lambdaでは、CloudWatch Logsにログを出力してます。Lambda内でINFO、WARNING、ERROR等のログレベルを設定した上でCloudWatch Logsにログ出力をするような仕組みになっています。 CloudWatch Logsではロググループに対し「サブスクリプションフィルタ」を設定できます。サブスクリプションフィルタにLambdaを設定すると、出力されたログをLambdaで処理する事ができます。 FBZのAPIではこの機能を使ってERRORログを検知し、検知したERRORログをPagerDuty、Datadogに通知する仕組みを組んでいます。 これをベースにエラーを検知し監視対応を行っています。 運用で発生した課題 この仕組みでしばらく運用した結果「エラーのノイズが多く対応しきれない」といった課題が発生しました。 サービスの成長と共にエラーが増大していきました。一番多い時では一日に起票されるエラーが何百というレベルで、常にPagerDutyの通知がされているような状態でした。 こういった状況が続くと以下のような状態になってきます。 数が多すぎる上に、ほとんどノイズなので次第に見なくなる 本当に対応すべきエラーが埋もれる リカバリや修正対応が遅くなり、外部や運用者に迷惑をかけてしまう エラーノイズが増えてしまっていた原因 なぜノイズが増えてしまっていたのかを考察した結果、主な原因は以下の3つでした。 そもそものログレベル設定が間違っている ログレベルの変更で対応できないエラーの存在 ノイズをフィルタをすることへのハードルが高い 1. そもそものログレベル設定が間違っている WARNINGやINFOで良いものまでERRORで出力されている状態でした。例外が発生した場合はほぼ全てがERRORログとして出力されている状態で、適切なログレベルが設定されている状態ではありませんでした。 2. ログレベルの変更で対応できないエラーの存在 Lambdaに記述したコード内でハンドリングできているエラーの場合は適切にログレベルが設定され通知されます。しかし、開発者がエラーハンドリングできない部分で発生したエラーについては、そもそもログレベルが設定できません。 例えば、メモリエラーやタイムアウトエラー、importエラーなどがこれに該当します。これらはログレベルが設定できないエラーであるため、発生を知らせる特定の文字列を元に検知する仕組みでした。 Lambdaのタイムアウトであれば、 Task timed out after X seconds が検出されたらエラーとして通知するといった仕組みでした。そのため、Lambda関数毎に検知する、しないの制御ができていませんでした。 3. フィルタを適用することへのハードルが高い 元々エラー検知されてノイズと判定されたものは、PagerDutyのフィルタ機能を使ってフィルタを行なっていました。 しかし、PagerDutyのフィルタではシンプルな条件でしかフィルタをできないため、特定のLambdaの特定のエラーのみフィルタするなど細かな制御ができませんでした。 また、PagerDutyでは実際にフィルタされたものの確認ができません。本来検知するべきだったエラーまでフィルタしてないだろうかという不安が常にありました。 上記のような理由から、PagerDutyのフィルタ機能は積極的には利用されず、結果ノイズとして残ってしまっている状態でした。 解決に向けたアクション これらの課題を解決するために、以下の2つのアクションをとりました。 ログレベルの見直し フィルター機能の強化 根本的な解決としては、ログレベルを適切に設定する事を目標としました。ただ、ログレベルの見直しでは改善できないノイズを削減するために、フィルタ機能を強化するアクションも合わせて行いました。 ログレベルの見直し まず行ったのは、ログレベルの見直しです。ほぼ全てがERRORとして出力されている状態から、ノイズとなっているログのログレベルを見直し、PagerDutyへ通知されないようにしました。 フィルター機能の強化 ログレベルの見直しでは解決できないノイズを削減するためにフィルタ機能の強化を行いました。大まかなアーキテクチャとしては以下の図のようになります。これまでERROR文字列を検知してPagerDuty、Datadogに通知していた機能を拡張し、この部分にフィルタ機能をもたせました。 フィルタのためのロジックはPythonで記述しています。当初設定値としてコード外で管理する案もあったのですが、自由度と後述する自動テストを入れるという観点からPythonにてロジックを記述しています。 また、エラーをフィルタするハードルを下げるために、工夫した点が2つあります。 1点目はフィルタのロジックに対して自動テストを書いていることです。フィルタをする際、本来通知されるべきものまでフィルタしてないか不安になります。ロジックの正当性を担保するため、フィルタの追加と共に自動テストも追加しました。 2点目はフィルタした結果が分かるようにしたことです。フィルタを導入した後に想定している件数とのギャップがないかを確認する目的と、フィルタした件数をLambda毎に集計し異常値検出をする目的です。 運用から得た教訓(みんなの安眠のために) 上記のようなアクションをとった結果、最大500件近く起票されていたエラーを少ない日は数件という程度に抑えることができました。 そして、運用をしていく中で以下の教訓を得ることができました。 エラー出力をする前に、なんのために必要なのか考えないといけない エラーが発生したが、再実行やリトライによって成功する(成功した)エラー 手動でデータ補正等を行う必要があるエラー 再実行やリカバリは不要・できないが、見逃せないエラー(バグなど) etc. 様々なパターンがありますが、よく自分も陥りがちなのは、「色々な例外がありそうだからとりあえずERROR出力をする」といったパターンです。とりあえず設定したログレベルは、一度リリースされると改修による不具合発生のリスクや、リソースの都合でななかな変更できない事が多いと思います。 本当に検知や対処が必要なものであればERROR出力は必要です。しかし、ERROR通知を受けた後何もしないのであれば、通知する必要はないのかもしれません。実装の時点で、検知したエラーを元に何をしたいのかを考慮した上で適切なログレベルをつける事が大切です。 ノイズは放置しない ノイズが多いという場合、ほとんどが急に発生したものではなく時間をかけて蓄積されたものです。少ないノイズだからと放置すると、いつの間にかノイズだらけ、という状態になります。 ただ、ノイズとして残っているものがある場合、除去できない理由があるのかもしれません。今回はフィルタを作成する事でノイズを除去する事ができました。 今ノイズとして残っている理由を見極め、効果のあるところから地道に改善していくという事の大切さを実感しました。 これからやっていきたいこと 今回はノイズへの対策をメインに紹介させていただきましたが、これからやりたい事はたくさんあります。 エラー発生後に自動でリカバリをする仕組みの構築 インシデントの適切な優先度付け SREチームとの連携 上記にあげたような、そもそもエラーとして検知しなくて済む仕組み作りや、検知後の運用がスムーズに回る仕組みなどに注力できればと考えています。 現状監視できていない部分や、そのリスクを把握しながら、逐次改善のアクションへとつなげていきたいです。ちょっとした改善で劇的に変わるものもあれば、なかなか手強い解決できない課題もあります。日々挑戦しながら、改善を進めていきたいと思っています。 さいごに 私たちは、AWSのマネージドサービスをフル活用し、ServerlessなAPIサービスを提供しています。失敗と成功を繰り返して、エンジニアとしてのスキルアップを目指しています。 ZOZOテクノロジーズでは、一緒にサービスを作り上げてくれる仲間を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください! tech.zozo.com
作った経緯 AWS料金Botの機能 実装 気をつけるべき点 Lambdaのコードをアップロードする際の問題 Serverless Application Modelの採用 CloudFormationとServerless Application Modelの比較 まとめ 最後に こんにちは。今年の4月に新卒で入社し、SRE部MA基盤チームに配属された川津( @jon_ground )です。 MA基盤チームではMAで利用しているインフラの使用料金が把握できていない問題があります。そこで気軽に料金を確認できるようにAWSの料金をSlackに報告してくれるBotを作成しました。本記事では上記の問題を解決するため、Botを作成するまでに至った経緯や数ヶ月運用して得られたメリットについて紹介します。 作った経緯 MA基盤チームでは、MA関連のインフラをAWS上で構築しており、開発、ステージング、本番と環境毎にAWSのアカウントを分けて運用しています。AWSの使用料金を確認する際、都度料金を確認するために各環境のAWSのコンソールからCost Explorerを確認する必要があり非常に面倒な作業でした。またCost Explorerを閲覧するためには強い権限が必要であり、確認できる人が限られていました。これによって料金が上がった部分を見落としてしまい、検証用のインスタンスを落とし忘れてしまったり、必要以上にAWSのリソースを使用してしまったことがありました。 こうした無駄な消費を避けるためにチームの人が毎日確認でき、リソース毎の使用料金も確認できるようにする必要がありました。サービスを運用する上でインフラのコストを意識し、妥当な料金であるか把握することは大事です。 AWS料金Botの機能 現状の問題点を解決するためには以下の要件を満たす必要がありました。 チームの人が毎日AWSの料金を把握できるようにする AWSの強い権限を持っていない人でも料金を確認できるようにする 上記の要件を満たすためにMA基盤チームが利用しているSlackのChannelに料金の情報を流すことにしました。理由としては対象のChannelで勤怠の確認を行っており、毎朝チーム全員が必ずSlackを見るからです。また、ZOZOテクノロジーズのSlackは原則オープンチャンネルなのでMA基盤チーム以外の人も見ることができ、強い権限を持っていなくてもAWSの料金を確認できます。 Slackに料金の情報を表示する方法に AWS チャットボット を利用する方法もあります。しかしリソース毎に料金が知りたい点とリソースの使いすぎを防止するために一昨日と昨日の料金の差分を表示し、利用料金がどれだけ変化したのかを知りたい要望があり、今回の要件には合わないためBotを自作することにしました。 作成したアプリケーションの機能としては、平日朝10時に対象のSlack Channelに各環境毎の使用料金をまとめたメッセージを送ります。また、使用料金の内訳も表示されます。全リソースの使用料金を表示するとメッセージが長くなるので、1USD以下の金額のリソースはその他の金額にまとめています。 実装 今回使用した言語、ツールは下記のものです。 言語:Go 1.14 プロビジョニングツール:CloudFormation(CFn)、Serverless Application Model(SAM) アーキテクチャーは下記の通りです。 作成されたアプリケーションはLambda上で動作しており、CloudWatch Eventsでcronを平日10時に設定してLambdaを動かしています。また、AWSの料金の取得はAWSのCost Explorer APIを使って取得しています。 今回使用したGo言語ではAWS SDKが提供されているので GetCostAndUsage を呼び出すことによりAWSの使用料金を取得できます。具体的には以下のコードのように関数を作成し料金を取得できるようにしました。 package costexplorer import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/costexplorer" "github.com/aws/aws-sdk-go/service/costexplorer/costexploreriface" ) type CostExplorer struct { session costexploreriface.CostExplorerAPI } func NewCostExplorer(session costexploreriface.CostExplorerAPI) CostExplorer { return CostExplorer{ session: session, } } func (c CostExplorer) GetCostForDaily(time_start string , time_end string , metrics [] string ) (*costexplorer.GetCostAndUsageOutput, error ) { granularity := aws.String( "DAILY" ) metric := aws.StringSlice(metrics) resp, err := c.session.GetCostAndUsage(&costexplorer.GetCostAndUsageInput{Metrics: metric, Granularity: granularity, TimePeriod: &costexplorer.DateInterval{Start: aws.String(time_start), End: aws.String(time_end)}}) if err != nil { return nil , err } return resp, nil } func (c CostExplorer) GetCostDetail(time_start string , time_end string , metrics [] string ) (*costexplorer.GetCostAndUsageOutput, error ) { granularity := aws.String( "DAILY" ) metric := aws.StringSlice(metrics) group := costexplorer.GroupDefinition{Key: aws.String( "SERVICE" ), Type: aws.String( "DIMENSION" )} resp, err := c.session.GetCostAndUsage(&costexplorer.GetCostAndUsageInput{GroupBy: []*costexplorer.GroupDefinition{&group}, Metrics: metric, Granularity: granularity, TimePeriod: &costexplorer.DateInterval{Start: aws.String(time_start), End: aws.String(time_end)}}) if err != nil { return nil , err } return resp, nil } 気をつけるべき点 Lambdaのコードをアップロードする際の問題 最初はCFnを使って全てのインフラリソースを管理していました。しかし、Lambda部分をCFnでInfrastructure as code(IaC)化している最中、Lambdaのコードをアップロードする部分で問題が発生しました。CFnではLambda上で動かすコードをCFnに直接埋め込む形と、S3で管理する形があります。 前者は AWS::Lambda::Function リソースの Code プロパティの ZipFile で管理できます。 しかし全ての言語に対応しているわけではなく、AWS料金Botの作成時点ではNode.jsとPythonしか対応していませんでした。また、CFnにコードを埋め込むとファイルが肥大化し、インフラとアプリケーションのコードが混ざってしまうのでコードの管理が大変になります。 後者ではLambdaのリソース作成時点でS3に対象のファイルが存在していない場合はエラーになります。そのため、Lambdaの作成リソースとLambdaのコードを管理するS3 bucketの作成リソースを同じCFnで管理すると、S3 bucketの中身は空なので作成エラーが発生します。 上記の理由によりCFnを適用する前にS3 bucketを作成し、Lambdaで動かすコードを事前にアップロードする必要があります。また、コードの変更がある際は対象のファイルのobject versionを S3ObjectVersion に指定しなければならず、コードの変更がある毎にobject versionを確認してCFnに変更を加えなければなりません。 Serverless Application Modelの採用 前述の理由からCFnでLambdaを管理するのには限界がありました。代替の方法がないかチームの人と相談した結果、 Serverless Application Model(SAM) を採用しました。 SAMはAWSがオープンソースで提供しているサーバレスアプリケーションを構築するためのフレームワークです。SAMの特徴は以下の点です。 SAMはCFnの拡張で、CFnに似たフォーマットなので学習コストが低い Localで開発する際に便利な揃っている環境でLambdaのテストが可能 Serverless関連のリソースのデプロイが簡単になる さらにSAMは SAM CLI が提供されています。変更セットの進行状況や変更セットのdiffが出力されるので、CIを組み込む場合でも親和性は高そうだなと思いました。 CloudFormationとServerless Application Modelの比較 具体的にCFnとSAMの相違点をまとめると下記の表で示す相違点があります。 項目 CloudFormation Serverless Application Model デプロイ aws cliを用いてデプロイできるが複数コマンドを組み合わせる必要がある sam deployコマンドを用いてデプロイできる 変更セットの確認 AWSのコンソール上からしか確認できない CLI上に変更セットが出力される テスト AWSのリソース上に上げないと確認できない Local環境でテストできる Lambdaのコード管理 CFnに埋め込む or S3で管理(ユーザがversioningする必要有り) S3で管理(versionigはSAM側で行ってくれる) 上記の表の比較から、Lambdaで扱うコードの管理をよしなに行ってくれ、Localで開発する際に便利な機能が揃っているSAMを採用しました。 まとめ 数ヶ月運用してみたのですが、毎日見るチャンネルに使用料金が通知されるようになったので、朝会のタイミングでAWSの料金について話す機会が生まれチームの人がAWSの料金に気を配るようになりました。また、AWSのCost Explorerを閲覧する権限を持っていない人でも確認できるので余分な権限を渡す必要もありません。 このタスクを通してMA内で使われているAWSの料金を知ることができ、その後の開発の際に使われるリソースの料金体系を気にするようになりました。また、Infrastructure as codeを行う事で他のAWS環境へ導入する際に反映する時間が短くなり、かつコードとしてインフラの構成を残しておけるのでInfrastructure as codeはアプリケーションを開発する上で必要であると感じました。 最終的にこのBotは間接的ですが会社で運用しているサービスの無駄なコストを省けるようになり、その分キャンペーンや企画にコストをかけられ、ユーザーにより質の良いサービスを提供できたと思います。 今回採用したSAMはまだまだ開発途上です。最近はAWS Step Functionsに対応しました!今後のアップデートに期待したいです。 最後に ZOZOテクノロジーズではより良いサービスを提供するための基盤作りを開発したい仲間を募集中です。以下のリンクからご応募ください。 tech.zozo.com
こんにちは、基幹システム部サポートチームの西山です。 私の所属するサポートチームでは、主にZOZOTOWNの注文の返品・返金のシステムや、カスタマーサポートの使用するバックオフィスシステムの開発に従事しております。 今回はECサイトの不正対策として、ZOZOではどういった観点で対策をしているのか、不正検知までどういったフローを踏んでいるのか、一部だけですがご紹介できればと思います。 はじめに 一般的なECサイトの不正利用ってこういうものがよく言われていますよね。 なりすましによるクレジットカード不正 集合住宅の空室やレンタルオフィスでの不正な受け取り 海外転送サービスの利用 その中でも、なりすましによるクレジットカード不正に関しては、特に不正の代表的な事例だと思います。 ZOZOTOWNも例外ではなくて、当然対策をしないといけません。 どのような対策をすれば良い? ほとんどのECサイトではクレジットカード決済時に、セキュリティコードの導入による対策をとられているのではないでしょうか。 セキュリティコードとは、クレジットカードの裏面に記載されている3~4桁の番号のことを言い、クレジットカード番号とは別の数字ですので、カード所有者にしかわからない情報です。 ECサイトでの決済時に、このセキュリティコードも入力してもらうことで、クレジットカード情報をスキミングなどで取得されたとしても、不正利用をしにくくする対策です。 このような対策を決済時に行うのはもちろんですが、うまく決済をすり抜けて発送までされてしまうケースがあります。 クレジットカードの不正利用をされたお客様は、見覚えのない請求があるということでクレジットカード会社へ返金を求めます。 クレジットカード会社より、ECサイト側にチャージバック請求がくるので、大量に不正注文が発生してしまうとECサイト側は大きな損失を負ってしまうことになります。 チャージバックとは、クレジットカード利用者が不正利用などの理由により利用代金の決済に同意しない場合に、クレジットカード会社が加盟店に対して支払いを拒絶することを言います。 このような損失を防ぐには、決済時の対策に加えて受注後の注文に対しても不正を検知することで、うまく決済をすり抜けてきた不正注文をあぶり出し、未然にチャージバックを防止する必要があります。 どうやって検知するの? ZOZOTOWNでも様々な不正の対策はしていますが、そのうちの1つとして独自の不正検知システムを利用しており、受けた注文の不正度合いを判定し、発送される前に対策を打つという使い方をしています。 不正度合いを判定する上で、こんな観点で見るといいよねというところを少し挙げてみます。 普通のお客様に迷惑をかけない これは一番大事なことかもしれません。 雑な条件で、一般のお客様の注文まで不正判定してしまうと、無駄に正常なお客様への確認や配送停止処理が発生してしまい、一般のお客様のショッピングを邪魔してしまうことになってしまいます。 逆に条件を絞りすぎた判定にしてしまうと、その条件に該当する不正だけは防げますが、本来防ぎたかった不正注文を広く防げなくなるため、バランスの見極めが非常に難しい部分でもあります。 不正ユーザーの気持ちになってみる 不正注文したことないのでどんな気持ちか実際はわかりませんが・・ 不正ユーザーはどういう商品をほしがるのか、どういう風にクレジットカード情報を入手するかなどを考えてみます。 イメージした不正手法をもとに、「ではそういう不正を防止するには、どういう判定条件や機能があればいいか」ということを考え、判定条件の土台を作ります。 顧客情報から推測する 過去の不正注文から、氏名、年齢、住所、電話番号などの顧客情報から不正ユーザーの法則を探し出したりもします。 注文特性から推測 注文金額、注文頻度、注文商品、配送先などの特性を掛け合わせて不正度合いを見極めます。 アクセスログから推察する 不正利用ユーザーがグループ犯罪であるケースもありえます。 最新の不正情報の傾向を把握する 不正対策の方向性を検討するのにセキュリティ系のサイトを参考にしています。 また、カスタマーサービス部門からも情報を吸い上げつつ、ZOZOTOWNならではな不正判定の視点での判定ロジックも組んでいたりします。 条件の作成までの過程 カスタマーサポート(CS)から不正判定の相談が来ます。 そこで、CS担当者とコミュニケーションをとりながら条件を決めていきます。 何かのサービスが始まるタイミングだったり、日々の運用の中で追加したい条件が発生したタイミングなどで都度相談が来る感じですね。 私が不正判定の条件作成時に気を付けていることとしては、こんなところでしょうか。 提示された条件で作成した判定処理のパフォーマンス 裏側のデータを知っているからこそ見える不正判定の切り口がないか 対象件数が数百件出るような場合は条件を疑う(誤検知を疑う) すでに設定されている判定条件に似たようなものがないか このようにパフォーマンス対策や、過剰検知にならないように担当者と調整して作成された条件を不正検知システムに仕込んでいくわけなのですが、以下のようなフローをたどって検知されていきます。 検知された注文は、不正検知システム上で管理されます。 CSによる確認を行いますが、一発で不正と判断できるものは当然、キャンセルという流れになっていきます。 一次判定では判別つかないものは保留になり、その後の判定で発送orキャンセルというフローです。 不正判断されたユーザー:不正確定、キャンセル対象 通常判定ユーザー:商品発送へ 不正の疑いがあると判断されたユーザー:怪しいけどセーフライン、確認のため保留 日々運用はアップデートしていきますが、基本的にはこういったフローが一般的なものです。 どれくらい効果があるの? 今年度の不正件数は昨年に比べて二倍強くらい増えてはいるのですが、その分防止もできていて、不正件数の精度としては年々向上しています。 精度の向上の裏には、エンジニアだけではなくZOZOTOWNの分析チームの協力もあり、より専門的で高度な観点から条件を作成できてきているという背景もあります。 課題もあります 毎年の検知件数はどんどん増えていますが、その中には実際は通常の購入であったのに検知された、誤検知もあります。 やはり、そういった誤検知によってお客様に対して不快な思いをさせてしまうこともあり、そこはZOZOTOWNに対して悪い印象を持たれてしまうことにもなってしまうためケアしていきたい部分です。 お客様だけでなく、CSの心理的負担や作業負荷にも繋がってしまうため、しっかり改善をしていかないとダメですね。 課題解決にむけて 現在は、より専門的な視点が必要と判断し分析チームに協力してもらっています。 そのおかげで、2020年度の不正防止率は向上し、だいぶ成果が出ている状況です。 さらに今後の取り組みとして、より高度な精度を求めるために、機械学習を用いて不正の疑いをスコアリングし、不正の疑いが高い注文に対して自動検知していくといった新しい検知方法の取り組みを進めています。 これが実現すると、不正検知の精度が向上して誤検知を防ぎ、お客様に気持ちの良いショッピングを楽しんでもらえます。 また、CSの負担軽減にも繋がっていきますね。 おわりに ZOZOTOWNのプロダクトで導入されている不正対策ということで、あまり詳しい情報まで提供はできないのですが、ZOZOTOWNとしての不正対策の観点や基本となる検知フローなどおわかりいただけたでしょうか? 少しでも参考になることがあればうれしいです。 このように、私たちサポートチームでは日々現場の業務改善、生産性UPを目標に取り組んでおります。ZOZOTOWNならではのスピード感があるので、相談を受けてから決まるまでが早いです。 また、いかに早く判定を開始できるかも重要なので、判定パターンのアップデートが容易にできるような作りにしています。どの部署も協力的なマインドを持っているからこそ実現できていると感じています。 ZOZOテクノロジーズでは、今回紹介したような裏側の仕組み作りや、運営する様々なサービスを一緒に作り上げていける方を募集しています。ご興味のある方は、以下のリンクからぜひご応募ください。 tech.zozo.com
こんにちは、基幹システム部USEDチームの柳瀬です。現在は主にZOZOUSEDで取り扱う商品の価格算出に関するシステムの開発・運用を中心に担当しています。 先日、とある案件でAmazon Aurora上のPostgreSQLに新規でのテーブル作成を伴う機能を開発する機会がありました。そのテーブルは3億件ほどのレコードを格納し、高頻度の参照および日次でのデータ追加が行われるものでした。 大量データを扱ううえでクリアすべき点として「処理速度など性能面での問題が発生しない事」があります。実装にあたってこの問題をクリアするために工夫した際に、パーティションテーブルを活用する事で解決する事ができましたので、その時の経験談をお伝えしたいと思います。 なお、今回の記事ではPostgreSQLを対象に説明しております。パーティションテーブルが実装されている他のRDBMSに関してもある程度は参考にできるかと思いますので、ご自身で担当されておりますシステムのDBに置き換えて読み進めていただければと思います。 そもそもなぜPostgreSQLを採用したのか 今回紹介する機能で使っているデータベース(以下DB)ですが、リリース当初はAWS上のMariaDBで稼働していました。しかしながら、その直後の追加改修においてwindow関数を使用したい場面が出てきました。 当時AWSで利用可能だったMariaDBのバージョン(10.2)においてwindow関数の動作を確認したところ、こちらが期待した通りの動作をせず、実用に耐えうるものでない事が判明しました。 window関数を使用しない方法で実装しようと思えばそれ自体は可能です。ただ今回は処理が複雑化して工数が大幅に増加する事が予見できたため、分析・集計系の処理に強いとされるPostgreSQLへ切り替えた方が速いという判断になりました。 やろうとした事 そもそも今回のテーブルは、別のチームが別のDBで運用していたものを巻き取る形で追加する事になったテーブルでした。そこで、移行するにあたって現状を確認したところ、データが増えすぎたせいでインデックスの追加が1日で終わらなくなっていた事が判明しました。 今回のテーブルでも同じような事が起こったら、移行する意味がありません。上位のインスタンスに置き換えてDBの性能を上げる事は費用の関係で難しかったため、テーブルの構成を工夫する必要に迫られました。 そこで採用したのがパーティションテーブルです。 外からは1つの大きなテーブルに見えているのですが、実際にはより小さなテーブルにデータが分けて入れられています。そのためアプリケーション側から見た場合、テーブルが分けられている事を意識させないような構造になっていると言えます。 利点としてあげられるのは処理の高速化です。 通常の検索・更新処理については参照する物理領域を減らせる事で速くなる場合があります。またバッチ処理で大量にデータを追加・削除する場合も、パーティション自体を操作すれば時間をかけずに実施する事が可能です。 パーティションテーブルの作り方 それでは作ってみましょう。まずは「親テーブル」を以下のSQLで作成します。 CREATE TABLE sample_table ( id serial, partition_column int, -- 中略 ) PARTITION BY RANGE (partition_column); カラム定義の括弧を閉じるところまでは普通のテーブルを作るのと同じで、括弧を閉じた後にPARTITION BYでパーティションの種類と対象の列を指定するのが普通のテーブルと異なるところです。 指定の仕方には範囲パーティション、リストパーティション、ハッシュパーティション(PostgreSQL 11以降)の3通りの指定方法がありますが、今回は範囲パーティションで説明を進めます。 次に、親テーブルに紐づけるパーティション(以下「子テーブル」)を以下のSQLで作成します。 CREATE TABLE sample_table_partition_1 PARTITION OF sample_table FOR VALUES FROM ( 0 ) TO ( 1000000 ); テーブル名のすぐ後にPARTITION OFで親テーブルを指定し、FOR VALUESで分割する値の範囲を指定します。この指定の仕方でセットされる値は 0 <= partition_column < 999999 になります。TOで指定した値より1小さくなる事に留意してください。 ちなみにパーティションに対するインデックスの設定ですが、自力で全ての子テーブルに作成しています。これは検証時のAurora PostgreSQL最新バージョン(10)にて親テーブルへインデックスが追加できなかったためです。 なお、PostgreSQL 11からは親テーブルへの指定で自動的に全ての子テーブルに反映させる事ができるようになっております。AuroraでもPostgreSQL 11が利用可能です。 この辺りは機会があれば改めて検証してみたいと思います。 インデックス作成時間の比較 上記のような手順で3億件のデータを10分割したパーティションテーブルに対し、PARTITION BYで指定したカラムに対するインデックスを作成してそれにかかった時間を計測してみました。 また、比較のため全く同一のデータが入った単一のテーブルを用意し、そのテーブルに対しても同様のインデックスを作成して比較してみました。 以下がその結果です。 単一テーブル:396.819秒 10分割パーティション:39.729秒(1パーティションあたり) パーティションテーブルの場合、単一のテーブルと比較して1/10の時間で作成できている事がわかります。物理テーブル1個あたりのデータ量が単一テーブルと比較して1/10になっていますのでほぼ想定通りです。 また、各パーティションのインデックスは並列で作成・再構築する事が可能なため、並列処理する事で単一テーブルに対するインデックス作成よりも短時間で作業を完了する事ができました。 また、作成したインデックスのサイズは、1パーティションあたりのサイズが単一テーブルのインデックスと比較して1/10になっていました。サイズが小さいほどINSERTや再構築にかかる時間も短縮されますので、より高速に動作する事が期待でき、単一のテーブルで作成するよりも運用面での負担が少ないと言えます。 実際にリリースしてから今日まで、大きなトラブルは発生しておりません。また、特定の条件でSELECTした場合も高速になるようですが、こちらも今後改めて検証したいと思います。 まとめ 今回得られた知見は他のテーブルやDBでも応用が可能です。特に今後は大量データを活用する場面がさらに増えると想定されますので、それに呼応して活用の場面が増えるものと考えられます。 ZOZOテクノロジーズでは、共にサービスを作り上げてくれる仲間を募集中です。ご興味のある方は以下のページをご確認ください! tech.zozo.com
こんにちは。MLOpsチームリーダー兼プラットフォームSREチームリーダーの sonots です。今年の4月からZOZOTOWNリプレイスプロジェクトにも関わるようになりました。Zoomの背景画像を「進め!電波少年」にしてみても、チームの若者に伝わらないのが最近の悩みです。 今回の記事は、昨年度にタスクフォースとして発足したOSSポリシー策定委員会を代表して、今年の4月に弊社で策定したOSSポリシーについて紹介します。 OSSポリシー策定の背景と目的 弊社でもOSSを利用・貢献・公開しているメンバーが増えてきています。また、会社としても業界貢献、技術アピールの側面からOSS活動を奨励したいという想いがあります。 しかし、弊社にはOSSポリシーが存在しなかったため、相談を受けた際にCTO室が都度判断するという状況がしばらく続いていました。都度判断ではスケールしないため、「社員がOSS活動しやすいようにする」ことを目的として、CTO室からの依頼でOSSポリシー策定委員会が発足され、OSSポリシーを策定することとなりました。 代表を務めた私(そのっつ)個人としては、OSSポリシー策定にあたって以下のような想いを抱いていました。 OSS開発は公私混同しがちなので、いっそのことそれが許されるようにしたい 社内秘伝パッチをあてて、本家にPRを送らないような事例は、負債にもなる(バージョンアップのたびにパッチをあてなければならない)ので極力なくしたい 特に 1. は著作権法や就業規則に絡んでくるため、エンジニアだけではなく、法務、労務のメンバーにも委員会に入ってご協力頂きました。 就業規則の改定も行い、取締役会での承認を得ました。 他社事例 OSSポリシー策定にあたっては、事前に他社事例を調査し、以下を参考にさせて頂きました。 オープンソースビジネスに取り組む SI 企業のための企業ポリシー策定ガイドライン オープンソースソフトウェアポリシーをつくろう - クックパッド開発者ブログ サイボウズのオープンソースソフトウェアポリシーを紹介します - Cybozu Inside Out | サイボウズエンジニアのブログ 特に サイボウズのOSSポリシー は、私の思想に近いものがあり、今回おおいに参考にさせて頂きました。この場を借りてお礼申し上げます。 著作権の帰属と職務規定 上述した私の想いである「1. OSS開発は公私混同しがちなので、いっそのことそれが許されるようにしたい」という課題をクリアするためには、いくつかの壁がありました。 第一に、弊社の就業規則に以下のような記述がありました。 職務著作 (1) 従業員が職務上作成する著作物の著作権(著作権法第27条、第28条所定の権利を含む。)は、会社に帰属する。 この規則により業務時間中に、入社以前から開発しているOSSをメンテナンスする場合や、他者OSSに送付するパッチを作成する場合でも、著作権が会社に帰属することになり非常に面倒です。弊社はフルフレックス制度を採用しているため、一度退勤してメンテナンス業を行い、終わったら出勤することでこの規則を躱すことはできますが、毎回やりたいことではありません。そこで取締役会にて相談し「OSS活動を行ったことによって作成した著作物については、別途定めるOSSポリシーに基づき、その著作権の帰属先を判断する」という一文の追加を許可して頂きました。OSSポリシーについては後述します。 第二に、就業規則に以下のような記述があり、OSS活動は職務に入るのか否かが議論としてあがりました。 職務専念義務 (1) 勤務中は職務に専念すること この点については、取締役会で「OSS活動は職務として認める」と決定して頂きました。 これらの結果、弊社では業務の一貫であるが個人の著作物としてのOSS活動を行うことができるようになりました。 本OSSポリシーの概要とポイント 今回作成したOSSポリシーは CC0 ライセンスで GitHub上 にて公開しています。また、社内にはOSS公開ガイドライン、OSSコントリビューションガイドライン、OSS利用ガイドラインを別途用意してあり、こちらのドキュメントも今後可能であれば公開していきたいと考えています。 本OSSポリシーは「社員がOSS活動しやすいようにする」ことを全面に押し出したポリシーになったと思います。本OSSポリシーには、以下のような特徴があります。 業務時間中であっても指示なく自発的に作ったソフトウェアは個人のものにできる(例えば.emacsや.vimrcなどを指していますが、それに限りません) 業務時間中に指示があって書いたソフトウェアでも著作権譲渡申請の許諾によって個人のものにできる 従業員が自己の所有するOSSプロダクトに対して自己が業務で作成した著作物を取り込む場合、著作権譲渡申請がなくても個人の著作物にできる パッチについても同様に、自発的に書いたOSSパッチは個人の著作物とすることができます。上長の明示的な指示のもと書かれたパッチにおいても、著作物譲渡申請を行うことで個人の著作物とすることができます。 ガイドラインおよびポリシーに準拠している限りは、OSS公開時に許可を得る必要はありません ガイドラインおよびポリシーに準拠している限りは、OSSコントリビューション時に許可を得る必要はありません CLA (Contribution License Agreement) 同意を求められた場合も、許可を得ずに当社を代表して署名できます おわりに 今年の4月に策定した ZOZOテクノロジーズ オープンソースソフトウェアポリシー について紹介しました。これにより非常にOSS活動しやすい組織になったと自負しています。 OSSポリシー策定にあたり、社内で委員会を発足し、関係各所のご協力をいただきました。委員会のメンバーに御礼申し上げます。特に takanamito にはOSS公開ガイドラインも書いて頂き大変助かりました。 ZOZOテクノロジーズではOSS活動を奨励しています。OSS活動により会社および業界に良い影響を与えてくれるメンバーを絶賛募集中です。ご興味のある方は、以下のリンクからぜひご応募ください! tech.zozo.com
はじめに こんにちは。ZOZOTOWN部サービスグロースチームでアナリティクスをしている井ノ口です。 この記事ではBigQueryで使える、ユーザー定義関数(UDF)という便利な武器をご紹介します。「UDFって何?」「何のために使うの?」という方に向けた記事のため、高度な分析などはこの記事では扱いません。 UDFとは UDFとは、最初から用意されているSUMやCOUNTのような関数を、ユーザー自身が定義する関数です。 私のチームではGoogle Analyticsのデータから、ユーザーが閲覧したページを分類したり、日時を計算したりする際などにUDFを用いてます。利用法など詳細は公式のドキュメントに記述されているため、そちらをご参照ください。 参考: 標準 SQL ユーザー定義関数 | BigQuery | Google Cloud UDFを使うメリット 使い方によって様々なメリットを受けられますが、特に大きいと感じるメリットは以下の3つです。 1. コーディングのコストを低減できる UDFは一度処理を記述するとUDFを呼び出すだけで処理を実行できるため、同じコードを繰り返し記述する必要がありません。UDFをチームで共有できるなら、時には自身が処理を記述する必要すらなくなるため、コーディングにかかる時間・労力のコストを低減できます。 また、作成したコードについてレビューを依頼する機会があれば、相手は既存のUDFについてレビューする必要がなくなるため、レビューのコスト低減にも繋がります。 2. コードを理解しやすくできる 処理が複雑で長いものであるほど、処理がどのようなものかを考える、あるいは理解するために大きなコストを要します。そこで処理の記述にUDFを用いると、コード中の複雑あるいは長い処理であっても役割として理解しやすくなります。UDFを用いた処理が複数回繰り返されるなら、それらが同じ役割であるとも容易に理解できます。 3. バグ混入のリスクを低減できる UDFは自身以外が作ったものでも使い回せます。 一度バグがないか検証されたUDFを利用すると、新たに処理を記述してバグが混入するケースを避けられます。たとえUDFにバグが含まれていたとしても、UDFの中身を修正すれば全ての処理が修正されます。 例えばコピー&ペーストで同じ処理を複数箇所に記述していた場合、該当箇所を全て修正する手間や修正漏れによるバグ混入の恐れがあります。 デメリットとしてはUDFは結合条件など一部では使えなかったり、UDFの中でWITH句の内容が使えなかったりします。ただし、結合条件の代わりにWHERE句で指定したりWITH句の内容をUDFで宣言できるケースがあるなど、工夫でなんとかなることもあります。 UDFを使用したクエリ例 上記のようなメリットがあるため、サービスグロースチームでは以下の用途などで使用します。 IDの管理 覚えるのが大変なため、UDFを使用しないとバグを混入させたり見逃したりしがちです。 条件の管理 ページや流入元を分類・判定する正規表現などを扱うことが多いです。条件は取りたい値が、なんらかの事情で変更されることが少なくないため、UDFを用いると条件の確認や変更に重宝します。 日時の処理 頻繁に使うものの直感的に記述・理解しづらかったり、記述が長くなりがちな処理なため、UDFが活躍する場面です。 実際にこのようなUDFを使用したクエリの例をご紹介します。 ZOZOTOWNの仕様に合わせた独自の条件などが含まれるため、一部をマスクしてあります。このクエリでは、セッションのテーブルから閲覧した日付、ページカテゴリごとにUUを求め、ページカテゴリが”その他”のものだけ除外します。 ------------------------------------ユーザー定義関数------------------------------------ -- visitStartTimeを日本時間の日付に変更する関数 CREATE TEMPORARY FUNCTION DATE_BY_VISITSTARTTIME(visitStartTime INT64) AS ( DATE (TIMESTAMP_SECONDS(visitStartTime), " Asia/Tokyo " )) ); -- ページに対応するIDを返す関数 --★ 複数回使っているUDFだが、この処理を変更すると全ての処理に変更が適応される CREATE TEMPORARY FUNCTION PAGECATEGORY_ID_BY_PAGECATEGORY_NAME(pageCategoryName STRING) AS ( CASE pageCategoryName WHEN " トップ " THEN 1 WHEN " 検索結果 " THEN 2 WHEN " 商品詳細 " THEN 3 WHEN " その他 " THEN 4 END ); -- ページを判定し対応するページIDを返す関数 CREATE TEMPORARY FUNCTION CATEGORIZE_WEB_PAGECATEGORY_ID(pagePath STRING) AS ( CASE WHEN REGEXP_CONTAINS(pagePath, r " [トップのページパスに該当する正規表現] " ) THEN PAGECATEGORY_ID_BY_PAGECATEGORY_NAME( " トップ " ) WHEN REGEXP_CONTAINS(pagePath, r " [検索結果のページパスに該当する正規表現] " ) THEN PAGECATEGORY_ID_BY_PAGECATEGORY_NAME( " 検索結果 " ) WHEN REGEXP_CONTAINS(pagePath, r " [商品詳細のページパスに該当する正規表現] " ) THEN PAGECATEGORY_ID_BY_PAGECATEGORY_NAME( " 商品詳細 " ) ELSE PAGECATEGORY_ID_BY_PAGECATEGORY_NAME( " その他 " ) END ); --★ ここまではチーム内で共通して使っているUDFなので、レビュー不要 SELECT DATE_BY_VISITSTARTTIME(visitStartTime) AS visitDate --★ 日時の変換処理が直感的でバグが混入しづらい , CATEGORIZE_WEB_PAGECATEGORY_ID(hits.page.pagePath) AS pageCategoryId --★ 記述すると長くなる分類処理が短く済む , COUNT ( DISTINCT fullVisitorId) AS UU FROM `[セッション情報からなるテーブル]`, UNNEST(hits) AS hits WHERE pageCategoryId <> PAGECATEGORY_ID_BY_PAGECATEGORY_NAME( " その他 " ) --★ <> 4 だと4の意味がわからない、誤りがあっても気づけない GROUP BY visitDate , pageCategoryId クエリ全体で見ると行数が多く感じるかもしれませんが、書く・読む作業が発生するのはほぼSELECT文のみで行数は少ないです。UDF部分はすでに用意してあるものを使っているため、書く・読む手間はほぼありません。 今回はUDFを一時的なUDFとして扱っていますが、永続的なUDFとして予め用意していると記述する必要もなくなります。 参考: 標準 SQL ユーザー定義関数 | BigQuery | Google Cloud このクエリでは日時の処理、条件の管理、IDの管理にあたるUDFをそれぞれ使っています。UDFの使用によりSELECT文中の記述量は減り、馴れもありますが処理の内容を理解しやすくなっているかと思います。 また、クエリ例の中ではIDの管理にあたる PAGECATEGORY_ID_BY_PAGECATEGORY_NAME() は別のUDFやWHERE句で複数回使われており、条件を変更したい場合にはこのUDF一箇所で済みます。 加えてコード中にIDの数値をそのまま書いていると、そのIDが何を指し正しいのかどうかが理解しづらいですが、IDを管理するUDFによって理解しやすくなっています。このようなマジックナンバーを避ける働きもできます。 UDFの例 最後に、このクエリに含まれていないUDFも含め、処理の説明を併せた例を紹介します。 セッション固有のIDを返す CREATE TEMPORARY FUNCTION SESSION_ID_BY_FULLVISITORID_VISITID (fullVisitorId STRING, visitId INT64) AS ( CONCAT (fullVisitorId, " - " , CAST (visitId AS STRING)) ); fullVisitorIdとvisitIdをハイフンでつなぎ、セッション固有のIDを生成します。セッションを比較し、同じものかどうかを判定する際などに用います。 参考: BigQuery Export のスキーマ - アナリティクス ヘルプ visitStartTimeを日本時間のTIMESTAMP型に変更する CREATE TEMPORARY FUNCTION TIMESTAMPDT_BY_VISITSTARTTIME (visitStartTime INT64) AS ( CAST (DATETIME(TIMESTAMP_SECONDS(visitStartTime), ' Asia/Tokyo ' ) AS TIMESTAMP) ); visitStartTimeを日本時間のTIMESTAMP型に変換する関数です。セッションが始まった日時を把握するために用います。 参考: BigQuery Export のスキーマ - アナリティクス ヘルプ yyyymmdd型の日付をDATE型に変換する CREATE TEMPORARY FUNCTION DATE_BY_yyyymmdd(yyyymmdd STRING) AS ( DATE ( CAST ( SUBSTR (yyyymmdd, 1 , 4 ) AS INT64), CAST ( SUBSTR (yyyymmdd, 5 , 2 ) AS INT64), CAST ( SUBSTR (yyyymmdd, 7 , 2 ) AS INT64)) ); "20200202"(2020年2月2日)のようにyyyymmddの形で書かれた日付をDATE型へ変換する関数です。 ワイルドカードテーブルを使用する際に、対象テーブルを指定するためにyyyymmddの形で日付を指定します。このyyyymmddを、DATE型でも使いたい際に用います。 例えば、他のテーブルとジョインしたいが、そのテーブルが持つ日時はyyyymmddでなくDATE型のため同じ形に合わせる必要があるというときに使用します。 参考: ワイルドカード テーブルを使用した複数テーブルに対するクエリ | BigQuery | Google Cloud yyyymmdd型の日付をx日前にずらす CREATE TEMPORARY FUNCTION DATE_SUB_yyyymmdd(yyyymmdd STRING, sub_date INT64) AS ( REPLACE (SAFE_CAST(DATE_SUB( DATE ( CAST ( SUBSTR (yyyymmdd, 1 , 4 ) AS INT64), CAST ( SUBSTR (yyyymmdd, 5 , 2 ) AS INT64), CAST ( SUBSTR (yyyymmdd, 7 , 2 ) AS INT64)), INTERVAL sub_date DAY) AS STRING), " - " , "" ) ); "20200202"(2020年2月2日)のようにyyyymmddの形で書かれた日付をSUB_DATE日前へずらす関数です。yyyymmddに加え、数日前のテーブルもデータ抽出の対象とする際などで用います。 yyyymmdd型の日付をx日後にずらす CREATE TEMPORARY FUNCTION DATE_ADD_yyyymmdd(yyyymmdd STRING, add_date INT64) AS ( REPLACE (SAFE_CAST(DATE_ADD( DATE ( CAST ( SUBSTR (yyyymmdd, 1 , 4 ) AS INT64), CAST ( SUBSTR (yyyymmdd, 5 , 2 ) AS INT64), CAST ( SUBSTR (yyyymmdd, 7 , 2 ) AS INT64)), INTERVAL add_date DAY) AS STRING), " - " , "" ) ); ひとつ上の関数の後ろにずらすバージョンです。 おわりに UDFについてイメージがついたでしょうか? コピー&ペーストなどで繰り返している処理をUDFに置き換えるだけでも、コードは書きやすく、読みやすくなるはずです。ぜひ簡単なところからでもお試しください。 ZOZOテクノロジーズでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 tech.zozo.com
こんにちは。技術開発本部SRE部の渡邉です。 リモートワークによる運動不足を解消するために毎朝ロードバイクで走る事を始めたところ、今では印旛沼 1 のまわりを走るのが生きがいになりました。 そんな私ですが2019年に入社して以降、現在に至るまで、ZOZOTOWNやWEARのインフラを担当しております。 前職からずっとインフラ周りの仕事をしておりますが常に頭を悩ませてきたのは監視の設計と構築というテーマでした。長年稼働しているプロダクトならば、まず抱えている問題のひとつにあがるのではないでしょうか。私たちが日々運用しているZOZOTOWNでも同様に監視システムで課題を持っていました。 本記事ではリプレイスプロジェクトが進むZOZOTOWNのインフラの可視化を向上させる目的でSplunkを導入し、得られたメリットについて紹介したいと思います。 背景 2020年現在、ZOZOTOWNは劇的な変化を遂げており、サービス開始当初から稼働しているオンプレミスとマイクロサービス化が進んでいるパブリッククラウドのハイブリッドな構成になっています。 リプレイスが進むにつれて、必然的に従来の監視システムだけでは成り立たなくなります。 また、移行を推進していく過程における副産物として監視システム自体のサイロ化を生みやすくもなるため、見直しを図る時期に差しかかっていました。 そこで、様々な機器からログを収集することで障害検知までの速度改善が見込め、今後の一元管理も可能となるSplunkの導入を決定しました。 Splunkの基礎知識 私自身、今までにSplunkを使用した経験はありませんでしたが、度々Splunkに関する説明を受けていて、以下の点が私たちにとってメリットがあると感じていました。 クラウド版を選択することで、インフラの設計を待たずに導入が可能であること 様々なAdd-onの提供が Splunkbase でされており、可視化する際に特別な工夫をしなくても簡単にダッシュボードを作成できること 取り込んだログはSplunk側で適切なログフォーマットにあわせた成形をしてくれること Search Processing Launguage(SPL)について 基本的な情報については様々な記事で紹介されているため詳細は割愛しますが、Splunkを使うためには Search Processing Launguage(SPL) を理解する必要があります。 SPLはBashと似ている部分もあればSQLと似ている部分もあるので、慣れるには実際のログを入れて動かすことが手っ取り早いと思います。そのため、Splunk Free版を使うことで手元のログを所定の形式でインポートして実際に試すことができます。Free版なので様々な制約はあるものの大体の使用感を理解するのは充分です。 なかでも参考にしたのは以下のリファレンスです。 Splunk Fundamentals 1 は無償で受講可能となっているEラーニングですがとても充実しています。 Command quick reference Splunk Fundamentals 1 サーチの使い方 index =main sourcetype= " ms:iis:auto " host= " Server* " サーチ結果について、デフォルトでは イベント が表示されています。この結果をどのように表示させるか以下の4種類から選択可能です。 イベント パターン 統計情報 視覚エフェクト グラフィカルに結果を出力できる視覚エフェクトを一番多く使います。チャートやグラフなどあらかじめ複数用意されていますし、サーチ結果に合わせてエフェクトを推奨するパターンもあり、親切に作られています。 次のサーチ文は特定のUri_Pathのアクセス回数をホストごとにカウントした結果を出力する例です。このサーチ結果を単純に回数を表現する Single Value を使って出力してみます。 index =main sourcetype= " ms:iis:auto " host= " Server* " uri_path= " /men-category/tops/ " |stats count by host Single Value の表示を"トレリスレイアウト"で設定すると、それぞれのホストごとに集計した画面を作ることができました。 監視ダッシュボードを作る あらかじめ監視画面を作成しておくために、必要なサーチ結果を組み合わせてダッシュボードを用意します。 作成したダッシュボードはこんな感じです。 エラー発生件数 レスポンスタイム CPU使用率 今回はサーバで HTTP Status 500 が発生していた例を使って説明します。 エラーカウントの推移 まず、 HTTP Status 500 の発生推移を確認します。 特定のサーバに偏っている場合はエラー回数を単純にカウントして降順ソートで出せば完了です。 index =main sourcetype= " ms:iis:auto " host " =Server* " AND status= 500 | stats count by host | sort - count しかし、今回は時間軸と傾向を把握したいため、サーバグループごとに分けて抽出しています。 index =main sourcetype= " ms:iis:auto " AND (host=ServerA* OR host=ServerB*) AND status= 500 | eval servergroup= case ( like (host, " %ServerA% " ), " ServerGroup1 " , like (host, " %ServerB% " ), " ServerGroup2 " ) | timechart span=1m count as error_count by servergroup エラー発生状況のグラフを確認するとServerGroup1に偏っている状態がわかります。ServerGroup2側と比較すると、よりその差が明確になっています。 レスポンスタイムの推移 続いてレスポンスタイムの傾向も同様にサーバグループごとに抽出しています。 index =main sourcetype= " ms:iis:auto " AND (host=ServerA* OR host=ServerB*) | eval servergroup = case ( like (host, " %ServerA% " ), " ServerGroup1 " , like (host, " %ServerB% " ), " ServerGroup2 " ) | timechart span=1m avg (time_taken) as responsetime by servergroup レスポンスタイムの比較でもServerGroup1とServerGroup2に明らかな差が生じていました。 CPU使用率の推移 最後に mstats というメトリクスサーチでサーバのメトリクスデータからCPU使用率を確認しています。 | mstats avg (_value) prestats= true WHERE metric_name= " Processor.%_Processor_Time " AND " index " = " em_metrics " AND (host=ServerA* OR host=ServerB*) AND `sai_metrics_indexes` span=1m | eval servergroup = case ( like (host, " %ServerA% " ), " ServerGroup1 " , like (host, " %ServerB% " ), " ServerGroup2 " ) | timechart avg (_value) AS Avg span=1m by servergroup | fields - _span* 同じ時間帯にも関わらずServerGroup1のCPU使用率が100%で張り付いてしまっている状況でした。エラー発生数とレスポンスの悪化を引き起こしていた原因はCPUの高負荷状態にあったと言えます。 アクセス数の単純増加など、高負荷を引き起こす外的要因が明らかになっていれば、調査に要する時間はもっと短くなるためダッシュボードにアクセス数も組み込んでいくこともできます。 このあたりはログ分析とあわせてメトリクス監視の要素を組み込むこともできる自由度の高さもSplunkの特徴だと思います。 Splunk App for Stream Splunkには Splunk App for Stream というネットワークキャプチャも存在しています。 これによってWebサーバから外部APIへ接続する際のレスポンスを取得していく事が可能になるため、サーバ内外のレスポンスの変化を調べることも可能になります。 index =main sourcetype= " stream:http " site IN (*api*) host= " Server* " | rename " sum(time_taken) " as time_taken | eval response=(time_taken/ count / 1000 ) | timechart avg (response) span=1m as avg by site Streamで取得できるレスポンスタイムの値はマイクロ秒になっているため計算をミリ秒に合わせています。 サーバからAPIへの内部通信のレスポンスを確認できるのはかなり重宝します。 しかしながらIngestされるデータ量が爆増するため事前にSplunkの方と容量の増加について相談をしておいたほうが良いです。 Splunk App for Infrastructure すでにダッシュボードで使用していたメトリクスサーチという機能で機器のメトリクスデータを扱うことができるAppです。 以下の画面はサーバ単体のメトリクス概要になります。様々な項目を収集できていることがわかります。 ほかのサーチ文同様に複数のサーバを組み込むことができるのでちょっとした変化に気づきやすくなりますし、比較対象をわかりやすく表示したい場合に重宝します。 | mstats avg (_value) prestats= true WHERE metric_name= " Memory.Available_Bytes " AND " index " = " em_metrics " AND " host " = " ServerA01 " OR " host " = " ServerA02 " OR " host " = " ServerA03 " AND `sai_metrics_indexes` span=10s | timechart avg (_value) AS Avg span=10s by host[f:id:vasilyjp:20200713120129j:plain] | fields - _span* ここで使用している mstats は、ほかのサーチと比べて結果を高速で返すため、あまりストレスがかからずに調査できる点もメリットです。 Splunk導入の効果とまとめ Splunkを導入してから、アラート発生時にSplunkで調査するというアクションをチーム全員が自然と取るようになっています。得られたデータをすぐにダッシュボードで共有する流れになったことで、誰が、どういった問題を検知したのかを視覚的に理解しやすくなったといえます。その結果、以前と比較して、アラートに対する動きが格段に速くなったと感じています。 今回は紹介しきれませんでしたが、次の機会があればSplunkの機械学習を使った監視設定や 効率よいサーチの作り方 について紹介できたらと思います。 ZOZOテクノロジーズでは、一緒にサービスを作り上げてくれる仲間を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください! tech.zozo.com 印旛沼 ↩
こんにちは、ZOZOテクノロジーズ CTO室の池田( @ikenyal )です。 ZOZOテクノロジーズでは、6/27-28(開発2日間・発表は6/29)にアスクル・一休・PayPay・ヤフーと合同で社内ハッカソン「Internal Hack Day」を開催しました。これまでヤフーの社内で開催されてきたハッカソンイベントを、今回は初のZホールディングスの各社合同で実施することになりました。合わせて、新型コロナウイルス感染症対策として、初のオンライン開催でもありました。 techblog.yahoo.co.jp techblog.yahoo.co.jp そして、今回のHack DayではヤフーのHack Day史上で初めてテーマが設定されました。「新しい生活様式での課題解決」というテーマです。このHack Dayの開催の形式もそうであったように、新型コロナウイルス感染症の影響で我々の生活様式が新しいものに移り変わってきています。その中で出てくる課題を解決し、世の中の役に立つものづくりをしようという思いが込められています。 弊社ではこれまでにも開発合宿を企画したり、エンジニアがプロダクト開発以外でも技術を学べる場の提供を行っております。 techblog.zozo.com 新型コロナウイルス感染症の影響により、計画していた開発合宿もできなくなっており、なかなかこのような場を作り出すことができない状況にありました。そのような状況で、ヤフーからInternal Hack Dayのお誘いをいただきました。Zホールディングスの仲間として、絶好の機会を得ることができました。 普段であれば社内のエンジニアとしか交流する場がなかったりするので、他社のエンジニアと同じハッカソンに参加し発表を見る、場合によっては同じチームで開発する機会は貴重な場です。 今回、弊社からは3チーム(1チームはヤフー・PayPay・ZOZOテクノロジーズの合同チーム)が参加しました。そこで得られた知見や感想をダイジェストでお伝えします。 参加者の感想 以降、それぞれのチームの感想を紹介します。 地図周りの知見を深めたくてHack Dayに参加(@kurarararara) ZOZOテクノロジーズ、iOSエンジニアの @kurarararara です。自分達は二人チームで参加しました。 同じく同僚でiOSエンジニアの @koiwai2020 さんに、「地図を使ったアプリを作ってみたいので参加しませんか」と声をかけHack Dayに参加しました。たった二日の開発期間というのはエキサイティングかつ非常に緊張感のあるものでしたがとても良い経験になりました。 実際どういった流れで参加したのかダイジェストでお伝えします。 1日目 朝 普段から同じチームで開発しており、テーマの方針も決めていたので余裕あり。 Discordで談笑しながら、具体的にどんなUIでどんな動きにするか話し合う。 余裕があるので、SwiftFormatや、.gitignoreを指定したりなど環境整理に費やす。 昼 事前に使おうと思っていたヤフーのローカルサーチAPIを調査。 同時にMKMapView + Core Locationについて調べる。特に引っかかることなく、地図の表示や現在地などを取得することに成功。 マスコットキャラクターの作成に着手。@koiwai2020さんの書く牛が非常に味のある絵で感心する。 マスコットキャラクターの役割を示すため、レーダーのようなアニメーションを追加。 夕 構想していた機能を追加し終えて、ほぼアプリ完成。 「終わっちゃいましたね。後1日どうしましょう?」と談笑。 この時はプレゼンに耐えられるアプリやスライド作りの大変さに、まだ二人とも気がついていない…。 夜 落ち着いて改めてアプリを見直す。「このアプリ大丈夫かな?機能も足りないし、マスコットキャラクターも変更した方が良いのではないか?」と軽く議論になる。 とりあえず、マスコットキャラクターを描き直すことに決めて翌日へ。 2日目 朝 2日目を迎えて、改めてマスコットキャラクターを見てみると非常に愛らしく素晴らしいことに気づいて続投決定。 マスコットキャラクターをより際立たせるためにコメント機能を追加。 変化が伝わるようにコメントの吹き出しにCore Animationを使ってアニメーションを追加。 Core Locationを使って地名の検索機能を追加。 昼 ここで急に残り時間がもうないことに気づく。焦る。 さらに、プレゼンとスライドを作らねばならないことに気づく。余計に焦る。 90秒のプレゼン時間に対して、20ページ近いスライドを作成。リハーサルしてみるとやはり尺が足りないことに気づく。とても焦る。 夜 なんとかプレゼンをブラッシュアップし、90秒の枠に収まるように修正。 なんとかアプリがきちんと動くようになり、完成。 全工程完了。なんとかやり切ったと達成感に包まれる。 3日目(発表日) 今回はリモートということもあり、他の参加しているチームが何をしているのか全くわからなかったが、蓋を開けてみるとどのチームも非常にユニークで技術的にも凄い作品ばかりで感心する。 発表は@koiwai2020さんに託す。プレゼンは非常にわかりやすく、自分達のアプリの機能の全てを90秒でしっかり伝えていただいて感謝。 ただ、機材トラブルで実際にアプリが動いているところが映らないアクシデント発生。どんまいです笑。 最後に 色々ありましたが、二日間に一気に集中してアプリを開発することで、普段の学習よりも素早く濃密に知見を深められた気がします。 前々から地図アプリを作ってみたかったので、この知見を生かして近々何か一個作ってみたいと思います。 また何よりあーだこーだ談笑しながらアプリを作るのはとても楽しかったですね。再びこんな機会があるならば参加してみたいと思います。 新卒5人でHack Dayに挑戦(さっと) ZOZOテクノロジーズZOZOTOWN部・検索チームのさっとです。 私達は、20新卒のエンジニア4人とデザイナー1人の計5人でHack Dayに参加しました。2日間という非常に短い時間の中での開発だったため、時間に追われ大変でしたが、得るものが多い2日間となりました。 開発したサービス 私達は、「身しりとり」というサービスを作りました。 身しりとりは、ZoomやLINE通話などのビデオ通話アプリと画像認識・画像分類の技術を活用し、体を動かしながらジェスチャーで楽しく「しりとり」を繋いで遊べるサービスです。 外出自粛による、運動不足やコミュニケーション不足といった問題を解決できないか、という思いから開発しました。 開発での苦労話 身しりとりを実現するために、ユーザがどのようなポーズを取っているかリアルタイムで判断する必要がりました。 そのため、サーバ側の処理としては、ビデオアプリでキャプチャーした画像からユーザのポーズを抽出するためにユーザの骨格情報を使ってピクトグラム化する処理を行います。 次に、事前に深層学習を使って作成した分類器を用いて、ユーザがどのようなジェスチャーをしているか類推します。2日間で分類器を作り、さらにチューニングを行うことはとても大変でしたが誤検知率が低下し、かつリアルタイム性を実現できたので努力した甲斐がありました。 チーム内でやってよかった行動 開発に詰まったとき、作業画面を共有してペアプロを行うようにしていました。自分では気づかない間違いにすぐ気づくことができ、さらに、方向性を話しながら修正できるので効率的に開発を進めることができました。 最後に 残念ながら賞を取ることはできませんでしたが、新卒研修後あまり接触がなかった同期とわいわいと話しながら開発ができてとても楽しかったです。 それぞれ2日間の開発で知識や技術力不足といった課題を見つけることができました。1年後パワーアップしてまた望みたいと思います。 ヤフー・PayPay・ZOZOテクノロジーズ 合同チーム(松浦・むーさん) 3チーム目は、ヤフー・PayPay・ZOZOテクノロジーズの3社の社員が集まった合同チームです。こちらのチームの参加レポートは後日ヤフーのテックブログにて公開されています。 techblog.yahoo.co.jp 最後に ZOZOテクノロジーズでは、プロダクト開発以外にも、今回のようなHack Dayやイベントの開催など、積極的に技術に接する機会を用意しています。 一緒にサービスを作り上げてくれる方はもちろん、エンジニアの技術力向上やエンジニアの組織づくりにも興味のある方を募集中です。 ご興味のある方は、以下のリンクからぜひご応募ください! https://tech.zozo.com/recruit/ tech.zozo.com
こんにちは。ZOZOTOWN部の荒井です。 先日 WWDC20 が開催され、今年も弊社iOSメンバーが参加してきました。Apple Siliconや各次世代OSなど面白い発表が目白押しでしたね。 カンファレンスの内容も非常に興味深いものでしたが、今年は諸般の事情を鑑みて、初のオンライン開催となったことも印象的でした。 本記事ではWWDC20オンライン開催にあたり、ZOZOTOWN iOS担当のメンバーがどう臨んだのか、参加して感じた現地開催との相違点をお伝えします。また、Developer Labsに参加し、Appleのエンジニアと日頃疑問に思ってる点について話をしてきました。可能な範囲で内容を公開しますので、是非最後までご覧ください。 WWDC? WWDC(Worldwide Developer Conference)は、Appleが年に1度開催している開発者向けのカンファレンスです。ZOZOテクノロジーズでは海外カンファレンスへの参加が推奨・サポートされており、例年当選したメンバーが業務の一環として参加しています。現地の様子は昨年の参加レポートをご覧ください。 techblog.zozo.com オンライン開催にどう臨んだか 初のオンライン開催ということで事前情報が少なく、働き方も非常に悩ましいものでした。ここでは開催期間中の働き方、Developer Labs参加への事前準備について紹介します。 開催期間中の働き方 ZOZOTOWNのiOSチームは話し合った結果、以下のような働き方にしました。 現地時間に合わせた日本時間2:00 - 11:00での勤務 6月27日(土)を出社とし、6月22日(月)を休みに振り替え 各自自宅からの参加 Engineering Sessionsはいつでも視聴できることが想定されていましたが、Developer Labsへ参加することを中心に考えていたため、現地時間に合わせました。弊社はカンファレンスへの参加が推奨されており、フルフレックス制度といった働き方にも柔軟性があります。ただし深夜勤務は推奨されていないためWWDC期間中のみ例外的な対応をしました。なお、WWDC期間中の業務は事前に調整しています。オンライン開催に限らず、希望し当選したメンバーは業務として全員が参加予定でした。 Developer Labs参加への事前準備 Developer LabsはAppleのエンジニアやデザイナーに直接質問できる貴重な機会です。WWDCに参加する醍醐味でもあり、効率よく参加するために僕たちのチームでは事前準備を行っています。以下の内容をチーム全員で共有し管理しました。 質問内容 業務との関連性 ラボ名 参加者 英文 結果 この管理は例年行っていることですが、オンライン開催にあたり「1-on-1」「録音禁止」「英語のみ」といった開示があったため、今年はその点を考慮した内容となっています。結果10程度のラボに参加でき、有益な情報収集ができました。「英語のみ」については通訳係として当選者以外のエンジニアも参加できたので、言語面での不安も軽減できました。 当日になって質問を考えると漏れが発生したり、間に合わず申請できないといったことがあるため、今後Developer Labsへ参加を予定している方は事前準備をおすすめします。 開催期間中の動き 効率的な情報共有をするため、ルールを2つだけチームで決めて動きました。 Slackの専用チャンネルにてテキストでの情報共有 1日に1回通話での情報共有 基本的にコミュニケーションの場を整える目的で、日報のような目的ではありません。各々がEngineering Sessionsを観たり、Developer Labsへ参加したり、サンプルコードを書いたりと自由に行動していました。Engineering Sessionsは後日でも閲覧可能であると確認が取れたため、2日目以降はEngineering Sessionsの優先度を下げていたメンバーが多かったです。 現地開催との相違点 今回のオンライン開催は現地開催と比べてどうだったのでしょうか。参加メンバーが感じたことを環境、セッション、ラボの観点でオンライン開催のメリット・デメリットをお伝えします。 環境 はじめに、オンライン開催と現地開催でどのような環境面の差を感じたかを紹介します。 メリット 現地よりもネットワーク環境が良く、βのダウンロードや検証がスムーズ みんなオンライン参加なので、同じPDTでの活動がタイムラインに流れてきて、例年とにぎやかさが全然違う デメリット 深夜開催なので音声に配慮が必要 現地で見る方がモチベーションアップに繋がる 移動がなかったため、ラボでいきなり英語をしゃべる落差がある 他社のエンジニアと情報交換が難しい 物販がない 発表された技術の検証はオンライン開催の方がやりやすいという意見がありましたが、モチベーション面は現地開催の方があがるという意見が多かったです。 Engineering Sessions 次はEngineering Sessionsです。 メリット いつでも閲覧可能なので、何回も巻き戻したり、技術について話しあったりしてセッションが身近に感じられた 日本語・英語字幕に助けられた時もあった デメリット オンラインだとライブ感がなく、参加者が何に注目しているのか掴みにくい 当日に何回も巻き戻して確認できるのはオンラインならではのメリットです。ただし「拍手」などのリアクションがないため、盛り上がりポイントが分かりづらいという意見もありました。 Developer Labs 最後にDeveloper Labsについての相違点です。 メリット 予約制なのでAppleエンジニアも回答準備をしており、参考サイトの共有などもあった デメリット ラボに入り浸れない 混んでいるラボ、空いているラボが判断できず、予約状況も分からない 予約制なので手を動かしてもう一度聞きたい時にいけない オンラインではすべてのラボが予約制のため、ラボに対する自由度は少なくなりました。繰り返し行くということがしづらいため、限られた時間内で問題解決しなくてはいけないことがデメリットの意見として多かったです。 個人で作業する分にはオンラインの方が効率的な印象を受けました。ただ、やはり現地参加の方がモチベーションも上がりやすくエンジニアとのコミュニケーションも活発になるので、現地開催のメリットは大きそうですね。 Developer Labs ここからはDeveloper Labsで聞いてきた内容の一部を各メンバーが紹介します。Developer Labsでは業務に直結することを優先的に質問していますが、エンジニアがそれぞれ疑問に思った業務外のことも時間の許す限り質問しています。内容はAppleとのNDAのため、可能な範囲での紹介となります。 UICollectionViewとUITableViewのこれから こんにちは、ZOZOTOWN部の小松です。例年のWWDCはKeynoteしか見ていなかったのですが、今回はセッションやラボに参加し、初めてのWWDCの全参加となりました。 今回のセッションをみていく中で、とあることに気付きました。UICollectionViewに昨年同様新しい機能が追加されていて、その新機能追加により、UITableViewで実装していたレイアウトがUICollectionViewでも実装しやすくなるとのことです。 「そうすると、UITableViewとどのようにして使い分けの判断をするべきか?」その疑問をAppleのエンジニアにぶつけるべく、UIKit and Build for iPad labへ参加することに決めました。初めてのラボでしたが、Appleのエンジニアの方は大変優しく、質問に対してとても丁寧にお答えいただいたのが印象的です。 今回質問した内容は以下の3つです。 UICollectionViewとUITableViewのどちらを使用するべきなのか? UITableViewはDeplicatedになるのか? なぜ、既存でUITableViewがあるのに、UICollectionViewListを作ったのか? 結論として、もし既存のアプリとしてUITableViewを使用しているのであれば、そのままUITableViewを使用し続けることには問題ない。しかし、新しいアプリを作るのであれば、UICollectionViewListが推奨なようです。今後はおそらく、UICollectionViewには追加されるが、UITableViewには追加されないであろう機能が増えていくであると思われるそうです。UICollectionViewListを使用するメリットは、複雑なレイアウトを容易に組むことができるようになることだそうです。たとえば、App Storeのアプリを参考にみてもらうとわかりやすいとのことでした。 以上を踏まえると、現状UITableViewを使用し続けることに問題はないが、より簡単にそして最新の機能を使うためにも徐々にUICollectionViewへと移行していくのが良さそうです。ただ、UICollectionViewListはiOS 13以上対応なので、今すぐにとはいかないところが難しい点ですね。 Appleのエンジンニアにxcresulttoolの使い方を教えていただきました ZOZOTOWN部の林です。 Xcode11からxcresultファイルを解析できるxcresulttoolコマンドが推奨されています。 View and share test results ZOZOTOWNアプリでも今後テストを充実していくために、xcresulttoolを利用することが避けられないでしょう。xcresulttoolをもっと理解するために、Testing and Continuous Integration lab(25分)に申請を出して当選しました。 事前にサンプルアプリのUIテストを用意して、達成したいことを申請時に伝えておりました。xcresultからUIテストで撮ったスクリーンショットを取り出して、指定したフォルダに保存することを目標としました。そして、ラボ当日にAppleのエンジニアの指示を聞きながら、用意したxcresultの解析を行いました。 xcresulttoolでxcresultファイルから変換されたjsonファイルの確認方法、xcresultのツリー階層など、xcresulttoolについてドキュメントに記載されていない内容もたくさん教えていただきました。 情報を聞くだけではなく、コマンドの操作も教えていただいたので、欲しかった結果を得られた瞬間、一緒に仕事ができた気分になりました。自分にとって貴重な経験でした。 SideBarはハンバーガーメニューと違うのか? ZOZOTOWN部のえんどうです。 iPadのナビゲーションにSideBarが推奨されるようになりましたね。 しかし、WWDC 2014の「Designing Intuitive User Experiences」のセッションでiOSにはハンバーガーメニュー(a.k.a SideBar)は推奨しないとありました。ではなぜ、2020年では推奨されるようになったのでしょうか? そもそも、「ハンバーガーメニューとAppleのいうSideBarは違うものなのか?」ということが気になりラボで質問をしてきました。 結論として、SideBarとハンバーガーメニューは違うものという回答でした。その違いは「常に表示されているか」です。 ハンバーガーメニューは非表示のところからユーザーがハンバーガーメニューを表示しなければなりません、SideBarは常に表示されています。そのため、ユーザーはいつでもどこにいるのか見失うことはありません。 ナビゲーションでは、「何が見つけられるか」「どこにいるか」「どこにいけるか」が大切だと教えてもらいました。この考えは画面遷移を考える上でとても重要なことなので意識して気をつけていきたいと思いました。 Interface Builder and Auto Layout lab ZOZOTOWN部の名取です。 ZOZOTOWNではInterface Builderを多くの画面で使用しているため、特定のStoryBoardを複数人で開発する際のtipsを教えてもらいました。 複数のViewControllerが配置されたStoryBoardにおいてはXcodeのEditor→Refactor To StoryBoardを選択することで特定のViewControllerのみを切り出したStoryBoardを作成できます。 この機能によりコンフリクトの発生を幾ばくか抑えることができるため複数人で開発する際は推奨とのことでした。 また近年SwiftUIが大きな盛り上がりを見せていますが、Interface Builderが将来的になくなることはなくそれぞれが違う技術として共存していくだろうという話もされていました。 NotarizationとWatch FaceとXcode 11.4の静的リンクについてラボで聞いてきました ZOZOTOWN部自称macOS担当の @banjun です。今年は10年に1度のウニ(・∀・∀・)バーサルyearでしたね(注:PPC→Intel移行のときの流行語)。私は AquaSKK などのOSSのmacOSアプリをメンテしていることもあり、 Universal App Quick Start Program にも参加しています。 ラボでは「**アプリとCLIではNotarizationチェックの仕組みと検証方法が異なる**こと(= 具体例では[SwiftBeaker]( https://github.com/banjun/SwiftBeaker )を検証する方法)」「Watch FaceでサポートされているLive Photosのfps上限や、**Watch Face Sharingのシリアライズフォーマット**が公開仕様か」「Xcode 11.4で導入されている**静的リンクのビルド前チェックとApple Siliconとの関連**や今後のライブラリーの管理はどう変わっていくのか」などを聞いてきました。いずれの知見も次の開発に活かせそうです。 まとめ 今回はWWDC20の参加レポートをお伝えしました。オンラインは初の試みでしたが、チームとしては参加して成功だと感じています。リアルタイムで情報をキャッチアップすると共に、すでにZOZOTOWNではiOS 14への調査・対応も進めています。来年は現地でWWDCが開催されると良いですね。 さいごに ZOZOテクノロジーズでは、一緒にモダンなサービス作りをしてくれる方を募集しています。ご興味のある方は、以下のリンクからぜひご応募ください! https://tech.zozo.com/recruit/ tech.zozo.com
はじめまして、SRE部の高塚です。新卒として4月に入社し、チーム研修ではBEARというSlackアプリを作成しました。 technote.zozo.com BEARは社内システムとして正式に導入が決まり、準備期間として1か月半が用意されました。この記事では、その期間に行ったインフラの再構築について紹介します。 BEARとは 旧BEARが抱えていた問題点 コード化されていないインフラ 自動化されていないデプロイ Re:ゼロから始めるインフラ構築 インフラをコード化する デプロイを自動化する まとめ 最後に BEARとは まずはBEARについて簡単にご説明します。なおこの章は読み飛ばすことも可能です。 BEARは本のレビューをRDBで管理し、Slackアプリとして CRUD や検索が行えるシステムです。AIによる「あなたにおすすめの本」の推薦機能もあります。 弊社には 書籍購入補助制度 があり、これまではSlackチャンネルに各自が本のレビューを投稿していましたが、以下のような問題がありました。 検索しにくい 過去のレビューを遡りにくい 書籍名やAmazonの購入ページのリンクなど、入力項目が多い BEARはこれらの問題を解決したSlackアプリです。 繰り返しで恐縮ですが、詳細は以下のカンパニーブログをご覧ください。 technote.zozo.com かくして新卒研修の発表会のあと、BEARは正式な社内システムになることが決定したのですが、インフラを担当していた私は青ざめました。 なぜなら発表会で披露したデモアプリのインフラは、まったく本番運用を想定したものではなかったからです!(ハッカソンあるある?) 旧BEARが抱えていた問題点 デモ時点でのBEAR(以下、旧BEAR)は、2つのインフラの問題を抱えていました。 コード化されていないインフラ 旧BEARはAWSで稼働していますが、各リソースはマネジメントコンソールの画面から作っていたため、詳細な設定はインフラ担当の自分しか把握していませんでした。 もちろんドキュメント化を心がけてはいましたが、設定を変更したあとドキュメントの更新を忘れ、バックエンド担当に迷惑をかけたこともありました。変更前のレビューや変更履歴の管理もできていませんでした。 自動化されていないデプロイ 旧BEARはデプロイをすべて手動で行っていました。例えば基盤のAPIを更新する際は、社内VPNからEC2にSSHでログインし、 git pull や docker-compose up をしていました。毎回とても面倒くさいことに加え、オペレーションミスも多発しました。また、デプロイのたびにダウンタイムが発生していました。 Re:ゼロから始めるインフラ構築 これらの問題を解決するため、新BEARではインフラをゼロから作り直しました。参考程度にアーキテクチャ図もご紹介します。基盤のAPIはECSに変えることで、ダウンタイムなしのデプロイを可能にします。 また今までは私一人でインフラ周りの作業を行っていましたが、同じくSRE部の新卒・川津にも合流してもらい、インフラチームとして開発を進めました。 インフラをコード化する まず取り組んだのがインフラのコード化です。一般にはInfrastructure as Code(IaC)と呼ばれています。IaCのメリットを以下に挙げます。 GitHubが使える。つまりバージョン管理ができ、変更履歴も残り、レビューもできる 手順書や環境定義書が不要になり、それらに起因するヒューマンエラーを防げる 雛形を作っておくことで、様々なプロジェクトでそれを使い回せる AWSの場合はCloudFormationを使うことで、リソースの設定をYAML(またはJSON)で書くことができます。なおCloudFormation自体は無料で、実際に作ったリソースだけに料金がかかります。 例えばVPCにプライベートサブネットを作る場合は以下のようになります。 AWSTemplateFormatVersion : 2010-09-09 Resources : VPC : Type : AWS::EC2::VPC Properties : CidrBlock : 10.10.0.0/16 EnableDnsHostnames : true EnableDnsSupport : true Tags : - Key : Name Value : my-vpc PrivateSubnet1a : Type : AWS::EC2::Subnet Properties : VpcId : !Ref VPC #上のVPCを参照している AvailabilityZone : ap-northeast-1a CidrBlock : 10.10.10.0/24 Tags : - Key : Name Value : my-private-subnet-1a このファイルから実際にリソースを作るには、aws-cliの以下のコマンドを使用します。 # テンプレートの検証 aws cloudformation validate-template --template-body file://my-file.yml # 変更セットの作成 aws cloudformation create-change-set \ --change-set-type " UPDATE " \ #初回はCREATE --stack-name " my-stack " \ --change-set-name " my-stack-change-set " \ --template-body " file://my-file.yml " # 変更セットの実行 aws cloudformation execute-change-set --change-set-name " my-stack-change-set " このようにCloudFormationを使い、すべてのAWSリソースをコード化しました。 しかし、せっかくIaCを実現しても、コマンドを人間が打ち間違えたら台無しです。 そこで、上記コマンドの実行を含む、すべてのデプロイ作業をコード化・自動化していきました。使ったのはGitHub Actionsというサービスです。 デプロイを自動化する GitHub Actionsは、GitHubが用意する仮想マシン上で任意の処理を行えるサービスです。リポジトリへのプッシュやプルリクエスト、あるいは日時指定といったトリガー条件が用意されています。処理やトリガー条件をYAMLで書き、 .github/workflows/ に置くと自動で実行されます。 気になるお値段は、パブリックリポジトリでは無料、プライベートリポジトリでも一定量までは無料です!(詳細は GitHub公式サイト をご覧ください) 例えばプルリクエストをトリガーにechoするには以下のようにします。 name : my-actions on : pull_request : branches : - master types : [ opened, synchronize, closed ] jobs : Greet : name : Greet runs-on : ubuntu-latest #仮想マシンの種類 steps : - name : Hello world run : | #コマンドは複数実行できる echo "Hello world!" echo "Hello world!!!" AnotherJob : # 以下省略 次に、GitHub Actionsでaws-cliを使う例として、画像のディレクトリをS3にアップロードするコードを示します(詳細は こちら )。この処理は新BEARにも組み込まれています。 - name : S3 sync working-directory : static #カレントディレクトリの指定 run : aws s3 sync . s3://my-static-files --delete --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --exclude "README.md" 以下のように自作のスクリプトを実行することもできるので、可能性は無限大です!ただし仮想通貨のマイニング等は GitHub利用規約 で禁止されています。 - name : Execute script working-directory : scripts #カレントディレクトリの指定 run : ./my-script.sh 新BEARでは、GitHub Actionsで主に以下の処理を行っています。 プルリクエストの作成や編集で実行 lint ユニットテスト プルリクエストがマージされたときのみ実行 CloudFormationの実行(=AWSリソースの作成) Dockerイメージのビルド、プッシュ、ECSタスクの更新 ソースコードのアップロード、Lambda関数の更新 これにより、テストからビルド、デプロイまでを自動化できました。プルリクエストが作成されたらすぐに一連の処理が実行され、もしテストやビルドでエラーが発生したらGitHub上に通知されます。問題なくプルリクエストがマージされれば、すぐにAWS環境がダウンタイムなしで更新されます。 これは一般にCI/CD(継続的インテグレーション・継続的デリバリー)と呼ばれる手法です。BEARはCI/CDで開発効率を高め、リリースサイクルを短くできました。 まとめ 新卒研修で作ったSlackアプリのインフラを再構築し、IaCとCI/CDを実現しました。CloudFormationとGitHub Actionsを使うのは初めてで苦労しましたが、とても勉強になりました。 ちなみに、ここまで新卒研修についてご紹介しましたが、ZOZOTOWNのリプレイスプロジェクトではより高度なCI/CDを実現しています。よろしければこちらの記事もお読みください。CI/CDについてもっと詳しく学べます! techblog.zozo.com 最後に ZOZOテクノロジーズでは、SREをはじめ様々な職種で一緒に働く仲間を募集中です。ぜひ以下のページをご覧ください! tech.zozo.com また、7月16日には「案件別採用説明会」をオンラインで開催します!このイベントでは「ZOZOのやりたいことリスト」に掲載している10の案件について、今やっていること、今後やっていくことを赤裸々にお伝えする予定です。奮ってお申し込みください! tech.zozo.com zozotech-inc.connpass.com
こんにちは。ECプラットフォーム部 推薦基盤チームで、DWH・DMP・広告まわりのデータエンジニアリングを担当している大谷です。 本記事では、マーケティング部門の広告運用のインハウス化に伴ってこれまで取り組んできた広告データの収集と活用、その仕組みにフォーカスして事例をご紹介します。 背景 データの収集と活用 Arm Treasure Data Integrations Hub ログ収集 アクセスログ 検索インプレッションログ Workflow フィードローダー (Google) レポーティング Googleスプレッドシート × BigQuery CausalImpact (Google) まとめ 背景 ZOZOでは事業・開発部門を問わず、様々な部門のスタッフが各自の業務に必要となるデータを取り扱い、レポーティングなどに活用する文化が根付いています。社内では人づてにデータを扱うノウハウが伝わり、Google BigQueryの発行済アカウント数は現在200を超え、様々なビジネスシーンにデータが活用されております。 マーケティング部門はここ1年ほどで広告運用がインハウス体制に切り替わりました。インハウス化以前から様々なセグメント配信を試行したり、日々レポートと向き合って効果の良し悪しを見極める運用体制ができていたので、現場の進め方が大きく様変わりすることはなかったように思います。 自社でアカウントを持つことによって、各種広告プラットフォームへ直接接続してデータの授受が可能になりましたので、キャンペーンの都度出稿先が変わるたびに接続先のパターンを増やしてきました。 データの収集と活用 弊社では現在、下図のような構成でデータの収集・活用を行っております。 Google BigQueryがデータ利活用の中心となる役割を担い、業務データ・Webログデータ・広告データ・データマート等、種々のデータがここに集約されております。 ZOZOTOWNの基幹データベースからBigQueryへの業務データ転送、データマートの更新処理については、DigdagとEmbulkを導入し運用しています。冪等性の保証やデータマートの依存関係を自動的に組み立てて実行される集計処理など、よく考え抜かれた仕組みとなっています。こちらについては、以前に弊社の塩崎・田島・平田の3名が記事を掲載しておりますのでご参照頂ければと思います。 techblog.zozo.com techblog.zozo.com techblog.zozo.com 広告データについては、オーディエンスリスト生成に必要な属性データはBigQueryから収集し広告プラットフォームへ転送。レポートデータは広告プラットフォームから収集しBigQueryへ蓄積し、レポーティングに活用されています。 現時点 1 で接続している広告プラットフォーム(代表3つを順不同で記載) Google Ads Facebook Ads Yahoo!広告 広告データ連携については、Google Compute Engineのインスタンス上もしくはCloud Functionsに、JavaもしくはPythonのプログラムを置いて動作させています。各種プラットフォームからクライアントライブラリが提供されているケースも多く、データをBigQueryと橋渡しする比較的シンプルな処理構造になっています。 内製処理と併せて、既成のプロダクトの力を借りることで、少人数でデータ運用が実現できています。ここでは、2つのプロダクトをご紹介します。 Arm Treasure Data ... 主に外部プラットフォームとのデータ連携ハブとしての役割を担う フィードローダー(Google) ... Google Adsのショッピング広告出稿に必要な商品フィードデータ連携を自動化する役割を担う それぞれの活用事例を見ていきましょう。 Arm Treasure Data Arm Treasure Data(以降TD)は、ログ収集・Workflow・分析・機械学習・外部データ連携などの機能を1つのプロダクトで実現する、フルマネージドなデータプラットフォームサービスです。弊社における活用事例をご紹介します。 Integrations Hub 広告プラットフォームへのデータコネクターが豊富に組み込まれており、それらを活用することで内製にかかる工数を削減し、データをビジネスに活用するまでのリードタイムを短縮できています。プラットフォームAPIの仕様変更がデータコネクターで吸収されたり、パラメータ変更方法などの事前アナウンスやサポートが受けられることも障害抑止に役立っています。 ログ収集 サイトから収集したログをニアリアルタイムで利用できるログ収集機能の強みを活用して、2種類のログを収集し活用しています。 アクセスログ ZOZOTOWNのアクセスログを収集しています。TDのSDKを使わず、IMGビーコンとしてサイトにタグを設置しています。このログをBigQuery ExportされたGoogle Analyticsのアクセスログと組み合わせることで、BigQuery上でセグメントしたオーディエンスデータを広告配信に活用しています。 タグの拡張パラメータに、Google AnalyticsのCookieの1つであるClientID(以降GoogleClientID)を渡してトラッキングします。これにより、ログ活用時にtd_global_id・各種CookieID・会員ID・GoogleClientIDの相互変換が可能になります。 TDのデータベースに記録されたアクセスログのうち、商品ページの閲覧ログを30分サイクルで名寄せして、ユーザーの閲覧データを生成後BigQueryのテーブルとして扱えるようにしています。このデータは、閲覧ベースの商品ランキングや、マーケティングオートメーションシステムのメール・プッシュ配信のレコメンド枠のデータソースとして活用されています。 検索インプレッションログ ZOZOTOWNの検索結果ページにおいて、インプレッションされた商品データの収集を行っています。Web/アプリの検索結果ページが表示され、ユーザーがインビューした検索結果の商品情報を1行単位でIMGビーコンタグにまとめてログ収集し、BigQueryのテーブルとして扱えるようにしています。このデータは、検索結果の分析やロジック改善のために活用されています。 これらの膨大なログ収集を、現在までダウンタイムやデータロスト等が1度も発生することなく運用できております。 Workflow 日々のCookie名寄せ処理、広告データの収集とオーディエンスデータの更新を自動化するため、Workflowで手続きを組んでいます。 Integrations Hubの豊富なコネクターをWorkflowで扱い、データ収集〜オーディエンスデータ更新のサイクルを自動実行するようにしています エラー通知をSlackへ集約しており、トラブルがあった際、すぐ対応できるようにしています もしこれらの用途を自社基盤で賄うとしたら、インフラ・システム開発/運用体制を用意する必要があり、ビジネス活用の段階までには相応のコストとリードタイムが発生します。実質1名のオペレーターが構築〜運用までを実現できている機能性が、TDを導入する最大のメリットだと考えております。 フィードローダー (Google) ECサービス企業が取り扱う広告の中でも効果的なものの1つが商品フィード広告です。サイト外の広告面に自社の商品名、画像、価格等の情報を掲載し、直接ユーザーが求める商品ベースでサイト流入〜購入に導くことができるため、一般的によいパフォーマンスが得られやすくなります。 商品フィード広告を出稿するためには、予め広告プラットフォーム各社へ自社の商品データを転送する仕組みを用意する必要があります。一般的に、各種プラットフォームのデータ仕様に応じた商品データの変換部分と、APIもしくはSFTPサーバ経由でデータを転送する部分の両方の準備が必要です。 フィードローダーは、Google Adsのショッピング広告出稿に必要となる商品データ転送を自動化するために開発され、Googleから提供を受けているツールです。 フィードローダーを利用するメリットは以下の通りです。 APIの繋ぎ込み不要 ... 広告主側で実装する必要があるGoogle Merchant CenterのContent API連携処理がツールに内包されています。 商品データの更新が高速 ... 最短1時間間隔の更新サイクルが組めることで、商品データ更新の遅れに伴って発生しがちなトラブルである在庫欠品や、プロパー価格とセール価格の切替不備などを低減できます。 フィードローダーのアーキテクチャはすべてGoogle Cloud Platform上で構成されます。予め作成したGoogle Cloudプロジェクト内でフィードローダーのインストーラを実行すると、必要なアーキテクチャがデプロイされ、実行環境が整います。 Google Cloud Storageのバケットに商品ファイルと転送完了ファイルを置くと、Google Merchant Centerのフィードデータ更新まで一連の処理が実行されます。 フィードローダーは、前回反映から変更・削除のある商品を検知して更新処理を行うため、短時間で処理が完了するように設計されております。このような仕組みを自社で用意するのは開発リソースがかかるので、止むを得ず少ない更新頻度で運用しているケースが多いのではないでしょうか。 弊社では、広告掲載・非掲載時を問わず、常時400万件ほどの商品データを数時間間隔で更新しています。現状かなり時間的な余裕を持たせていますので、さらに更新サイクルを早めることは可能です。 現在のところフィードローダーの不具合による更新不備などは発生しておらず、高い安定性も運用担当者にとって魅力のひとつとなっています。 レポーティング レポーティングにおいては、広告の出稿情報とその効果をデータとして一元管理することによって、以下の3点の課題の解決を目指しています。 マーケターやアナリストが、サイト流入の増減理由を正確に把握し、より精度の良い分析ができるようにする 広告プラットフォームによって仕様の異なる指標群を束ねてレポーティングするために指標を標準化する 広告によるアップリフト効果を定量的に把握できるようにする 1,2に関しては、広告プラットフォームのデータと、外部から収集できない独自のマスタ情報がセットで必要となるため、運用担当者がデータの参照とメンテナンスをしやすい方式にする必要がありました。 Googleスプレッドシート × BigQuery 慣れ親しんだツールであるスプレッドシートとBigQueryを組み合わせることで、データの参照と、マスタテーブルの管理が可能になります。スプレッドシートはBigQueryのデータコネクターを標準でサポートしており、日々BigQueryに蓄えられたデータをスプレッドシート上で扱うことができます。 広告レポートデータ、Google Analyticsのアクセスログ、業務データなどを、クエリベースで結合・集計したデータをシート上に展開できます。2020年6月30日時点でデータコネクターは Connected Sheets としてアップデートされ、クエリを記述しなくてもテーブルのデータを展開できるなど、さらに利便性が向上しました。 また逆に、スプレッドシートをBigQueryのテーブル(フェデレーションテーブル)として扱うこともできます。レポーティングに必要な自社のマスタデータをスプレッドシート上で更新し、テーブルとしてデータを扱うことが可能になります。 弊社では、シートをマスタにしたキャンペーン付加情報テーブルを用意しました。各種広告プラットフォームで運用しているキャンペーンIDに、広告の種類・常時掲載またはイベント掲載・成果地点はどこかといった付加情報を定義します。これにより、各種広告プラットフォームを横断して、種類別・目的別の収益・コストなどをクエリベースで集計可能にしました。 同様のことをBIや内製ツールなどで実現する場合、高機能なツールの使い方を学ぶ必要があったり、データ更新用のインタフェースを用意する必要があったりします。またツールが分散することでマスタデータの更新作業が疎かになりがちです。 レポーティングの初期段階では、スプレッドシート活用がおすすめです。 CausalImpact (Google) 3.広告によるアップリフト効果を定量的に把握できるようにする この課題を解決するために、GoogleがOSS提供している CausalImpact を活用しています。CausalImpactは、イベントやキャンペーンによってどのくらいの介入効果があったと推定されるかを定量的に評価してくれるライブラリです。 以下のようなパラメータを与えて実行することにより、評価結果を表組の数値もしくはプロットで描画可能です。 キャンペーンの影響を受けていないコントロール群の時系列データ キャンペーンの影響を受けているトリートメント群の時系列データ キャンペーン実施前の期間 キャンペーン実施期間 時系列の周期やサンプリング回数等のオプションパラメータ 弊社では、予めオーディエンスリストを居住地や会員IDのルール等の条件でコントロール群とトリートメント群に分けておき、トリートメントリストを配信対象に設定して配信します。そして配信後、コントロール群・トリートメント群それぞれに紐づく実績値を時系列データで与え、日別の初回購入数・新規登録数・新規訪問数などのアップリフト値を算出する運用を試行しています。 まとめ 本記事でご紹介した事例について、長く広告運用に携わられている方々の中には、こうすればもっと上手にできるのに…とご感想を持たれる方もいらっしゃるかと思います。ぜひ忌憚のないご意見・ご感想をお聞かせ頂ければ幸いです。 私たちのようにインハウス化に舵を切り、日々やり方を模索されている方々に少しでも参考になる事例が含まれていればと思い、記事を書かせていただきました。 ECプラットフォーム部 推薦基盤チームではメンバーそれぞれの強みを活かしながら、検索・レコメンド・広告など、打ち手となるさまざまなプロダクトの開発に取り組んでいます。 一緒にプロダクトを成長させていくメンバーを募集しておりますので、ご興味のある方は以下のリンクからご応募ください! tech.zozo.com 本記事が公開された2020年7月時点 ↩
こんにちは、ZOZOテクノロジーズ CTO室の池田( @ikenyal )です。 ZOZOテクノロジーズでは、6/22に ZOZO Technologies Meetup -ZOZOテクノロジーズの大規模データ活用- を開催しました。 zozotech-inc.connpass.com 「ZOZOテクノロジーズの大規模データ活用に興味のある方」を対象としたイベントです。 登壇内容 まとめ 弊社のエンジニア4名が登壇し、ZOZOテクノロジーズにおける大規模データ活用の事例紹介を行いました。 ZOZOTOWNを支える検索パーソナライズ基盤 (児玉 悠 / @dama_yu ) 大規模データをAIに活かすワークフローツールの紹介 (渡辺 慎二郎 / @shikajiro ) ZOZOTOWNにおけるRecommendations AI、AI Platformの事例紹介 (安田 征弘 & アニルドフ ジャムカンヂ / @anirudhgj ) 最後に ZOZOテクノロジーズでは、プロダクト開発以外にも、今回のようなイベントの開催など、外部への発信も積極的に取り組んでいます。 一緒にサービスを作り上げてくれる方はもちろん、エンジニアの技術力向上や外部発信にも興味のある方を募集中です。 ご興味のある方は、以下のリンクからぜひご応募ください! tech.zozo.com
こんにちは!那須どうぶつ王国でスナネコの赤ちゃんの一般公開が開始された 1 ことに喜びを感じている、SRE部エンジニアの塩崎です。 ZOZOTOWNでメルマガやPUSHの配信のために、IIAS(IBM Integrated Analytics System)というDWHアプライアンスを利用しています。このIIASは以前から利用していたPureDataの後続機で、先日にマイグレーションをしたので、よろしかったらそちらの記事をお読みください。 techblog.zozo.com 概要 メルマガを配信するにあたって、メールアドレスや氏名などの個人情報を扱う必要があります。これらの情報は特に取扱いに注意を要するものであり、情報を参照できるユーザーを可能な限り限定する必要があります。素朴な解決策として、個人情報が入っているテーブル全体に対するSELECT権限をREVOKEすることが思いつきます。 しかし、この方法を採用するとテーブル全体へのアクセス権が失われるため、運用作業に影響の出る恐れがあります。特に非正規化したデータマートのテーブルは横長になりがちなので、その傾向が強くなります。 そこで、我々のチームではIIASのRCAC(Row and Column Access Control)機能を使い、隠す必要のある「列」だけをピンポイントでマスクしました。 本記事ではこのRCAC機能の設定方法やRCAC機能のアドバンストな使い方、他のDBでも使える同等の機能の紹介などをいたします。 参考: Row and column access control (RCAC) overview 個人情報をマスクする素朴な実装とその問題点 最初に個人情報などをマスクする場合の素朴な実装と、それを採った際に発生しうる問題点を説明します。 テーブルに対するSELECT権をREVOKEする 権限を剥奪するならREVOKEだということで、REVOKE文を実行することでSELECT権を剥奪することが真っ先に思い浮かぶ方は多いかと思います。 この場合は、以下のようなSQL文を実行することになります。 REVOKE SELECT ON <テーブル名> TO <ユーザー名・ロール名>; ここで注意が必要なのは、REVOKE文で指定することができる最小粒度はテーブルであるということです。権限を設定したい境界線の全てがちょうどテーブルとテーブルの間にあるならば何も問題はないです。 ですが、多くのケースではそうなっていることの方が稀です。特に歴史のあるサービスで、テーブルに対するカラム追加が何回も行われていることは珍しくありません。そのようなケースでは権限境界がテーブルの中にまで入り込むことが多々あります。REVOKE文を使った権限管理では不必要に厳しいルールになりがちで、現場での運用負荷の上がる恐れがあります。 個人情報が含まれないVIEWを作成する 次に思いつく実装は個人情報が含まれていないVIEWを作成し、元のテーブルの参照権限をREVOKEすることです。 name列とemail列に個人情報が入っているusersテーブルがある場合、以下のVIEWを作ることでこれらの列をマスクできます。 CREATE VIEW users_without_personal_information AS ( SELECT id, NULL as name, NULL as email, created_at, updated_at FROM users ); REVOKE SELECT ON users TO <ユーザー名・ロール名>; GRANT SELECT ON users_without_personal_information TO <ユーザー名・ロール名>; この方法であれば列単位での細やかな権限設定が可能です。 しかし、TABLEとVIEWの二重管理が発生し、テーブル追加やカラム追加の時の手間が倍増します。 IIASで使える冴えたやり方 列単位で個人情報をマスクしたいけど、オブジェクトの数を増やしたくはないという課題を解決するために、IIASのRCAC機能を利用しました。 以降でこの機能の使い方を説明していきます。 サンプルテーブルの作成 まずは、サンプルテーブルの作成とデータの投入を行います。 CREATE TABLE users ( id INTEGER , email VARCHAR ( 255 ) ); INSERT INTO users VALUES ( 1 , ' sato@example.com ' ), ( 2 , ' suzuki@example.com ' ), ( 3 , ' takahashi@example.com ' ), ( 4 , ' tanaka@example.com ' ); 個人情報の閲覧用ロールの作成 次に、個人情報の閲覧用ロールを作成します。このロールそのものには何も権限をGRANTせずに運用します。 CREATE ROLE personal_information_viewer; 個人情報マスクの作成・適用 ここまでで必要なテーブル・ロールの準備は済んだので、いよいよ本題に入ります。 個人情報を保護するためのマスクを作成し、その有効化を行います。3行目のCASE式でクエリを投げたユーザーが個人情報の閲覧用ロールに属しているか否かを判定し、その結果によってemailをNULLにするかどうかを決めています。 CREATE MASK EMAIL_MASK ON users FOR COLUMN email RETURN CASE WHEN VERIFY_ROLE_FOR_USER(SESSION_USER, ' PERSONAL_INFORMATION_VIEWER ' ) = 1 THEN email ELSE NULL END ENABLE; ALTER TABLE users ACTIVATE COLUMN ACCESS CONTROL; www.ibm.com 個人情報を見るための権限をユーザーに付与 このままですと、全ユーザーがemailを読み出すことができないので、個人情報の閲覧権限をユーザーに渡しましょう。 GRANT ROLE PERSONAL_INFORMATION_VIEWER TO USER <ユーザー名>; また、権限を剥奪するためには、以下のSQLを実行します。 REVOKE ROLE PERSONAL_INFORMATION_VIEWER TO USER <ユーザー名>; 個人情報の閲覧権限の有無によるSELECT結果の変化 最後に、権限の有無によってSELECT結果が変わることを確認します。 権限を持っているユーザーが SELECT * FROM users を実行した時の結果を以下に示します。マスク対象のメールアドレスが正常に閲覧できていることが分かります。なお、個人情報の閲覧権限とは別にuserテーブルそのものに対するSELECT権限も必要です。 一方で権限を持っていないユーザーが同様のクエリを実行すると以下の結果になります。メールアドレスがマスクされ、NULLになっていることが確認できます。 応用編 ここからは、この機能の応用編です。アドバンストな使い方や、他のDBで同様の機能の紹介をします。 データをNULLに置換するのではなく、部分的に見せたい 上記の例ではデータをNULLに置換していましたが、一部分だけをマスクしたいというケースはよくあります。ここではその一例として、メールアドレスのローカル部のみをマスクし、ドメインはマスクせずに表示することを考えます。 例えば、 sato@example.com というメールアドレスは @example.com に置換されます。 CREATE MASK文のRETURN句の後ろには任意のCASE式を書くことができます。そのため、以下のMASKを作成することでドメインのみを返すことができます。 CREATE MASK EMAIL_MASK ON users FOR COLUMN email RETURN CASE WHEN VERIFY_ROLE_FOR_USER(SESSION_USER, ' PERSONAL_INFORMATION_VIEWER ' ) = 1 THEN email ELSE SUBSTRING(email, INSTR (email, ' @ ' )) END ENABLE; この方法は他にも応用でき、例えば電話番号の下4桁のみを返したり、SHA-2関数などでハッシュ化した情報を返したりが考えられます。 IIAS以外のDBでも使いたい このような柔軟なマスク機能は便利なので他のDBでも使いたいです。IIAS以外のDBでは同等の機能があるかどうかを調査しました。 MySQL 現時点ではMySQLで同等の機能はありません。ドキュメントを読み足りないだけかもしれないので、もし同様の機能がある場合は教えて頂けますと幸いです。 MySQL Enterprise Edition 5.7以降でData Maskingがありますが、求めているものとは少し違います。個人情報の一部をXXXに置き換えるための便利な関数が提供されたり、ダミーデータ生成用の便利な関数が提供されるだけなようです。 dev.mysql.com PostgreSQL PostgreSQLも同様の機能はありません。ドキュメントを読み足りないだけかもしれな(略)。 PostgreSQL Anonymizerという拡張機能がありますが、これもMySQLのData Masking機能と同様で、値の一部を置き換えるための便利関数を提供しているのみです。 www.postgresql.org SQL Server SQL ServerのDynamic Data Masking機能は今回紹介したIIASの機能とほぼ同等の柔軟性を持っています。個人情報マスク済みのVIEWを作らずに個人情報マスクを実現でき、またマスクする際には一部分のみマスクすることも可能です。ただし、バージョン2016以降でないと使えない点に注意が必要です。 docs.microsoft.com Google BigQuery Google BigQueryでは、2020年6月18日現在でcolumn-level security機能がベータ版として提供されています。この機能を使うことによって、特定の列をマスクできます。ただし、データの一部のみをマスクすることはできず、特定の列を完全に見せるか見せないかのどちらかしか実現できません。 cloud.google.com まとめ IIASのRCAC機能を使うことで個人情報などのセンシティブなデータをマスクする仕組みを作ることに成功しました。この仕組みを使い、より安全に日々の運用業務を行うことができるようになることが期待されます。 ZOZOテクノロジーズではセキュリティと利用者の利便性の両立を目指すことができる仲間を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください! tech.zozo.com スナネコの赤ちゃん誕生!6月13日より公開 ↩
こんにちは。ECプラットフォーム部マイグレーションチームの高橋です。 マイグレーションチームとは ZOZOTOWNでは、システム的にレガシーな部分が多く存在しており、全体的なシステムリプレイスを進めています。その中でサーバーアプリケーションのリニューアルを行うために、2019年に発足したのがマイグレーションチームです。 現在は、データの取得・更新処理の実装の置き換えを主に対応しています。もともとのシステムではSQL Serverのストアドプロシージャを実行し、データの取得・更新を行っていた処理を、JavaでAPIとして実装し直し随時移行しています。 Spring Bootのバージョンアップ チーム発足以来、参照系の処理をSpring Bootを採用してJavaのAPIに移行することを中心に進めてきました。 ようやく、参照系のAPI作成の終わりが少しずつ見え始めたため、先延ばしになっていたSpring Bootのバージョンアップに着手しました。使用していたSpring Bootのサポート期限が2019年8月1日までとなっており、既にサポートが切れていたため、セキュリティ面からも早急に対応する必要がありました。 環境 主な環境は以下の通りです。 種類 バージョン Java 1.8 Maven 4.0.0 SQL Server 11.0 Swagger 2.0 MyBatis 3.4 後述しますが、Spring Bootのバージョンアップに伴いMyBatisのバージョンも3.4から3.5に上がりました。 バージョンアップ対象・内容 プロジェクト開始当時のSpring Boot最新バージョンは1.5.15(2018.7.30〜)、今回、バージョンアップの対象としたバージョンは着手当時の最新バージョンである2.2.5(2020.2.27〜)です。 メジャーバージョンも1つ上がっています。そのため、 Spring Boot 2.0 Migration Guide を参考に spring-boot-properties-migrator を一時的に追加して進めました。 これを追加することで、バージョンアップによりプロパティが古くなってしまっているものが警告として表示されます。 例えば、以下のようなエラーメッセージがあります。 Each configuration key has been temporarily mapped to its replacement for your convenience. To silence this warning, please update your configuration to use the new keys. The use of configuration keys that are no longer supported was found in the environment: Property source 'applicationConfig: [classpath:/application.yml]': Key: endpoints.health.sensitive Line: 63 Reason: Endpoint sensitive flag is no longer customizable as Spring Boot no longer provides a customizable security auto-configuration . Key: management.security.enabled Line: 60 Reason: A global security auto-configuration is now provided. Please refer to the migration guide or reference guide for potential alternatives. 上記エラーの修正を参考に application.yml から以下の2つのプロパティを削除しました。 management: security: enabled: false endpoints: health: sensitive: false 主なライブラリ・プラグインのバージョンアップ プロジェクトで使用している主なライブラリ・プラグインのバージョンの変更点は以下の通りです。 ライブラリ・プラグイン 変更前 変更後 org.springframework.boot 1.5.15 2.2.5 org.apache.maven.plugins 2.17 2.22.2 org.mybatis.spring.boot 1.3.1 2.1.1 com.microsoft.sqlserver 7.0.0 8.2.1 org.mockito 2.2.7 3.3.0 swagger-ui 2.7.0 2.9.2 主な修正点 ライブラリ、プラグインのバージョンアップに伴うエラーの解消 ライブラリ・プラグインのバージョンを上げたところ、一部のライブラリの影響でビルドエラーが発生したため、解消を行いました。 発生したエラーの内容と対処方法について紹介します。 Description: Failed to bind properties under 'spring.datasource.type' to java.lang.Class<javax.sql.DataSource>: Property: spring.datasource.type Value: org.apache.tomcat.jdbc.pool.DataSource Origin: class path resource [application.yml]:7:11 Reason: No converter found capable of converting from type [java.lang.String] to type [java.lang.Class<javax.sql.DataSource>] Action: Update your application's configuration こちらは、 spring.datasource.type に org.apache.tomcat.jdbc.pool.DataSource を指定していますが、 spring-boot-starter-data-jpa のバージョンアップに伴って tomcat-jdbc に移行されたため、 tomcat-jdbc を追加しました。 datetime型カラムのMyBatisとの関連付け ZOZOTOWNのシステムでは、SQL Serverのdatetime型のカラムの値を、LocalDateTime型としてMyBatisで扱っています。 しかし、MyBatisのバージョンアップをした際に以下のエラーが発生しました。 Error attempting to get column 'startDatetime' from result set. Cause: com.microsoft.sqlserver.jdbc.SQLServerException: datetime から DATETIMEOFFSET への変換はサポートされていません。 原因は OffsetDateTimeTypeHandler を利用している場合、MyBatisのバージョンアップに伴いタイムゾーン情報が失われてしまうためでした。 こちらはdatetime型に対する以下のようなHandlerを実装することで、対応できます。 @MappedTypes(OffsetDateTime.class) public class OffsetDateTimeTypeHandler extends BaseTypeHandler<OffsetDateTime> { public OffsetDateTimeTypeHandler() { } @Override public void setNonNullParameter(PreparedStatement ps, int i, OffsetDateTime parameter, JdbcType jdbcType) throws SQLException { ps.setTimestamp(i, Timestamp.valueOf(parameter.toLocalDateTime())); } @Override public OffsetDateTime getNullableResult(ResultSet rs, String columnName) throws SQLException { return getOffsetDateTime(rs.getTimestamp(columnName)); } @Override public OffsetDateTime getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return getOffsetDateTime(rs.getTimestamp(columnIndex)); } @Override public OffsetDateTime getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return getOffsetDateTime(cs.getTimestamp(columnIndex)); } private static OffsetDateTime getOffsetDateTime(Timestamp timestamp) { return timestamp != null ? OffsetDateTime.ofInstant(timestamp.toInstant(), ZoneId.of("Asia/Tokyo")) : null; } } application.ymlの設定値を修正 ライブラリのバージョンを更新した後、ビルド&エラー解消を繰り返す中で、application.ymlの設定値を変更する必要のあるものがありました。 Description: The bean 'meterRegistryPostProcessor', defined in class path resource [org/springframework/boot/actuate/autoconfigure/metrics/MetricsAutoConfiguration.class], could not be registered. A bean with that name has already been defined in class path resource [io/micrometer/spring/autoconfigure/MetricsAutoConfiguration.class] and overriding is disabled. Action: Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true こちらは、エラーメッセージの通りにapplication.ymlを以下のように修正することで解決できました。 spring: main: allow-bean-definition-overriding: true metricsの変更点 ZOZOTOWNでは、Datadogというサービスを使用してメトリクス監視を行っています。ZOZOTOWNで使用している監視ツールについては、 ZOZOTOWNの監視にモダンなツール(Datadog、PagerDuty、Sentry)を導入した話 をご参照ください。 今回のバージョンアップによりメトリクスを収集できなくなってしまった項目が一部ありました。原因は、バージョンアップにより、デフォルトでエンドポイントが公開されなくなってしまったことと、メトリクス名が変わってしまったことでした。 エンドポイントの解決策として、application.ymlファイルに /prometheus や /metrics といったエンドポイントを以下のように許可することで、バージョンアップ前と同様のメトリクスを取得できるようにしました。 management: endpoints: web: exposure: include: "metrics,prometheus" base-path: "/" また、メトリクス名は以下のような変更があったため、Datadog側で取得するメトリクス名を修正しました。 修正前メトリクス名 修正後メトリクス名 jvm_threads_live jvm_threads_live_threads tomcat_threads_busy tomcat_threads_busy_threads バージョンアップをしてみて チーム発足以降、フレームワークのバージョンアップにはなかなか着手ができなかったのですが、今後はできる限り短い間隔で最適なバージョンを取り入れていこうと思いました。 この記事を執筆している時点で、Spring Bootの最新バージョンは2.3.0へと上がっています。今回バージョンアップを実施して感じたことは、バージョンアップを長期間実施しなかったことで修正量が多くなってしまい、ハードルが上がってしまうことでした。 今後は、定期的にバージョンアップを実施し、チーム内メンバーの誰もが実施できるようにノウハウを共有していく予定です。 マイグレーションチームのこれから 2019年の春から本格的に参照系のストアドプロシージャをJava APIへ移行してきました。まだ一部ストアドプロシージャを使用している箇所もありますが、主要な部分の参照系の移行は終わっています。今後着手していく予定の更新系の処理については、参照系の数倍以上の量が予想されています。 ECプラットフォーム部マイグレーションチームでは、仲間を募集しています、ご興味のある方は、こちらからご応募ください。 hrmos.co
こんにちは、基幹システム部の伊藤です。 私は現在ZOZOのバックオフィスのシステム開発をしていますが、以前はZOZOBASEの入荷セクションで勤務していました。本稿では物流とエンジニアの両視点からZOZOBASEを支える仕組みの一部を紹介します。 ZOZOBASEについて知りたい方は、 物流倉庫の実績集計を自動化して現場の負担を軽減したはなし をご覧ください。 はじめに 弊社物流プラットフォームであるZOZOBASEは、お客様宅への配送以外、物流に関わる全てを自社で行っている強みを生かすことで年間取扱高3000億を超える国内最大のアパレル物流拠点になりました。2020年の秋には新しい物流センターの本格稼働を目指しており、私たちは日々ZOZOの成長を感じています。ZOZOBASEでは多くの従業員が日々ものすごい物量をさばいているのですが、ブランド様と色々なデータを連携することで、ZOZOBASEの様々な作業の効率化を実現しています。 今回はその中の「出荷データ連携」にフォーカスし、データ連携をすることで、ZOZOBASEのどういった作業にどのようなメリットがあるか説明していきたいと思います。 出荷API 出荷データ連携の機能はAPIとして提供しており、ブランド様から商品情報とZOZOBASEへの出荷情報をまとめて受け取るAPIです。商品がZOZOTOWN上に出るまでにはブランド様による 商品登録 → ZOZOBASEでの荷受 → 検品 → 採寸 → 撮影 → 棚入れ というステップを踏むのですが、この出荷APIはステップ上流の「 商品登録 → 荷受 → 検品 」を効率化するための仕組みになります。 次に、作業毎にデータ連携の有無によってどれくらい作業内容に違いが出てくるのかを説明します。 1. ブランド様による商品登録 2. ZOZOBASEでの荷受作業 3. ZOZOBASEでの入荷作業 1.ブランド様による商品登録 ZOZOTOWNで販売する商品情報の登録はブランド様が行います。商品情報は主に以下の様な内容で構成されています。 商品データ(商品単位) 商品名 ブランド品番 商品カテゴリID 商品単価 商品の素材 原産国 商品説明文 品番 性別タイプ 商品データ(カラー・サイズ単位) カラーID サイズID 商品バーコード これらは商品を構成する情報の一部ではありますが、1つの商品に対してもこれだけの情報の登録が必要になります。 データ連携を行っていない場合、以下の課題が常に付きまとうことになります。 管理画面から手入力で商品情報を登録するため、手間・コストが大きい 商品登録ミスが発生しやすい 販売までのリードタイムが長くなることによる販売機会ロスが発生する 管理画面からの定型データのアップロードによる一括で商品登録を行う機能も提供していますが、取扱商品数の多いブランド様だと毎日のように新商品が発売されるので、その運用すら煩雑です。 また、ZOZOBASEの荷受時のマスト条件として商品情報が登録済みであることです。商品の登録作業に時間が掛かることで、ZOZOBASEへの出荷が遅れ、販売開始までに時間を要することで販売機会を逃してしまうかもしれません。 データ連携を行う場合、以下のように、手動登録で生じる可能性のある課題がデータ連携により解決できます。 仕組み化してしまえば自動で商品登録が行われる 人の手を介さないため登録ミスがほとんど起きない 手動登録で要していたリードタイムを大幅に削減できる 出荷データ連携によりこういったメリットが生まれますが、開発に合わせてブランド様にやっていただく準備があります。 商品登録時に必要な情報として、ZOZOのカテゴリID・カラーID・サイズIDといったものがあるのですが、こちらとブランド様の各種マスタIDをマッピングデータという形で定義していただく作業です。 ブランドA社の、 カラー:ホワイト(ID:001) = ZOZOのカラー:ホワイト(ID:4) カテゴリ:Tシャツ(ID:384) = ZOZOのカテゴリ:Tシャツ(ID:7411) といったような変換テーブルのようなイメージです。 マッピングするデータはカテゴリ・カラー・サイズなど5,6種類、レコード総数で10000件を超えるブランド様もあります。はじめにマッピングテーブルを作る作業は苦労されている印象ですが、我々は今まで数十社との連携の実績がありますので、必要に応じて弊社からもアドバイスをしながら作成の支援をさせていただいています。 また、商品登録と同時にZOZOTOWNでサイズ検索を可能にする検索タグの登録を行っています。検索タグを登録することでお客様が「SサイズのTシャツ」を検索した際、サイズ名が「S」や「SMALL」の商品だけでなく「7号」や「36」など、そのブランドのSサイズに相当する商品を検索結果に表示することが可能になります。 こちらもマッピングで実現しており、 A社のサイズ36 = ZOZOの検索サイズの「S」 B社のサイズ36 = ZOZOの検索サイズの「M」 といったブランド様毎のマッピングテーブルをZOZO側で保持して、商品登録時にそこを参照しながらサイズ検索用のデータを作成しています。こちらのマッピングデータは、ブランド様が弊社の管理画面から自由に編集することが可能です。 このように、APIで受け取った商品情報を自動で登録する仕組みにより、手動登録の課題をほぼすべて解決できています。 ブランド様に連携の仕組みを整えていただく必要はありますが、10年以上前から徐々に連携ブランドを拡大し、今ではZOZOで販売される商品の約4割がデータ連携で登録されています。 2.ZOZOBASEでの荷受作業 次はZOZOBASEでの物流のスタート地点である荷受作業になります。ここからは出荷APIで商品情報と同時に受け取ったZOZOBASEへの出荷情報を活用します。荷受作業ではブランド様から納品された荷物を受け取り、同梱されている紙の納品書をデータ化する作業を行っています。納品書に記載されている情報は主に以下の項目です。 納品書情報 納品書番号 納品書日付 品番 カラー サイズ 納品数量 データ連携を行っていない場合は紙媒体の納品書に記載されているブランド品番から事前に 1. でブランド様が登録した商品データを参照し、納品書番号や数量の入力をしていきます。紙の納品書を見ながらの手入力となるため、入力ミスが発生しやすく作業に時間を取られることで、ここでもリードタイムがかかり販売機会ロスにつながります。 データ連携を行う場合、出荷APIでは 1. の商品情報の登録と同時に出荷情報を活用して、荷受作業で人の手で行っている納品書のデータ化を自動で行っているので、上記の荷受作業を完全にスキップできます。データ連携されているブランドの商品は着庫後直ぐに荷受の次の工程である入荷作業へ移行でき、荷受作業で発生するリードタイムを丸ごとカットできます。 3.ZOZOBASEでの入荷作業 最後は入荷作業です。入荷作業には2種類あり、データ連携をしていない商品を入荷する場合は目検で数量確認を行い、データ連携をしている商品を入荷する場合は商品タグに記載のバーコードの読み取りによる数量確認をしています。弊社では前者を「通常検品」、後者を「バーコード検品」と呼んでいます。 <通常検品> 通常検品は納品書と商品に付いた商品タグの情報を照らし合わせ、品番や数量などに間違いがないかをチェックし、商品管理シールを貼って入荷を行う方法です。この照らし合わせ作業が目視による作業となるため、どうしても作業時間が掛かってしまいます。数量のチェックが必須なため、複数人で行う必要があることから生産性が良くありません。 また、ここでも入力ミスが課題で、それによる過剰入荷(商品は3点しかないのに4点として在庫計上してしまう)なども起こりえます。その状態のまま販売開始の処理がされると在庫数以上に注文を受けてしまいお客様にご迷惑をおかけする原因にもなります。 こういった作業の特性から、ある程度作業の経験を積まないと円滑な作業が行えないことも難点です。 <バーコード検品> バーコード検品は商品に付いているブランドタグのバーコードをスキャナーで読み込み、在庫の計上と商品管理シールを発行する仕組みです。 通常検品と比較したときのメリットとして、以下の点があげられます。 作業が簡単で熟練度による作業量のばらつきが少ない バーコードを読む度に、事前に連携された出荷情報と突合することで商品タグや数量の目視確認が不要 入荷数を入力する作業がないので入力ミスが発生しない この入荷方法は、1.の段階で登録された「商品バーコード」情報と、同時に連携された出荷情報を活用して実現しています。「商品バーコード」情報は、商品に付いているタグに記載のバーコードと同じもので、スキャンしたタグのバーコードをあらかじめ連携された出荷情報内のバーコードと突合し入荷すべき商品であるかのチェックを行い、同時に在庫を1点計上しています。目視の作業がないので作業スピードも通常検品に比べて圧倒的に早く、入荷数の入力作業もないので入力ミスによる過剰・過少入荷も基本発生しません。 また習熟不要な入荷方法であることから、初めて作業をする人でもある程度の作業実績を上げることができるので作業が平準化されて作業予定が立てやすくなり、教育コストの削減にも寄与しています。 まとめ 今回紹介した出荷APIの例を見ただけでも、ブランド様の作業の自動化やZOZOBASEの作業の効率化に大きな効果があることをご理解いただけたかと思います。出荷API以外にも、多くのデータ連携の仕組みが稼働しており、ZOZOTOWNの要であるブランド様とZOZOBASEの作業を裏側から支えています。また、継続して連携ブランドの拡大も進めているため、さらなる作業効率化を目指して新たな連携の仕組みも増えてきています。 一方、連携するブランド様やデータの種類が増えることで管理・運用コストの増大が顕在化してきており、それは今後の解決すべき課題として考えています。 さいごに ZOZOテクノロジーズでは、今回紹介したような裏側の仕組み作りや、運営する様々なサービスを一緒に作り上げていける方を募集しています。ご興味のある方は、以下のリンクからぜひご応募ください。また、データ連携にご興味を持たれたブランド様がおられましたらZOZO営業までお気軽にお問い合わせください。 tech.zozo.com
こんにちは、SRE部ZOZO SREチームの中道です。 私が所属するZOZO SREチームは、普段ZOZOTOWNのインフラをメインに、サーバ・ネットワーク・仮想基盤・クラウド・バックアップなどの構築運用を担当しています。 DRやBCP対策の中でバックアップ/リカバリの体制や構築に悩むことは多いと思います。今後DRを検討していくにあたり、バックアップ/リカバリ方式改善のために導入したCohesityについて紹介したいと思います。 環境や状況によってバックアップ/リカバリに関する課題は様々ですが、チーム内で主にあがっていた課題は以下の3点です。 既存フローの課題点 それまで利用していたバックアップ/リストアの方式がベンダー考案・構築されているものが多い OS、DB、Webなどの対象ごとにバックアップ/リストア方式(手順書)が異なる 手順書が長く複雑で、手数が多く、リストア実績が少ない 以前まではツールの選定やフローの導入をベンダーに任せていた事が多かったため、ベンダー依存から脱却し、まずは自分たちでしっかり対応できる環境を整備する必要がありました。 複数のOS、物理サーバ、仮想サーバ、DBなどの対象ごとにバックアップ/リストアの方式が異なり、別々で手順が用意されているため、手順書が多く複雑でスムーズに対応できませんでした。チーム全員が同じレベルでの理解・把握が難しく、手順書を見てすぐ対応できるとは言い難い体制であったと思います。 定期的に復旧訓練を実施していますが、本番と同等の環境での訓練は難しかったりもします。また、実際に障害が起きたとき、迷いなくスムーズにリカバリを行えるのか? 自信をもってできるといえるのか? そこには不安な部分もあり、大きな課題であったと思います。 また既存バックアップ製品ではバックアップとリカバリに時間がかかり、SLA要件を満たせないなどの問題がでてくることもあると思います。 そうしたことから、インテリジェンスなリカバリを行える環境・体制を整える必要となり、バックアップソリューションのリプレイスを進めることになりました。比較検討した結果、運用している環境にマッチしていること・設定の容易性・単一インタフェースで対応できることによる統合効果などから、 Cohesityの導入を進めました。 Cohesityの機能について簡単に説明していきたいと思います。 Cohesityについて ハイパーコンバージド型の統合セカンダリ・ストレージがコンセプト データ保護のサイロをなくし、単一の管理UIでポリシーベースの保護が可能 制限がなく、透過的なスケールアウトやオンラインでのアップグレードと拡張が可能 グローバル重複排除と圧縮による効率化の実現 データ保護だけでなく、ファイル、オブジェクト、分析、テスト/開発などさまざまな活用が可能 広範なアプリケーションとインフラストラクチャのサポート あらゆるデータを保護 資料提供元:Cohesity Cohesityの基盤となるのが、ストレージ基本管理ソフトウェアの Cohesity DataPlatform とバックアップ管理ソフトウェアの Cohesity DataProtect です。 Cohesity DataPlatform Cohesity DataPlatformは、Webスケールのアーキテクチャーを基本として、独自の分散ファイルシステムであるSpanFSに基づくスケールアウトソリューションです。複数のセカンダリワークロードを単一のプラットフォームに統合することで、セカンダリデータとセカンダリアプリケーションの管理をモダナイズおよびシンプル化します。 資料提供元:Cohesity Cohesity DataProtect Cohesity DataProtectは、コア、クラウド、エッジまでの広範囲をカバーし、最新のアプリケーション主導型インフラストラクチャを実現する、高パフォーマンス且つソフトウェアデファインドのデータ保護ソリューションです。仮想または物理的なDB、NAS、クラウド環境といったさまざまな場所のワークロードをポリシーベースですべて管理し、包括的に保護します。サイロ化した従来のバックアップをなくし、バックアップとリカバリを操作しやすい単一のユーザーインタフェースによって管理することで、データ保護を根本的に簡素化します。 Cohesityの導入によって目指した改善点は主に以下の3点です。 Cohesity導入で目指す改善点 作業手順の簡素化・単純化 バックアップリストア方式の統一化 運用の省力化 具合的にCohesityの機能・操作について、説明しながらみていきたいと思います。 ダッシュボード VM、物理サーバ、NASなどのバックアップはすべてデフォルトのDashboardから情報が確認できます。MS SQLだけが独立のDashboardとなり、下記のようにより詳細な情報の確認が可能です。 バックアップ バックアップ取得までの流れを説明します。必要な操作は下記のステップになります。 対象ソースの登録 ポリシーの作成 バックアップジョブの作成・実行 バックアップジョブの実行確認 1. 対象ソースの登録 物理マシンにはCohesity Agentをインストールします。Cohesity AgentはDashboardからダウンロードできます。仮想マシンでは、例えばVMであればvCenterを登録することで、仮想マシンのバックアップの取得が可能になります。 2. ポリシーの作成 ポリシーの作成で実行頻度、保持期間、バックアップ形式等を設定します。ひとつのポリシーを複数のバックアップジョブで利用できます。デフォルトで3つのポリシーが用意されており、そのまま利用することもできますし、デフォルトポリシーを元に編集し利用することも可能です。 3. バックアップジョブの作成・実行 バックアップジョブの作成でソースの選択、オブジェクトの追加、ポリシーの選択、実行時間等を設定します。永久増分バックアップなので、2回目以降のバックアップの所要時間が短縮されます。VM、DBも同じステップで設定可能です。 4. バックアップジョブの実行確認 Protection Jobs画面から実行のステータスを確認できます。 リカバリ 次にリカバリ方法について説明します。必要な操作は下記のステップになります。 リカバリジョブの実行 リカバリジョブの実行確認 1. リカバリジョブの実行 ここでは仮想マシンのリカバリの手順を記載します。リカバリ画面でVMsを選択し、対象マシン、任意のデータを選択します。リカバリ対象の仮想マシンがすでに削除されている場合は、Rename Recovered VMsのチェックを外すことで同一の仮想マシンとしてリカバリできます。Networking OptionsでDetach networkにチェックを入れることで、ネットワークから切り離された状態でリカバリできます。 Point-in-timeのリカバリ SQLサーバでは、データベースのバックアップおよびT-logのバックアップを組み合わせて、Point-in-time(いつの状態でもリカバリ可能)のリカバリが対応可能です。Point-in-timeリカバリの画面は下記のようになります。カレンダーベースとなり非常にわかりやすい画面です。 2. リカバリジョブの実行確認 Recovery画面から実行のステータスを確認できます。 クローン さらにクローン機能について触れたいと思います。Test & Devという機能で、バックアップデータをテスト利用することが可能です。 対象 動作 VM 指定スナップショットをNFS共有して、vCenter & ESXi上にデータストアとしてマウント、VM起動します。 SQL 指定スナップショットを、SMB共有してWindowsサーバ側でマウントし、SQLサーバはSMB共有上のデータ・ログファイルを使って、データベースを定義します。 必要な操作は下記のステップになります。 クローンの作成 クローンの実行確認 クローンの削除(ティアダウン) 1. クローンの作成 ここでは仮想マシンのクローンの手順を記載します。Test & Dev画面でVMsを選択し、対象マシン、任意のデータを選択します。詳細設定を行いクローンを生成します。 2. クローンの実行確認 Test & Dev画面から実行のステータスの確認ができます。 3. クローンの削除(ティアダウン) Test & Dev画面からTear Downを実行します。 動作のまとめ SQL バックアップ SQLサーバのバックアップ機能ではなく、OSレベルのVSSを利用してバックアップを行う。 ※今後VDI APIを利用しての動作に変更予定。 ログはVDIを利用してバックアップ。 ※VDIは無停止が可能。 WindowsにインストールしたCohesity Agentに、Cohesityから指示を送って実施する。 WindowsがデータをCohesityに送信。 Cohesity Agentが、SQLサーバの情報を読み取りVSSを含め適切に動作。 リカバリ Cohesity本体上の指定スナップショットをSMB共有して、Windowsサーバ側でマウント。 SMB共有上のデータ・ログファイルを使ったCohesity Agentが、SQLのリストアコマンドを発行。 必要なログのマージなどもここで実施。 リストアコマンドに基づいてデータベースがアクセス可能な状態になる。 クローン Cohesity本体上の指定スナップショットをSMB共有して、Windowsサーバ側でマウント。 SQLサーバはSMB共有上のデータ・ログファイルを使って、データベースを定義。 上記の動作を約10秒程度で準備し、データベースとしてアクセス可能(Read/Write共にOK)な状態にできる。 最終的にTear Downボタンにて廃棄する動作(廃棄までがクローンの一連の流れ) VM バックアップ vCenterと高度な連携を行い、最新の管理情報を元にした動作を行う。 vCenterを経由したAPI情報を元に、vCenterライクなUIによって対象を決定。 バックアップ操作を行うと、ESXi上でスナップショットの取得を実施。 Cohesityのボリュームを、NFSでESXi上にマウント。 上記スナップショットを、CohesityのNFSデータストアにESXiが書き込み。 バックアップ終了後、ESXi上のスナップショットを削除。NFSのアンマウント。 リカバリ Cohesity本体上の指定データストアとしてスナップショットをNFS共有して、vCenter & ESXi上にマウント。 上記のファイルを利用してVMのインベントリ登録&起動(PowerOnを選んだ場合)※インスタント・マス・リカバリ機能。 裏ではストレージvMotionを利用して、指定ターゲットにファイルを配備。 ストレージvMotionの途中でVMは利用可能になる(PowerOnを選んだ場合) すべてが終わったらNFS共有がアンマウントされる。インベントリ情報も実際のターゲットのデータストアに切り替わる。 クローン Cohesity本体上の指定スナップショットをNFS共有して、vCenter & ESXi上にデータストアとしてマウント。 上記のファイルを利用してVMを起動(PowerOnを選んだ場合) 上記の動作を約20秒程度で準備し、その後OSが起動すれば利用できる(検証環境では、ログインプロンプトまで3-5分程度かかった) Read/WriteいずれもOK。バックアップには影響なし。 最終的にTear Down Cloneボタンにて廃棄する動作(廃棄までがクローンの一連の流れ) ※上記の数値は弊社での検証時の値になります。 上述した通り、VM、SQLのバックアップ・リカバリ・クローンの操作感はほぼ変わらないことがわかるかと思います。現代的なUIのもと、非常にテンポよく操作が可能です。VMとSQLが同じコンソールで対応できることによる、統合効果も大きいと思います。 導入による効果 既存のバックアップソリューションでは手順書で10ページ以上はあった各手順が、直感的に操作可能なUIによって作業手順の簡素化、単純化を可能にしました。さらに複数あったバックアップ/リストア方式の統一化を可能にしました。 今後既存バックアップを集約することで運用の省力化をさらにはかってきたいと考えています。 ZOZOテクノロジーズでは、一緒にサービスを作り上げてくれる仲間を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください! tech.zozo.com
こんにちは。ZOZOテクノロジーズZOZOTOWN部 検索チーム 兼 ECプラットフォーム部 検索基盤チームの有村です。 ZOZOTOWNでは、以前からキーワード検索時にはRDBと併用してElasticsearchを使用していました。本記事ではこれまでRDBで行っていたIDによる索引検索も含め、すべての検索をElasticsearchへ置き換えた事例と、その際に行った設定内容の一部をご紹介します。 背景 弊社CTOによるこちらの記事 にもある通り、ZOZOTOWNでは現在マイクロサービス化を進めており検索システムについてもその対象となっています。検索の文脈では、全文検索/サジェスト/ロギング等関連する様々な課題への解決策として有効であるElasticsearchを採用しマイクロサービス化を進めています。 また、もう1つの背景として検索のパーソナライズ化があります。これまでZOZOTOWNでは表示順として「人気順」という多方面から集計されたデータを基に算出されたスコアを使用していましたが、このスコアは商品に紐づくため全ユーザに対して一律な結果を返していました。一方でファッションは年代・性別だけでなく個人の趣味・時代によって大きく好みが異なる分野であり、個人にパーソナライズされた検索が必須となることは明らかでした。そこでRDBからElasticsearchのような検索に特化したシステムへ置き換えることによって検索のパーソナライズ化を実現することとしました。 先述した通りZOZOTOWNではElasticsearchをキーワード検索時の検索エンジンとしてRDBと併用していたため、Elasticsearchにデータを連携するシステムは存在していました。しかしRDBとの併用を廃止し、完全にElasticsearchのみで検索を行うにあたり既存のシステムでは以下のような難しい部分がありました。そこで今回インデキシングに特化した新規システムを構築しこれらの課題への対処を行うこととしました。 RDBからElasticsearchへ反映されるまでのリードタイム 既存のElasticsearchを用いた検索ではキーワードにマッチした商品IDのみを取得し、それをキーにRDBから最新の情報を引き当てていました。故に品切れや価格等のクリティカルな情報はRDB側で補完できたため、情報の更新は定期的なバッチで行っていました。一方でRDBの併用を廃止しElasticsearchのみでの検索を実現するにあたり、RDB内マスタテーブルの更新からElasticsearchへ変更を反映するまでのリードタイムに制約が設けられました。この制約を満たすためには既存のバッチの仕組み上対処が難しく、これに耐えうる新しい仕組みが必要でした。 多方面からのデータ登録 パーソナライズ化を進めていくにあたり、研究所の機械学習に基づくデータや分析チームのデータ分析に基づく商品特徴量など、データを追加する関係部署が複数考えられました。そのため任意の箇所から任意のデータを受け入れ、柔軟に検索可能になる状態を目指しました。今回構築した基盤はBigQueryをベースとしているため、データを抽出するクエリさえ用意できれば少ない手間でデータの反映が出来るような状態となっています。 システム概要 移行に際して新たに構築したシステムは以下のようなアーキテクチャです。 (1) CDCを取得 CDCとはChange Data Captureの略で、データベース内で行われた変更を追うことができる機能です。この機能はテーブル単位で設定可能で、レコードの追加・変更・削除やテーブルに対するカラム追加・削除の履歴が取得できます。 今回構築したシステムではこのCDCを以下の2つの用途で用いました。 テーブルへの変更差分を蓄積することによって、レプリケーションが行えないテーブル間でもスナップショットと差分の組み合わせから最新のテーブルを再現する insert/delete/updateのうちどの操作が行われたのかを判断し、Elasticsearchに対する適切な更新オペレーションを行う システム概要図のうち (1) の部分を更に詳細に示したのが以下の図になります。 SQL ServerからCDCを取得し、解読可能なメッセージの形への変換・配信するツールにはQlik Replicateを採用しました。 (2) 更新対象の取得、(3) データ返却 (1) で得られたCDCだけでは変更されたレコードの情報しかなく、用途にも書いた通り検索可能なデータにするためには別のテーブルと結合する必要があります。弊社では以前からDWHとしてBigQueryを使用しており、本システムに要求される大規模データを素早く処理するという要件に適していました。そのためBigQuery上でCDCを処理し、そこから得られた情報と、ある時点のスナップショットからなる元テーブルを結合し最新のテーブルを構築しています。 またBigQueryは構築されたテーブル同士の結合や配列への変換、データ整形も行っており、スケールしにくいバッチ側の負担を抑える役割も担っています。 (4) 更新 (2)、(3) の処理はストリームではなくバッチで行われており、データを取得した後はElasticsearchに対してリクエスト可能な形に変換し送信しています。このバッチはAppEngine上で動作しており、AppEngineでは cron.yaml を用いて定期的に処理を実行する事が可能です。今回のシステムでは変更差分を定期的に確認・処理する目的で使用するほか、定期的に行うデータ更新バッチのトリガーとして使用しています。 以上のシステムを用いることで、商品がマスタテーブルに登録されてから、もしくは売り切れ等でステータスが変わった場合でも平常時約10分以下のリードタイムでユーザに情報を届けることが可能となりました。 Elasticsearch故の苦労/Elasticsearch故にできること これまでRDBで補っていた部分をElasticsearchで全て完結しなくてはならなかったため、かなり苦戦した箇所もありました。またその逆で、RDBから解放されたことによって制約が外れ、自由にできることも増えました。今回はその中から特に印象的だった2つを紹介します。 JOINが難しい ZOZOTOWNではファッションECならではの機能として、「サイズ感が分かりづらい」という課題に対して、mm単位で指定可能なこだわりサイズ検索を提供しています。 これまで、この機能はRDB内でサイズに関するサマリテーブルを作成しJOINして検索することで実現していました。この機能を実現するためにはこれまでと同様にサイズのテーブルをJOINするか1アイテムに対して複数サイズの情報を持たせ、それぞれの情報に対して絞り込めることが必須条件でした。しかし、Elasticsearchではインデックス間のJOINの機能がないため、必然的に後者での実装を行う事となりました。 データ型として Join datatype といういかにもそれっぽいものが存在していますが、このデータ型には以下のような制約が存在しています。 同一shard上にparent/child documentが存在しなければいけない child documentがparent documentのIDを把握しなくてはいけない そこで、Join datatypeの代わりとして Nested datatype を用いることにし、1ドキュメント内にJOIN対象のデータを子要素として合わせて持たせることにしました。Nested datatypeではnested queryを用いることでそれぞれの子要素に対する検索をかけることができるため、SQLでいうJOINの動作が再現できます。 例 : 身長・体重を過去の履歴含めて検索する /* Mapping Definition */ PUT /health_index { "mappings": { "properties": { "id": { "type": "integer" }, "name": { "type": "text" }, "data": { "type": "nested", "properties": { "weight": { "type": "float" }, "height": { "type": "float" }, "created_at": { "type": "date" } } } } } } /* Create Data */ POST /health_index/_doc/ { "id": 1, "name": "Mike", "data": [ { "weight": 50, "height": 160, "created_at": "2020-01-01" }, { "weight": 55, "height": 160, "created_at": "2020-02-01" } ] } POST /health_index/_doc/ { "id": 2, "name": "John", "data": [ { "weight": 53, "height": 162, "created_at": "2020-01-01" }, { "weight": 55, "height": 165, "created_at": "2020-02-01" } ] } /* Search for matching document */ GET /health_index/_search { "query": { "nested": { "path": "data", "query": { "bool": { "filter": [ { "range": { "data.weight": { "gte": 50.0, "lte": 52.0 } } }, { "range": { "data.height": { "gte": 160 } } } ] } } } }, "_source": "name" } これらは以下のテーブル、SQLと同じような意味合いになります。 User userId name 1 Mike 2 John HealthData dataId userId weight height created_at 1 1 50 160 2020-01-01 2 2 53 162 2020-01-01 3 1 55 160 2020-02-02 4 2 55 165 2020-02-02 SELECT name FROM User INNER JOIN HealthData ON User .usedId = HealthData.userId WHERE HealthData.weight BETWEEN 50 AND 52 AND HealthData.height >= 160 GROUP BY name このように、JOINが必要となるデータに関してはあらかじめ1つのdocument内に併せ持つことによって、シャードの考慮等必要なくデータの再現が可能となりました。一方デメリットとして、Nested datatypeを使用した場合、親要素 + 子の要素分のドキュメントが生成されます。これによってKibana上のMonitoring > Indicesから見た際のDocument Countとインデックスに対して /_count で問い合わせた際の件数とで差異が生じます。 www.elastic.co 定義の追加なくA/Bテストが可能に RDBのみで検索を運用していた際のA/Bテストでは、事前にパターン分のカラム・データをテーブルに追加する作業が発生していました。 対してElasticsearchではRDBと異なり、明示的に定義されていないフィールドも受け入れ可能にするDynamic Mappingがデフォルトで有効化されています。更にこの設定はフィールドのトップレベルだけの設定だけでなく、子要素にも指定可能となっています。そのため、トップレベルではインデックスの構造を保護するためにDynamic Mappingをstrictに、A/Bテスト用の自由フィールドだけ子要素をdynamicにといった構造にすることも可能です。 /* テンプレート登録 */ PUT /_template/ab_index { "index_patterns": [ "ab_index_*" ], "mappings": { "properties": { "ab_params": { "dynamic": true, "properties": {} } }, "dynamic_templates": [ { "ab_params": { "path_match": "ab_params.*", "mapping": { "type": "float" } } } ], "dynamic": "strict" } } /* 許可されていないドキュメント */ POST /ab_index_1/_doc { "param1": 0.00 } => { "error": { "root_cause": [ { "type": "strict_dynamic_mapping_exception", "reason": "mapping set to strict, dynamic introduction of [param1] within [_doc] is not allowed" } ], "type": "strict_dynamic_mapping_exception", "reason": "mapping set to strict, dynamic introduction of [param1] within [_doc] is not allowed" }, "status": 400 } /* 許可されているドキュメント */ POST /ab_index_1/_doc { "ab_params.param1": 0.00 } => { "_index" : "ab_index_1", "_type" : "_doc", "_id" : "8t2RSnIBqlQNXy6Wvovk", "_version" : 1, "result" : "created", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 1, "_primary_term" : 1 } /* 検索 */ GET /ab_index_1/_search?filter_path=hits.hits._source { "query": { "range": { "ab_params.param1": { "lte": 2.0 } } } } => { "hits" : { "hits" : [ { "_source" : { "ab_params.param1" : 0.0 } } ] } } この設定によってデータの構造が正常であることを担保しつつ、1インデックスで必要に応じてA/Bテスト用のパラメータが追加・検索可能な状態になりました。また、背景の箇所に書いた他部署による任意のデータを受け入れ可能な状態もこれによって実現されています。実際に現在ZOZOTOWNの検索の一部でこの機能が使われており、データ分析を行うような専門のチームによって作成されたスコアを用いた評価が行われています。 まとめ 本記事では、ZOZOTOWNすべての検索をElasticsearch移行した事例と、その最中に行った印象的な設定について紹介しました。新規の検索基盤に移り変わったばかりですが、様々なA/Bテスト案や改善案が立ち上がっており移行の恩恵を早々と受けています。 最後に、ZOZOテクノロジーズでは検索をさらに改善する検索エンジニアを募集しています。ご興味のある方は、以下のリンクからぜひご応募ください! tech.zozo.com
はじめに こんにちは、ZOZO研究所福岡の下所です。 検索チームでWEARの検索ログの解析を行なっているのですが、その中でファッション業界に限らず、多くの言語学者・データ解析者がインターネット上での文字解析、特に新語の理解に苦労していることを知りました。特に日本語のように表現が曖昧で流動的な言語を理解することに多くの労力を要しているように感じました。 例えば読者の皆さんは、「かわぱんつ」というキーワードを見て何を想起されますか?私は「革のパンツ」を思い描きました。しかし、昨今のファッション用語ではこれは「かわいいパンツ」としても通用するのです…! この例のような困難なカテゴリ分類の問題が存在した時に、WEARのファッション用語に的を絞ることで、質の高い組織化を行えるよう研究を行いました。まだまだ課題は多いですが、近い将来、業界の大規模データの活用が簡素かつ高精度の状態で利用できるよう、この度の私の気づきを紹介します。 データ特徴 今回利用したデータの特徴は以下の通りです。 人気ブランド、アイテムの着こなしが探せるファッションコーディネートアプリで取得したデータ 検索ログはクリーニングが行われていない生データ ロードマップ 上記で紹介したデータを、以下の流れで考察していきました。 データフォーマットの理解とデータ抽出の自動化 現状把握と課題の確認 可能性の模索と問題点の確認 1. データフォーマットの理解とデータ抽出の自動化 データ抽出の自動化を行うために、データのパターンについて調査し、そのフォーマットの理解をします。 2. 現状把握と課題の確認 データはファッション業界におけるSNSにて最大級の規模を持つWEARの情報を用いました。自由検索とクリック検索の記録が混在するデータ形式で、データパターンとしてはクエリと情報contextの一致しないものが多い印象でした。加えて検索された単語の多くは、文法制限(表記パターン・表現)を受けず、さらに日本語の形態素解析を受け入れにくいものでした。 さらにインターネット、ファッション業界特有の造語・新語・略語も多数みられた印象です。誤字・新語・それとも短縮後なのか辞書を用いても判断がつかないものが多かったです。そのため、上記にもありますが、不鮮明な分類の影響から商品名やカテゴリ分類の由来や適応範囲を定義づけることは難しい印象です。 その結果、検索インデックスや常用語の言語コーパスを用いる既存の方法では、変化に富むファッション用語を正確に認識することに限界がありました。さらに商品説明から制作したファッション用語に特化したコーパスは、商品説明に特有の単語の用途の類似点から個々の差別化が困難だと分かりました。 残念ながら、ユーザーが一度の検索で使用する語彙数は少なく、文章の構造的な類似点を見つける手法も不向きでした。 3. 可能性の模索と問題点の確認 データ抽出の自動化を行う際に最も重要となるのが、入力単語の認識です。そのためには、正確な意味を把握することが必要になります。 実際、アパレル商品を定義する際に大きな問題となるのは、定義・分類できない言葉、由来や適応範囲が曖昧な用語の存在の多さです。デニム、ジーンズ、Gパンなどのように、表現は多種にわたり、時流の影響を受けます。 また、カーディガンとコートを組み合わせたコーディガンのような造語(out of vocaburary)も生まれやすい業界です。ガウチョ、スカーチョ、スカンツなど、キリがありません。 単語の部分一致によってカテゴリーを特定しようと試みると「スタイvs.スタイル」や「コートvs.ペチコート」のような問題も起こります。 結果として、字句解析やファジー解析では汲み取れない言葉の変化に対応する必要性を痛感しました。 他にも、短縮語の存在は形態素解析に大きな影響を与えました。 例えば「かわぱんつ」。形態素解析を用いると「かわ」と「ぱんつ」に分割され、それぞれの意味は解析によって「皮(革)」と「パンツ」という判定を受けてしまいます。正解の可能性に「可愛いパンツ」もあるらしいですが、現状では汲み取ることができません。 他にも「ワンピース」は衣類、アニメに加え、oneとpieceが可能性として出現します。 ではスペースはどうでしょうか?分けて考えるべきでしょうか?例えば「SHOO LA RUE」や「BEAUTY&YOUTH UNITED ARROWS」そして「アーバン リサーチ ドアーズ」の場合はどうでしょうか? スペースだから、アルファベットだからと言って単語の分かちとは限りません。結局、ユーザーの検索モチベーションを判断し、単語を確定する上でログの情報量は圧倒的に不足していると感じました。 解析結果 ある程度の精度でデータ抽出、タグの再分配が完了したところで抽出単語の意味に執着せず文字構造の一致から条件付統計を取りました。その結果、データから年代別・性別・ブランド別・カテゴリー別・色別などの季節変動を読み取ることができました(Fig.1参照)。Fig.1は、2017年WEARで検索された季節性の見られるカテゴリクエリの出現頻度を表しています。振幅の大きいものほど季節変動が大きくなります。つまり平坦なグラフを持つカテゴリほど通年を通して検索されていることになります。 他にも、1年を通してよく検索される商品やブランドもあれば、繁忙期・閑散期を持つ商品(色・性別・身長・カテゴリー・イベント)を区別できます(Fig.2参照)。Fig.2は、Fig.1と同様2017年のトレンドを表しています。振幅の大きいものほど季節変動が大きい(閑散期・繁忙期)ブランドとなります。 さらに条件を加えながら多くの相関関係を可視化できます。これらの情報はデータに潜む関連性を定数化し、結果を考察する際に大変有効なものです。 次にお伝えするのは私の経験(単純な条件付の時系列解析)からの考察です。WEARのユーザーはショップ名・ブランド名に固有表現を、商品は「かわぱんつ」のようにdescriptive(ニュアンス)を含んだアイテムを検索する傾向があるようです。 このような多種にわたる表現が混在するデータを解析するにあたり、データ再構築(分類ごとにまとめ直す)作業が重要になります。一般的に大規模なカテゴリー分類のほとんどは階層的分類で、カテゴリー分類はルールベースの手法を取られます。そして昨今、機械学習モデルを使用することでその精度を上げる手法も提案されています。 今回は情報の量と質(意味・定義)を同時に保存、表記揺れ・誤字・新語・造語・短縮語の判定するためのhierarchy classifcationに特化したネットワーク構造を用意しました。ここで言うネットワークは、単語の意味から上位語・下位語を纏めたグラフ上の情報構造体のことを指します。 そのために、単語の「音・構造・意味(類義語)・上(下)位語・連想語・用途」に着目し完全自動化されたファッション用語の分類器のプロトタイプを設計しました。 その結果、商品名やカテゴリ分類の由来や適応範囲を定めるパラメータに対してhypothesisを提案できるようになました。今では多数存在するコート商品(アウター)を、定義に基づいてある程度のまとまりに分類できました。 しかもこの際には、ペチコートはアウターではないと区別されています。なお、今回の研究では、日本語や中国語を英語より優先的に考察しています。 その原因は言語特徴が似ていることに加え、ステミングやレンマの必要性が低い辞書表記となるため、形態素解析の精度に問題解決の焦点がある程度定められるためでした。 今後、素材や生地の情報を組み込むことで、さらに情報補填ができると考えています。 まとめ WEARがファッション業界の中でも有数のユーザーが抱えるSNS媒体であることもあり、そのログデータは有用なものだと判断できます。しかしミクロ解析の精度を上げ、さらにデータの価値を高めるには、まだまだ数多くの課題があることも事実です。 人・社会が変化する中で、言葉・常識も変化します。その結果、情報のあり方も影響を受けます。 ファッションを数値化するために、ファッション用語を理解することは避けて通れません。複雑に絡み合う情報をデータなどから紐解き、分かることを増やしていく地道な作業の重要性を感じました。 さいごに ZOZOテクノロジーズでは検索チーム、検索基盤チームのメンバーを募集しております。 hrmos.co