セーフィー株式会社のブログ - TECH PLAY

TECH PLAY

セーフィー株式会社

セーフィー株式会社 の技術ブログ

269

こんばんは。データ分析基盤グループ所属の大室です。 セーフィーではデータドリブン経営を推進すべく、2021年頃からデータ分析基盤のシステム開発が始まり、2022年に専門の組織「データ分析基盤グループ」が立ち上がっています。 システムも組織も産声を上げたばかりのデータ分析基盤。本記事ではその歴史や背景、組織やアーキテクチャにスポットライトを当てたいと思います! そもそもデータ分析基盤とは? データレイク DWH ETL BIツール セーフィーのデータ分析基盤ヒストリー データ分析基盤グループについて 所属組織 所属メンバー 開発手法 セーフィーのデータ分析基盤アーキテクチャ 旧基盤 新基盤 最後に We are hiring! そもそもデータ分析基盤とは? データ分析基盤とは、「 大量のデータを収集・蓄積・整形し、分析を行うためのシステム基盤 」のことを指します。 データ分析基盤は様々な要素で構成されますが、代表的な構成要素として「 データレイク 」「 DWH 」「 ETL 」「 BIツール 」が挙げられます。 データレイク データレイク とは、大量のデータを保存・管理するためのアーキテクチャまたはシステムを指します。 構造化データや半構造化データ、非構造化データなど、あらゆる形式のデータをそのまま保存することができます。 DWH DWH とは、データウェアハウス( D ata W are H ouse)の略語です。 異なるデータソースからのデータを統合し、組織の意思決定に役立つ情報を提供するために設計されたデータベースシステムを指します。 データは通常、タイムスタンプや一定のルールに基づいて整理され、組織のビジネスプロセスや意思決定のニーズに合わせて構造化されます。 ETL ETL とは、( E xtract/ T ransform/ L oad)の頭文字をとった略語です。 DWHへデータを統合する際に用いられる各プロセスを指します。 ①Extract(抽出・収集) 様々なデータソースから必要なデータを抽出するプロセスです。 データベース、ウェブサービス、ファイル、外部システムなど多岐にわたるデータソースからデータを抽出します。 ②Transform(変換・整形) データを目的の形式や構造に変換するプロセスです。 データの変換には、クレンジング(不正確なデータの修正や欠損値の処理)・結合・集計などが含まれます。 ③Load(格納) 抽出・変換したデータをデータレイクやDWHに格納するプロセスです。 元々、 E xtract → T ransform → L oad の順に処理するフローが主流だったため、 ETL という略語が定着しました。 データの変換処理は専用のサーバー上で実行するのが一般的でしたが、近年(2010年代〜)は性能の高いクラウド型DWHが台頭したため、 E xtract → L oad → T ransform といったようにデータをDWHに格納してからDWH上で変換処理を実行するフロー、所謂 ELT という略語も使われ始めています。 BIツール BIツール (ビジネスインテリジェンスツール)とは、データを分析・可視化するためのソフトウェアツールを指します。 企業や組織がデータに基づいたビジネスの意思決定を効率的に行うための手段として用いられます。 BIツールは主に下記の機能を備えています。 ①データソースへの接続機能 DWH・Webサービス・Excelファイル・csvファイルなど、様々なデータソースに接続し、必要なデータを取得します。 ②データの分析機能 データの集計・集約・フィルタリング・グルーピングなどの分析操作を実行し、データからパターンや傾向を発見する手助けとなります。 ③可視化・ダッシュボード機能 データを視覚的に理解しやすい形式で表示します。グラフ・表・マップなどのコンポーネントを使用し、データをインタラクティブなダッシュボードに組み込むことができます。 BIツールそのものをデータ分析基盤に含めるかどうかは議論の余地があると思いますが、ビジネスユーザーがデータを分析・可視化する上で非常に便利なツールであることは間違いありません。 セーフィーのデータ分析基盤ヒストリー セーフィーのデータ分析基盤に纏わる歴史について紹介します。 ※脚色が若干入っています。 私は2022年4月にジョインしたので、データ分析基盤導入以前の状況はあまり詳しくないのですが… 巷の噂によると、当時は社内のデータ分析に関する問い合わせがCTOに集中し、多くの時間を割いている状況だったとのことです。 事業拡大&社員増加に伴いデータ分析の社内ニーズが日々高まる一方、当社のCTOは他にも重要な役割を担っています。 上記のような問い合わせ対応に追われ続けるのは、全社的にあまり好ましい状況ではなかったようです。 また、当時はサービスのバックエンドのデータベースシステムから直接データを参照していましたが、分析用の処理に特化したシステムではないため、分析要件を満たすデータの算出には多くの時間を要する状況でした。 このような状況の打開策として「データ分析基盤の構築」と「専門組織の組成」が挙がり、2021年下期からCTO直下組織「CTO室」主導でデータ分析基盤の構築が急ピッチで進められました。 並行してデータ人材(データエンジニアやデータアナリスト)の採用も積極的に行い、2022年上期にCTO室から「データ分析基盤の構築」及び「データ分析の支援」業務をスピンアウトした組織「 データ分析基盤グループ 」が立ち上がりました。 専門組織が立ち上がって一安心!………とはいかず、急ピッチで構築したデータ分析基盤は性能や拡張性に多数の課題を抱え、徐々に運用保守が困難な状況に陥ってしまいました。 この状況を打破すべく、2023年上期からデータ分析基盤リニューアルPJが始まり、今に至っています。 データ分析基盤グループについて 所属組織 セーフィーでは機能別組織を採用しており、社長直下組織を除くと、開発本部・企画本部・営業本部・カスタマーサービス本部・経営管理本部の5つの本部が存在します。 データ分析基盤グループは、 開発本部 配下の 第3開発部 に所属しています。 組織図 | セーフィー株式会社 - Safie Inc. 第3開発部には、顧客管理システムや契約管理システムなど、社内業務に必要不可欠なシステムの開発を担う組織「 業務システムグループ 」と、 Store People Detection Pack に代表される、映像解析AIの開発を担う組織「 AI Visionグループ 」も所属しています。 所属メンバー データ分析基盤グループには、データ分析基盤の開発や運用保守を担う データエンジニア と、各部署のデータ分析支援を担う データアナリスト が所属しています。 役割は異なりますが、ユーザーヒアリング・要件定義・テーブル設計など、密に連携して様々な業務に取り組んでいます。 開発手法 セーフィーでは開発手法として スクラム を導入している開発組織が多いのですが、データ分析基盤グループでも2023年1月から導入しています。 まだまだ試行錯誤の段階ではありますが、スクラムを導入したことによって、属人化の低減やコラボレーションの促進、柔軟性の向上や生産性の改善に繋がっていると感じています。 スクラムについては下記の記事に詳しく書かれていますので、ぜひご覧下さい! engineers.safie.link engineers.safie.link セーフィーのデータ分析基盤アーキテクチャ セーフィーが提供するクラウド録画サービスのバックエンド(通称:Safieプラットフォーム)はAWSの各サービスで構築されており、デバイス(カメラなど)やアプリケーション(Web/モバイル/AIなど)が発するデータのほぼ全てがAmazon AuroraやAmazon S3に格納されます。 セキュリティの都合上、Safieプラットフォーム側とデータ分析基盤側のAWSアカウントは分離しており、AWS Lake Formation経由でデータ分析に必要なデータのみ共有する仕組みを構築しています。 AWS Lake Formationを用いたデータ共有の詳細については下記の記事に詳しく書かれていますので、ぜひご覧下さい! zenn.dev 旧基盤 前述の通り、データ分析基盤は現在リニューアル中のため、通称「 旧基盤 」と「 新基盤 」2つのアーキテクチャが存在する状況です。2023年度内に新基盤への移行を完了する見込みです。 旧基盤では、AWS Lake Formationで共有されたデータをAWS GlueのPython Shell JobでAmazon S3にParquet形式で出力し、Amazon EC2上のShell Script/Python/SQLでAmazon Redshiftにデータを抽出/格納/変換する処理を実装しています。 社内のユーザーはRedashやTableauを通してAmazon Redshift上のデータを参照することができます。 新基盤 Amazon S3にデータを出力する処理までは旧基盤と同じですが、新基盤ではDWHをSnowflakeに変更し、Amazon S3 → Snowflakeへデータを転送する処理にはSnowpipe(Snowflakeの継続的なデータロードの仕組み)、データの変換処理にはdbt(data build tool)を採用しています。 社内のユーザーには、SQLでアドホックに分析したい場合 = Snowsight(Snowflake標準のWeb UI)、SQLが苦手な場合や視覚的に分析したい場合 = Tableau経由でSnowflake上のデータを参照していただく予定です。 旧基盤で採用していたRedashは、データ分析基盤以外の用途でも幅広く利用されている&別部署が管理しているといった事情を踏まえ、新基盤では採用しない方針です。 また、旧基盤では所謂データモデリング手法が特に採用されておらず、テーブルの構造や関係性が非常に把握し難い状況に陥っていたのですが、新基盤ではData Vault ModelingとDimensional Modelingを採用し、データモデリングの改善を図っています。 データモデリング手法については下記の記事に詳しく書かれていますので、ぜひご覧下さい! engineers.safie.link 最後に 今回はセーフィーにおけるデータ分析基盤の歴史や背景、組織やアーキテクチャについて紹介しました。 新基盤の全貌やリニューアルPJの取り組み内容については詳しく紹介できなかったため、また改めて別の記事で紹介したいと思います。 We are hiring! 余談ですが、データ分析基盤グループは慢性的な人手不足でして…一緒に働いてくれる仲間を大募集中です! セーフィーのデバイスやアプリケーションからは1日あたり数十億件のログが発生し、データ量は線形的に増加しています。 このような超ビッグデータを効率的に処理し、ビジネスの意思決定に繋げるための仕組みを一緒に作ってみませんか? ご興味のある方は セーフィー採用サイト を覗いてみて下さい。 皆様のご応募、心よりお待ちしております!
こんにちは。サーバサイドエンジニアの村田です。 今回は、セーフィーのエンジニアがどんな開発環境を使っているのかアンケートをとってみたので、その結果をみなさんに共有したいと思います。 セーフィーにはどんなエンジニアがいるの? アンケート結果 使用しているエディタ・IDEは? 使用しているキーボードは? 使用しているマウス・トラックパッドは? まとめ セーフィーにはどんなエンジニアがいるの? 今回はセーフィーの全エンジニアを対象にアンケートを取りました。セーフィーには様々な領域を担当するエンジニアが在籍しており、まとめると以下の職種のエンジニアがいます。 フロントエンドエンジニア サーバサイドエンジニア インフラエンジニア/SRE AI/画像処理エンジニア 組み込みソフトウェアエンジニア iOS/Android エンジニア 業務システムエンジニア QAエンジニア データエンジニア 各領域のエンジニアがどんな仕事をしているかは以下の記事で詳しく紹介しているので、こちらもぜひ読んでいただけると嬉しいです! engineers.safie.link アンケート結果 今回は開発環境として、使用しているエディタ・IDE、キーボード、マウスの3項目についてアンケートを取りました。それでは早速結果をみていきましょう! 使用しているエディタ・IDEは? VS Codeが半数を超える人気となっていました。選択理由としては「高機能で使いやすい」「プラグインが豊富」「Web開発では標準となっている」といった意見が挙げられていました。 続いて、モバイルアプリ開発で必須となっている Xcode, Android Studio が多く、次いでVim が一定の支持を集めました。Vimの選択理由としては「軽量」「高速で行の入れ替えが可能なため、思考の整理に使える」といった意見が挙げられていました。 少数派としては Intellij IDEAやPyCharmといったJetBrains系のIDEや、Emacs, Atom, Sublime Text, 秀丸エディタを使っている方もいました。 こちらの結果は、私自分の実感ともほぼ一致していました。普段ペアプログラミングなどで同僚がエディタを使っている様子を見る機会がありますが、やはりVS Codeを使っている人が多く、人気が高い印象を持っていました。 使用しているキーボードは? ノートPC純正のキーボードを使っている方が半分以上を占めている結果となりました。選択理由としては、「特に不便がない」「キーボードを持ち歩くのが面倒」といった意見が目立ちました。 それに次いで、 HHKBとRealforce が多くみられました。どちらのキーボードも「打鍵感が心地よい」「コンパクトさがよい」といった意見が多く挙げられていました。 少数派としては、Keychron、ELECOM スリムキーボード、ARCHISS ProgresTouch RETRO TKL、Mistel、また自作キーボードを使っているという方もいらっしゃいました。いずれのキーボードも打鍵感や見た目、キー配置、またトラックポイントの有無といったところが、こだわりポイントとして挙げられていました。 ノートPC純正のキーボードが一番多いというのは意外でした。エンジニアは結構 HHKBや自作キーボードなど、キーボードに高いこだわりを持っている方が多い印象がありましたが、セーフィーのエンジニアの中では少数派のようです。 使用しているマウス・トラックパッドは? ノートPCのトラックパッドが一番多く、続いて 会社支給のLogicoolのワイヤレスマウスを使っている方 が多い結果となりました。マウス・トラックパッドに関しては特にこだわりがないという意見が多数でした。 それ以外のものを使っている方の選択理由としては、トラックボールの有無や静音性、また腱鞘炎対策などの観点が挙げられていました。 まとめ いかがでしたでしょうか? 開発環境に関するアンケートの結果をご紹介しましたが、人それぞれ大事にしている観点が違うのが興味深いですね。今回はエディタ、キーボード、マウスの3つを対象に調査を行いましたが、他の項目についても調査してみると面白い結果が得られるかもしれません。ターミナルやシェル、リモートワークで使うようになったマイクなどもこだわりがある方が多いと思いますので、機会があればぜひ調査してみたいと思います。 最後まで読んでいただき、ありがとうございました!
企画部の居内です。Safie APIのプロジェクトの運用を担当しています。 Safie APIはβ版として提供しておりましたが、先ごろ正式版がリリースされ、現在多くのお客さまにご利用いただいております。 今回は、非エンジニアの方でも気軽にSafie APIを体験していただく機会を提供できればと思い、本職のエンジニアの方には簡単な内容ではありますが、Google App Script(GAS)を使ってSafie APIを利用する方法を、テックブログを通してご紹介できればと思っております。 この記事では、Google App Script(GAS)でSafie APIを実行し、指定したカメラの静止画をSlackへ投稿する方法をお伝えいたします。 大まかな流れ 実際の操作手順 1.Safie Developersにログインし、APIキーのアプリケーションを作成する 2.Safie Viewerにログイン後、今回APIを利用したいカメラに対しDeveloper連携を行う 3.Slack APIを開きbotを作成、botのトークンを取得する 4.Google App Scriptの新しいプロジェクトを作成する 5.Google App Scriptのプロジェクト内にプロパティを設定する 6.Google App Scriptにコードを記載し、実行を行う 最後に 大まかな流れ コードだけ知りたい方もいらっしゃるかと思いますので、先に記載します。 var API_KEY = PropertiesService.getScriptProperties().getProperty( 'API_KEY' ) var SLACK_TOKEN = PropertiesService.getScriptProperties().getProperty( 'SLACK_TOKEN' ) function postImageToSlack() { var safieApiUrl = 'https://openapi.safie.link/v2/devices/[device_id]/image' var params = { 'method' : 'GET' , 'content_type' : 'application/x-www-form-urlencoded' , 'headers' : { 'Safie-API-Key' : API_KEY, } , } var apiResponse = UrlFetchApp.fetch(safieApiUrl, params) Logger.log(apiResponse.getResponseCode()); var imageBlob = apiResponse.getBlob(); // ここからSlack投稿 var slackUrl = 'https://slack.com/api/files.upload' var payload = { 'channels' : '[channel名]' , 'file' : imageBlob, 'filename' : "image.jpg" } var options = { 'method' : 'post' , 'payload' : payload, 'content_type' : 'multipart/form-data' , 'headers' : { Authorization: "Bearer " + SLACK_TOKEN } } var slackResponce = UrlFetchApp.fetch(slackUrl,options) Logger.log(slackResponce) } ※[device_id]と[channel名]は必要なものを記載ください 実際の大まかな流れは以下の通りです。 Safie Developersにログインし、APIキーのアプリケーションを作成する Safie Viewerにログイン後、今回APIを利用したいカメラに対しDeveloper連携を行う Slack APIを開きbotを作成、botのトークンを取得する Google App Scriptに新しいプロジェクトを作成する Google App Scriptのプロジェクト内にプロパティを設定する Google App Scriptにコードを記載し、実行を行う 実際の操作手順 1.Safie Developersにログインし、APIキーのアプリケーションを作成する まず、カメラを契約中のメールアドレスでSafie Developersにログインしてください。 ログイン後、右上に表示される「管理ツール」をクリックし、管理ツール画面を開きます。 画面に表示された「APIキー認証アプリケーション新規作成」をクリックし、画面の表示に従い入力を進めてください。 今回、カメラを契約中のメールアドレスでログインしていますので、プランをトライアル、利用サービスはDevices v2を選択ください。 アプリケーション作成後は、画面の表示に従い、APIキー新規発行を実行ください。 2.Safie Viewerにログイン後、今回APIを利用したいカメラに対しDeveloper連携を行う カメラを契約したアカウントでSafie Viewerにログインすると、左メニューにユーザー設定の項目があるのでクリックください。表示された画面の「Developers連携」をクリックし、続けて「アプリケーションを追加」を押下、1の手順で作成したAPIキーを「アプリケーションコード」の欄に入力し、連携するカメラを選択ください。 連携後、Safie Developersに再度ログインを行い、1の手順で作成したアプリケーションの「連携デバイス管理」からこの作業でDevelopers連携申請を行ったカメラの承認作業を行ってください。 注意頂きたいこととして、Safie ViewerからDeveloper連携を行えるのはカメラを契約したアカウントでログインしたときのみです。シェアユーザーでは連携を行えません。 3.Slack APIを開きbotを作成、botのトークンを取得する 今回新たにSlackbotを作成する際は、下記を参考に作成ください。 Create New App > From scratchを選択し、アプリ名と利用するワークスペースを選択する Permissions > Bot Token Scope > files:writeで権限を許可する Install to Workspaceで今回利用するワークスペースにアプリをインストールする Permissions内のBot User OAuth Tokenをコピーする カメラの静止画を送付したいSlackチャンネルに、この手順で作成したSlackアプリをインストールする 4.Google App Scriptの新しいプロジェクトを作成する Google App Scriptの画面を開いて、左上のボタンから新しいプロジェクトを作成します 5.Google App Scriptのプロジェクト内にプロパティを設定する 左メニューのプロジェクトの設定を選択し、スクリプト プロパティに下記を作成ください。 プロパティ 値 API_KEY 手順1で取得したAPIキー SLACK_TOKEN 手順3で取得したBot User OAuth Token 6.Google App Scriptにコードを記載し、実行を行う エディタに戻り、下記を実行ください。実行すると手順2で連携したカメラの情報が表示されるので、表示されたdevice_id=の後ろに記載された文字列をコピーしてください。 var API_KEY = PropertiesService.getScriptProperties().getProperty( 'API_KEY' ) function mydevices() { var safieApiUrl = 'https://openapi.safie.link/v2/devices' var params = { 'method' : 'GET' , 'content_type' : 'application/x-www-form-urlencoded' , 'headers' : { 'Safie-API-Key' : API_KEY, } , } var response = UrlFetchApp.fetch(safieApiUrl, params) var json = JSON.parse(response) Logger.log(json.list); } エディタの内容を下記に書き換え、[device_id]の箇所に先ほどコピーしたdevice_idを、[channel名]の箇所に、手順3でインストールを行ったチャンネル名を記載ください。 実行すると、指定したSlackチャンネルにカメラの静止画が投稿されます。 var API_KEY = PropertiesService.getScriptProperties().getProperty( 'API_KEY' ) var SLACK_TOKEN = PropertiesService.getScriptProperties().getProperty( 'SLACK_TOKEN' ) function postImageToSlack() { var safieApiUrl = 'https://openapi.safie.link/v2/devices/[device_id]/image' var params = { 'method' : 'GET' , 'content_type' : 'application/x-www-form-urlencoded' , 'headers' : { 'Safie-API-Key' : API_KEY, } , } var apiResponse = UrlFetchApp.fetch(safieApiUrl, params) Logger.log(apiResponse.getResponseCode()); var imageBlob = apiResponse.getBlob(); // ここからSlack投稿 var slackUrl = 'https://slack.com/api/files.upload' var payload = { 'channels' : '[channel名]' , 'file' : imageBlob, 'filename' : "image.jpg" } var options = { 'method' : 'post' , 'payload' : payload, 'content_type' : 'multipart/form-data' , 'headers' : { Authorization: "Bearer " + SLACK_TOKEN } } var slackResponce = UrlFetchApp.fetch(slackUrl,options) Logger.log(slackResponce) } 最後に Google App Scriptはトリガーから、時間指定でscriptの実行が可能ですので、毎週何曜日に実行などの定期実行が可能になります。 例えば弊社であれば、郵便や宅配便が届いた際には部署ごとのラックに置いておりますので、宅配便などの受付スペースからラックまでを画角に収めれば、Slackに投稿される画像だけで配送物が届いているかの確認も可能です。 いかがでしたでしょうか。 単純な静止画の連携だけでも、総務の方から、店舗の管理、また映像の共有は行わない従業員間の連携などが可能になるものだと思っています。防犯用途以外でも、APIによりカメラの利用用途が広がるかと思いますのでぜひご利用いただければ幸いです。
こんにちは。サーバサイドエンジニアの村田です。 2023年7月14日に開催された freeeさん主催の技術イベント「freee Tech Night」に参加してきましたので、ご報告します! freee-tech-night.connpass.com freee Tech Night とは 当日の様子 感想 freee Tech Night とは 今回参加させていただいた「freee Tech Night」というイベントは freee株式会社さん主催の技術イベントで、freeeさんがクラウド会計・人事労務ソフトを開発する中で得られた経験や成果を共有する場として、定期的に開催されているイベントになっています。 他の会社のエンジニアを招いての開催も多く、今回は機会をいただきまして、弊社と一緒に開催させていただく運びとなりました。 freee-tech-night.connpass.com 当日の様子 freeeさんとセーフィーそれぞれ2名ずつエンジニアが参加し、パネルディスカッション形式で進行していきました。セーフィーからは自分と同じくサーバサイドのエンジニアでテックリードでもある 鈴木敦志 さんが参加しました。 イベントはオフライン開催でしたが、YouTube liveで配信もされておりオンラインでの参加もできるようになっていました。本格的なスタジオ設備がオフィス内にあってびっくりしました。 今回のイベントのテーマは「巨大なデータとの向き合い方」ということで、 freeeさんとセーフィーの双方で、どういったデータを扱っているか、データの規模、巨大なデータを扱う上での苦労や工夫などをお互いに話していきました。 セーフィー側からは、まず扱うデータとしては映像データが常時入ってきており、大量にあること。また映像データだけでなく解析データも非常に量が多い点についてお話させていただきました。 実際のデータ量についても具体的にご紹介させていただきました。セーフィーでは1日に約900TBの映像データが入ってきており、それが契約プランに応じた期間(7日 ~ 1年) 保存され、トータルで約23PB程度の映像データがあること、また解析データは一日約1億件が溜まっていっています。 これらの巨大なデータを扱う上で工夫している点として、まずインフラ側の構成についてご紹介させていただきました。セーフィーでは一定カメラ台数(約13000台程度)ごとにそれを処理するためのインフラ構成(ネットワーク、DB、redis、サーバ類)一式を新たに用意する水平分割の構成をとっており、それによりインフラ側の負荷を抑えられるようにしています。 また、DBクエリ周りの工夫点としては、MySQLは削除が遅いため論理削除 + 非同期バッチで削除するパターンをよく使っている点などについてご紹介させていただきました。 freeeさんの方からは、会計データを大量に扱っていることや確定申告や決算申告のタイミングで負荷高くなること、対策として地道なクエリチューニングや読み込み系のクエリをreaderに向けるようにすることによるwriterの負荷軽減の取り組みなどをについて、ご紹介いただきました。 短い時間ではありましたが、お互いに実際に扱っているデータの性質や規模感、取り組みについてかなり具体的に共有することができました。 他にもドメインのキャッチアップ方法など、いろんなお話をしたのですが、ここには書ききれないので、詳しい内容はyoutube live配信のアーカイブを見てみてください! www.youtube.com 感想 こういったイベントに出演するのは初めてだったのでとにかく緊張しましたが、とても学びある時間になり、良い経験となりました。終了後の懇親会では、いろんな方とお話できたのも楽しく、オフラインイベントにはこういった人と直接会話する楽しさがあるのが良いなと感じました。 イベントの運営について、freeeのエンジニアの方にお話を伺ったところ、もともと現場のエンジニア呼びかけから始まったイベントで、運営も自分達でやられているとのことで、自分達自身で技術発信の文化を作っていっているところがとても素晴らしいなと感じました。 またこういうイベントがあれば積極的に参加したいと思います!
こんにちは。Safieでモバイルアプリの開発をしている渡部です。 モバイル版Safie Viewerでは、バージョン3.11.0から、新しいバージョンがリリースされるとユーザーに向けてアップデートを知らせるアラートが表示されるようになっています。 iOS版 Safie Viewer 今回は、このアップデート機能の実装について振り返ってまとめてみました。 実装の経緯 必要な要件 サービス選定 実装 RemoteConfigの設定 アプリ側の設定 効果 実装の経緯 モバイル版Safie Viewerでは現在約1ヶ月おきに定期リリースを行っています。以前は開発サイクルがもう少し長期間だったのですが、チーム体制が充実したこともありコンスタントに新しいバージョンを用意することができるようになりました。 それに伴い、ユーザーに新バージョンをアピールする必要性もより高まりました。新機能を実装したり、バグを修正するスピードが早まったものの、ユーザーに気づいてもらえない限りは意味がありません。 より安定したバージョンを使ってもらうためにも、アップデート機能を実装することになりました。 必要な要件 実装にあたって、まずはモバイルアプリのアップデート機能で求める要件を洗い出しました。 立ち上げたアプリが対象バージョンより低い場合、アップデートのお知らせをアラートで表示したい 対象バージョンとは必ずしも最新版ではなく、任意のバージョンを指定したい。 強制と半強制を使い分けたい アプリを立ち上げてアップデートについてアラートが表示された際に、アップデートしないと先に進めないパターン(強制)と、アップデートせずともアプリを使い続けられるパターン(半強制)の2つを、バージョンによって切り替えたい。 深刻なバグ修正の場合は強制で、それ以外の場合は半強制でお知らせしたい。 その他、いろいろな設定値を持ちたい アラート表示を開始/終了する日時、アラート表示の対象となるOSバージョンなど。 iOS版・Android版の設定を一括で管理したい 手軽に更新したい サービス選定 アップデート機能を実現するための仕組みはすでに色々なサービスで提供されています。 例えば、 ①iTunes Search APIを使用する(iOSのみ) ②Google Play Core Libraryを使用する(Androidのみ) ③アップデート機能用の3rdパーティライブラリを使用する ④バックエンドサービスにアップデートの設定ファイルを置き、アプリ側で読み込む など。 ①や②は公式のサービスですが、カスタム性は乏しいです。③もメンテナンスのことを考えるとできれば避けたくなります。 ということで、今回は、前述した要件を満たすために④の方法で、サービスはFirebase Remote Configを活用することにしました。 ④を実現するだけなら、その他のBaaSを活用するやり方もありますが、 すでにFirebaseを導入済みだったこと コンソール上のUIのみで複数ファイルを更新できる手軽さ 以上を加味して、 Remote Configを選びました。 実装 Firebaseプロジェクトの作成については、今回は割愛します。 RemoteConfigの設定 要件をふまえて、コンソールで以下のようなjsonを設定しました。 { "parameters": { "ios_update": { "defaultValue": { "value": { "version":"3.19.0", "is_force":true, "start_date":"2023-02-28", "support_os_version":"13.0" } }, "valueType": "JSON" }, "android_update": { ... } } } version: 対象バージョン。この数値より低いバージョンのアプリを起動したら、アップデート案内のアラートを表示する。 is_force: 強制か半強制か start_date:アラート表示開始日 end_date: アラート表示終了日 support_os_version:アプリのサポート対象OS アプリ側の設定 プロジェクトにRemote ConfigのSDKを追加 iOS CocoaPodsを使用しているので、Podfileに以下を追加。 pod 'Firebase/RemoteConfig' Android build.gradleに以下を追加。 implementation 'com.google.firebase:firebase-config-ktx:21.4.0' 2. アップデートのアラートを表示したいタイミングで、RemoteConfigからjsonを取得するように実装。 iOS アプリを起動して最初に立ち上がるViewControllerにて override func viewWillAppear (_ animated : Bool ) { // ここで取得 } 通知をタップして任意の画面を開いた時 func userNotificationCenter (_ center : UNUserNotificationCenter , didReceive response : UNNotificationResponse , withCompletionHandler completionHandler : @escaping () -> Void ) { // ここで取得 } フォアグラウンドからの復帰時 func applicationWillEnterForeground (_ application : UIApplication ) { // ここで取得 } 取得の流れ // シングルトンオブジェクトを作成 remoteConfig = RemoteConfig.remoteConfig() // RemoteConfigから値を取得 remoteConfig.fetch() { (status, error) -> Void in remoteConfig.activate { changed, error in // RemoteConfigStruct:RemoteConfigで設定したjsonと対応するStruct let response = try ? JSONDecoder().decode(RemoteConfigStruct. self , from : remoteConfig [“ios_update”].dataValue) } } Android 起動時、フォアグラウンド復帰時にて class MainApp : Application() { override fun onCreate() { ProcessLifecycleOwner. get ().lifecycle.addObserver( object : DefaultLifecycleObserver { override fun onStart(owner: LifecycleOwner) { // ここで取得 } }) } } 取得の流れ // シングルトンオブジェクトを作成 val remoteConfig = Firebase.remoteConfig // RemoteConfigから値を取得 remoteConfig.fetchAndActivate().addOnCompleteListener { task -> if (task.isSuccessful) { val gson = GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create() // RemoteConfigData:RemoteConfigで設定しているjsonと対応するDataクラス val remoteConfigData= gson.fromJson(remoteConfig.getString(”android_update”), RemoteConfigData :: class .java) } } 3. 取得した設定値(remoteConfigData)によって、アラートの表示/非表示、強制/半強制を出し分けるように実装。 効果 強制アップデートの仕組みを実装してから、ユーザーは以前よりも速やかに最新版に移行してくれるようになったのか、Analyticsから確認してみました。 いずれも、新バージョンをリリースしてから1ヶ月間のユーザー推移を表しています。 ①強制アップデート実装前(旧バージョン3.9 → 新バージョンv3.10) ②実装して2ヶ月経過(旧バージョン3.12 → 新バージョンv3.13) ③実装して半年以上経過(旧バージョン3.18 → 新バージョンv3.19) 強制アップデート実装から時間が経過するにつれ、旧バージョン使用数の減退が早まっています。旧バージョンの使用割合も減りました。 今まで以上に多くのユーザーがより早く新バージョンを使用することに繋がり、アプリの安定化に繋がったかなと思います🙌
​データ分析基盤グループでデータエンジニアをしている平川です。 DataVaultに関する記事の第2回目となります。(第1回の記事は こちら です) 第2回の記事は、DataVaultモデリングの中心となるHub/Link/Satelliteをdbtのパッケージを利用して作っていくという内容です。 2,3回目の内容が当初と少し変わっていますので、再掲いたします。 第1回: DataVaultってなに?どんな特徴があるの? 第2回: automate_dvを使ってDataVaultモデリングの中心となるテーブルを作ってみてわかったこと ← 今回はここ 第3回: BusinessVault、発展的なSatelliteテーブルやキーがNullだった場合の対処方法についてなど 前回のおさらい はじめに automate_dvについて automate_dvとは? 便利な点は何? 注意点 automate_dvの使い方 インストール方法 Hub/Link/Satelliteの実装 ハッシュキーの生成 Hubの生成 Linkの生成 Satelliteの生成 automate_dvを使う際の小技 automate_dvを使用していてハマったポイント 履歴化されているデータソースを取り込む場合 取り込みたいモデルの構造 対象のモデルのデータの変化 automate_dvを使ってSatelliteテーブルを作った際の結果 RawVault層までの実装にautomate_dvを使った感想など まとめと次回予告 参考資料 前回のおさらい 前回の記事では、データウェアハウス設計における1つのアプローチであるDataVaultの特徴やメリットについて説明しました。DataVaultは、柔軟性や拡張性に優れ、大量のデータを効果的に管理することができます。 また、DataVaultを実装するためには、Hub/Link/Satelliteという構造を持ったテーブルを生成する必要があります。これらのテーブルの生成には手間がかかることがありますが、automate_dvというパッケージを使うことでテーブル生成を簡易にすることができます。 今回の記事では、このテーブルの生成をサポートするautomate_dvの紹介と実際のテーブル生成までの手順を解説します。 はじめに 第2弾の記事では、データウェアハウスのテーブル構築方法について、automate_dv 1 を利用した手法について紹介します。 まずは、前回の内容を振り返りつつ、データウェアハウスの設計に関する用語を簡単に説明します。 用語 意味 ELT データウェアハウスにおけるデータの取り込み方法の1つで、データを抽出してから変換し、最後にロードする手法です dbt データウェアハウスの構築や管理をするためのオープンソースのツールです。SQL(一部Jinja)でデータパイプラインを記述できます。 DataVault データウェアハウスの設計パターンの1つで、拡張性や柔軟性の高さなどが特徴です。 Hub DataVaultで構築する上での中心となるテーブルで、ビジネスキーとそのハッシュキーで構成されます。 Link DataVaultでのHub同士の関係性を表すテーブルで、関連するHubのハッシュキーと関連する2つのHubのビジネスキーをconcatしてハッシュ化したキーで構成されます。 Satellite HubやLinkのキーに対して付随する属性情報を保持するテーブルです。属性情報をまとめてハッシュ化したHashdiffカラムによって、変化を検知することができます。 次のセクションでは、automate_dvを使用してDataVaultのテーブル構築を行う方法について、手順を解説します。 automate_dvについて automate_dvとは? dbtのパッケージの1つで、DataVault2.0モデルに基づいたデータウェアハウスの構築をサポートしてくれます。このパッケージのマクロを利用することで、DataVaultモデリングに関するテーブルのSQL実装を簡単に行うことができます。 便利な点は何? automate_dvを利用することで、Hub/Link/Satelliteテーブルやハッシュ化したキーを含むテーブルの作成を容易にすることができます。 これにより、SQLの記述量を大幅に削減し、テーブルの実装にかかる時間を短縮することができます。 注意点 automate_dvは便利なツールですが、利用しているプラットフォームによっては使用できないマクロがあるため注意が必要です。 例えば、一部のマクロはRedshiftやPostgreSQLでは利用できません。一方で、Snowflake/BigQuery/MS SQL Serverでは、現在一般提供されているマクロを使用することができます。 プラットフォームによって利用できる機能に違いがあるため、使用する前にドキュメントを参照することをおすすめします。 automate_dvの使い方 インストール方法 automate_dvを使うにはdbtを実行する環境にインストールする必要があります。dbtのプロジェクト直下にある。 package.yml に以下のようにautomate_dvパッケージの依存関係を追加し、 dpt deps を実行してください。 packages : - package : Datavault-UK/automate_dv version : 0.9.5 Hub/Link/Satelliteの実装 dbt run or dbt build を実行することで作成したSQLから各種ビューとテーブルを作成してくれます。 Hub/Link/Satelliteを作成するための各SQLの書き方を以下で説明していきます。 ハッシュキーの生成 Hub/Link/Satelliteを実装するにはそれぞれハッシュ化されたキーとhashdiffが必要になります。 そのため、実装する3テーブルが参照するビューも必要になるため、まずはハッシュキーを含むビューをautomate_dvを使って実装していきます。 実装例として、 orders , customers , products , customers_orders , orders_products という5つのソーステーブルからハッシュキーを含むビューを作成していきます。 hashed_ordersのSQLは下記のようになります。(hashed_orders以外のSQLについては折り畳んでありますので詳細を知りたい方は展開していただければと思います。) {%- set yaml_metadata -%} -- ① source_model: -- ② orders derived_columns: RECORD_SOURCE: " !ORDER_MANAGEMENT " hashed_columns: ORDER_HASH_DIFF: is_hashdiff: true columns: - ORDER_NAME - ORDER_AMOUNT - CREATED_AT ORDER_HK: - ORDER_ID {%- endset -%} {% set metadata_dict = fromyaml(yaml_metadata) %} {{ automate_dv.stage( include_source_columns= true , source_model=metadata_dict[ " source_model " ], derived_columns=metadata_dict[ " derived_columns " ], hashed_columns=metadata_dict[ " hashed_columns " ], null_columns=none, ranked_columns=none, ) }} -- ③ ① : jinjaの記述方法でyaml形式で各種パラメータを設定する ② : ハッシュキーの設定や参照するモデルの設定などをする ③ : automate_dvのstageマクロを使い各種パラメータを引数に設定する その他のモデルのクエリ hashed_products {%- set yaml_metadata -%} source_model: products derived_columns: RECORD_SOURCE: "!ORDER_MANAGEMENT" hashed_columns: HASH_DIFF: is_hashdiff: true columns: - PRODUCT_NAME - PRICE - CREATED_AT PRODUCT_HK: - PRODUCT_ID {%- endset -%} {% set metadata_dict = fromyaml(yaml_metadata) %} {{ automate_dv.stage( include_source_columns=true, source_model=metadata_dict["source_model"], derived_columns=metadata_dict["derived_columns"], hashed_columns=metadata_dict["hashed_columns"], null_columns=none, ranked_columns=none, ) }} hashed_customers {%- set yaml_metadata -%} source_model: customers derived_columns: RECORD_SOURCE: "!ORDER_MANAGEMENT" hashed_columns: HASH_DIFF: is_hashdiff: true columns: - EMAIL - PREFECTURE - CREATED_AT CUSTOMER_HK: - CUSTOMER_ID {%- endset -%} {% set metadata_dict = fromyaml(yaml_metadata) %} {{ automate_dv.stage( include_source_columns=true, source_model=metadata_dict["source_model"], derived_columns=metadata_dict["derived_columns"], hashed_columns=metadata_dict["hashed_columns"], null_columns=none, ranked_columns=none, ) }} hashed_customers_orders {%- set yaml_metadata -%} source_model: customers_orders derived_columns: RECORD_SOURCE: "!ORDER_MANAGEMENT" hashed_columns: HASH_DIFF: is_hashdiff: true columns: - CREATED_AT CUSTOMER_HK: - CUSTOMER_ID ORDER_HK: - ORDER_ID CUSTOMER_ORDER_HK: - CUSTOMER_ID - ORDER_ID {%- endset -%} {% set metadata_dict = fromyaml(yaml_metadata) %} {{ automate_dv.stage( include_source_columns=true, source_model=metadata_dict["source_model"], derived_columns=metadata_dict["derived_columns"], hashed_columns=metadata_dict["hashed_columns"], null_columns=none, ranked_columns=none, ) }} hashed_orders_products {%- set yaml_metadata -%} source_model: orders_products derived_columns: RECORD_SOURCE: "!ORDER_MANAGEMENT" hashed_columns: HASH_DIFF: is_hashdiff: true columns: - CREATED_AT ORDER_HK: - ORDER_ID PRODUCT_HK: - PRODUCT_ID ORDER_PRODUCT_HK: - ORDER_ID - PRODUCT_ID {%- endset -%} {% set metadata_dict = fromyaml(yaml_metadata) %} {{ automate_dv.stage( include_source_columns=true, source_model=metadata_dict["source_model"], derived_columns=metadata_dict["derived_columns"], hashed_columns=metadata_dict["hashed_columns"], null_columns=none, ranked_columns=none, ) }} 続いて、今作成したハッシュキー含むモデルから、Hub/Link/Satelliteを作成していきます。 Hubの生成 ordersのHubテーブルの作成は以下のSQLのようになります。(customersとproductsのHubテーブルは折りたたみ内にSQLを記載しています) {{ config(materialized= " incremental " ) }} {%- set yaml_metadata -%} source_model: hashed_orders src_pk: ORDER_HK src_nk: ORDER_ID src_ldts: LOADED_AT src_source: RECORD_SOURCE {%- endset -%} {% set metadata_dict = fromyaml(yaml_metadata) %} {{ automate_dv.hub( src_pk=metadata_dict[ " src_pk " ], src_nk=metadata_dict[ " src_nk " ], src_ldts=metadata_dict[ " src_ldts " ], src_source=metadata_dict[ " src_source " ], source_model=metadata_dict[ " source_model " ], ) }} その他のモデルのクエリ hub_products {{ config(materialized="incremental") }} {%- set yaml_metadata -%} source_model: hashed_products src_pk: PRODUCT_HK src_nk: PRODUCT_ID src_ldts: LOADED_AT src_source: RECORD_SOURCE {%- endset -%} {% set metadata_dict = fromyaml(yaml_metadata) %} {{ automate_dv.hub( src_pk=metadata_dict["src_pk"], src_nk=metadata_dict["src_nk"], src_ldts=metadata_dict["src_ldts"], src_source=metadata_dict["src_source"], source_model=metadata_dict["source_model"], ) }} hub_customers {{ config(materialized="incremental") }} {%- set yaml_metadata -%} source_model: hashed_customers src_pk: CUSTOMER_HK src_nk: CUSTOMER_ID src_ldts: LOADED_AT src_source: RECORD_SOURCE {%- endset -%} {% set metadata_dict = fromyaml(yaml_metadata) %} {{ automate_dv.hub( src_pk=metadata_dict["src_pk"], src_nk=metadata_dict["src_nk"], src_ldts=metadata_dict["src_ldts"], src_source=metadata_dict["src_source"], source_model=metadata_dict["source_model"], ) }} Linkの生成 orderとproductの関係性を記述するLinkテーブルの作成は以下のSQLのようになります。(link_customers_ordersは折りたたみ内にSQLを記載しています。) {{ config(materialized= " incremental " ) }} {%- set yaml_metadata -%} source_model: hashed_orders_products src_pk: ORDER_PRODUCT_HK src_fk: - ORDER_HK - PRODUCT_HK src_ldts: LOADED_AT src_source: RECORD_SOURCE {%- endset -%} {% set metadata_dict = fromyaml(yaml_metadata) %} {{ automate_dv. link ( src_pk=metadata_dict[ " src_pk " ], src_fk=metadata_dict[ " src_fk " ], src_ldts=metadata_dict[ " src_ldts " ], src_source=metadata_dict[ " src_source " ], source_model=metadata_dict[ " source_model " ], ) }} その他のモデルのクエリ link_customers_orders {{ config(materialized="incremental") }} {%- set yaml_metadata -%} source_model: hashed_customers_orders src_pk: CUSTOMER_ORDER_HK src_fk: - CUSTOMER_HK - ORDER_HK src_ldts: LOADED_AT src_source: RECORD_SOURCE {%- endset -%} {% set metadata_dict = fromyaml(yaml_metadata) %} {{ automate_dv.link( src_pk=metadata_dict["src_pk"], src_fk=metadata_dict["src_fk"], src_ldts=metadata_dict["src_ldts"], src_source=metadata_dict["src_source"], source_model=metadata_dict["source_model"], ) }} Satelliteの生成 orderの属性情報を記述するSatelliteテーブルの作成は以下のSQLのようになります。(sat_customersとsat_productsは折りたたみ内にSQLを記載しています。) {{ config(materialized= ' incremental ' ) }} {%- set yaml_metadata -%} source_model: hashed_orders src_pk: ORDER_HK src_hashdiff: HASH_DIFF src_payload: - ORDER_NAME - ORDER_AMOUNT - CREATED_AT src_ldts: LOADED_AT src_source: RECORD_SOURCE {%- endset -%} {% set metadata_dict = fromyaml(yaml_metadata) %} {{ automate_dv.sat( src_pk=metadata_dict[ " src_pk " ], src_hashdiff=metadata_dict[ " src_hashdiff " ], src_payload=metadata_dict[ " src_payload " ], src_ldts=metadata_dict[ " src_ldts " ], src_source=metadata_dict[ " src_source " ], source_model=metadata_dict[ " source_model " ], ) }} その他のモデルのクエリ sat_customers {{ config(materialized='incremental') }} {%- set yaml_metadata -%} source_model: hashed_customers src_pk: CUSTOMER_HK src_hashdiff: HASH_DIFF src_payload: - EMAIL - PREFECTURE - CREATED_AT src_ldts: LOADED_AT src_source: RECORD_SOURCE {%- endset -%} {% set metadata_dict = fromyaml(yaml_metadata) %} {{ automate_dv.sat( src_pk=metadata_dict["src_pk"], src_hashdiff=metadata_dict["src_hashdiff"], src_payload=metadata_dict["src_payload"], src_ldts=metadata_dict["src_ldts"], src_source=metadata_dict["src_source"], source_model=metadata_dict["source_model"], ) }} sat_products {{ config(materialized='incremental') }} {%- set yaml_metadata -%} source_model: hashed_products src_pk: PRODUCT_HK src_hashdiff: HASH_DIFFF src_payload: - PRODUCT_NAME - PRICE - CREATED_AT src_ldts: LOADED_AT src_source: RECORD_SOURCE {%- endset -%} {% set metadata_dict = fromyaml(yaml_metadata) %} {{ automate_dv.sat( src_pk=metadata_dict["src_pk"], src_hashdiff=metadata_dict["src_hashdiff"], src_payload=metadata_dict["src_payload"], src_ldts=metadata_dict["src_ldts"], src_source=metadata_dict["src_source"], source_model=metadata_dict["source_model"], ) }} automate_dvを使う際の小技 automate_dvのドキュメントでは、stageマクロを実行する際のsource_modelには最新分だけを含んだモデルを対象にするのがベストプラクティスとされています。 ですが、止むを得ず参照するモデルに複数日付が含まれていることがあるかもしれません。この場合、最新分だけが存在する層を作るということもできますが、stageマクロを使いつつ、最新分だけのビューを作成することも可能です。 具体的な手順としては、 automate_dv.stage 部分をwith句に入れて一時テーブルとして定義し、日付を絞る方法です。以下のように記述することで実現できます -- パラメータの設定は省略 with stage as ( {{ automate_dv.stage( include_source_columns= true , source_model=metadata_dict[ " source_model " ], derived_columns=metadata_dict[ " derived_columns " ], hashed_columns=metadata_dict[ " hashed_columns " ], null_columns=none, ranked_columns=none, ) }} ) select * from stage where loaded_at = ' YYYY-MM-DD ' automate_dvを使用していてハマったポイント automate_dvは、データウェアハウスの構築において高いパフォーマンスを発揮するツールですが、日本語の情報も少なくハマってしまった時に原因を理解するのに時間がかかると思います。ここでは、実際にDataVault層のSatelliteテーブルを構築する際にハマったポイントについて説明していきます。 履歴化されているデータソースを取り込む場合 Satelliteテーブルに取り込む対象が、前日からの増分のみであれば、automate_dvのSatelliteマクロを使用しても問題ありません。しかし、Satelliteが参照するモデルが過去から現在までの全期間のレコードを持っている場合、予期しない結果になる可能性があります。具体的なサンプルと動作を見ていきましょう。 取り込みたいモデルの構造 カラム名 history_id customer_id amount created_at loaded_at 対象のモデルのデータの変化 2023-04-02 history_id customer_id amount created_at loaded_at 1 1 1000 2023-04-01 12:00:00 2023-04-02 00:00:00 2 2 1500 2023-04-01 13:00:00 2023-04-02 00:00:00 2023-04-03 history_id customer_id amount created_at loaded_at 1 1 1000 2023-04-01 12:00:00 2023-04-03 00:00:00 2 2 1500 2023-04-01 13:00:00 2023-04-03 00:00:00 3 1 1000 2023-04-02 12:00:00 2023-04-03 00:00:00 4 1 3000 2023-04-02 12:30:00 2023-04-03 00:00:00 2023-04-04 history_id customer_id amount created_at loaded_at 1 1 1000 2023-04-01 12:00:00 2023-04-04 00:00:00 2 2 1500 2023-04-01 13:00:00 2023-04-04 00:00:00 3 1 1000 2023-04-02 12:00:00 2023-04-04 00:00:00 4 1 3000 2023-04-02 12:30:00 2023-04-04 00:00:00 5 3 5000 2023-04-03 15:00:00 2023-04-04 00:00:00 6 1 1000 2023-04-03 16:00:00 2023-04-04 00:00:00 automate_dvを使ってSatelliteテーブルを作った際の結果 SatelliteテーブルのSelect結果 上記のSatelliteテーブルの赤い枠で囲まれた1行目と5行目は同一のレコードになっています。全期間の履歴を保持するようなモデルに対してSatelliteマクロを使うとこのような現象が起きてしまいます。 このような状態になってしまう原因は、automate_dvのマクロを使用することで生成されるSQLを見ることでわかります。 以下生成されたSQLを抜粋 WITH source_data AS ( SELECT a.CUSTOMER_HK, a.HASH_DIFF, a.AMOUNT, a.CREATED_AT, a.LOADED_AT, a.RECORD_SOURCE FROM hs_order_histories AS a WHERE a.CUSTOMER_HK IS NOT NULL ), latest_records AS ( SELECT a.CUSTOMER_HK, a.HASH_DIFF, a.LOADED_AT FROM ( SELECT current_records.CUSTOMER_HK, current_records.HASH_DIFF, current_records.LOADED_AT, RANK () OVER ( PARTITION BY current_records.CUSTOMER_HK ORDER BY current_records.LOADED_AT DESC ) AS rank FROM sat_order_histories AS current_records JOIN ( SELECT DISTINCT source_data.CUSTOMER_HK FROM source_data ) AS source_records ON current_records.CUSTOMER_HK = source_records.CUSTOMER_HK ) AS a WHERE a. rank = 1 ), records_to_insert AS ( SELECT DISTINCT stage.CUSTOMER_HK, stage.HASH_DIFF, stage.AMOUNT, stage.CREATED_AT, stage.LOADED_AT, stage.RECORD_SOURCE FROM source_data AS stage LEFT JOIN latest_records ON latest_records.CUSTOMER_HK = stage.CUSTOMER_HK AND latest_records.HASH_DIFF = stage.HASH_DIFF WHERE latest_records.HASH_DIFF IS NULL ) SELECT * FROM records_to_insert このSQLでは、ソースモデル(ハッシュキーを含むモデル)とSatelliteテーブルから条件を付与して抽出したテーブルを結合して、新たに追加するレコードを作成しています。 with句の2番目に定義されている最新レコード(latest_records)を見るとRankを使用して最新のレコードを抽出しています。 Satelliteテーブルに追加されるレコードは、ソースデータと最新レコードを結合し、ソースデータにだけ存在するレコードを抽出しています。 (where句での latest_records.HASH_DIFF IS NULL でソースデータにだけ存在するレコードを取ろうとしています。) 今回例として取り上げたような、同じハッシュキーに対して、異なるHASH_DIFFが追加されるようなテーブルの場合、Satelliteマクロは予期した動作をしないことがあります。 対応策としては、最初に述べたように、Satelliteテーブルが参照するモデルには最新分しか含まないようにすることです。中間層に手を加えたくない場合は、SQLでSatelliteテーブルに存在しないレコードのみを抽出するようなSQLを書きます。 select distinct customer_hk, hash_diff, amount, created_at, loaded_at, record_source from hs_order_history as stg where customer_hk is not null {% if is_incremental() %} and not exists ( select 1 from ( select customer_hk, hash_diff from {{ this }} as cur where stg.customer_hk = cur.customer_hk and stg.hash_diff = cur.hash_diff ) ) {% endif %} RawVault層までの実装にautomate_dvを使った感想など 1つのビジネスキーに対して、ログを溜め続けるようなデータソースがある場合、Satelliteマクロを使用する際に生成されるSQLを見ると、余計なレコードが生成されてしまうことがあります。そのため、データソースによってはマクロの使用の有無や参照モデルの修正など、使い分けや手を加える必要があり、少し面倒に感じました。 マクロを使用することで、キーのハッシュ化など、SQLで記述する場合には多くのコード量が必要な部分をマクロで置き換えられるのはすごく便利に感じました。 予期しない動作が発生した場合、コンパイルされたSQLを見ることで原因を理解することはできますが、automate_dvの実装を見るとマクロ内でマクロを呼び出しているためautomate_dvでの仕組みを理解するのには時間がかかる印象でした。 データソース側で物理削除されている場合、Satelliteではレコードが削除されたかどうかが判断できないので、StatusTrackingSatelliteが欲しくなる場面がありましたが、現在(2023年6月時点)はautomate_dvにマクロはまだ実装されていないのでSQLを直接書く必要がありました。(EffectiveSatelliteのマクロはあるのでもしかしたらそちらで対応できるかもしれません) まとめと次回予告 今回の記事ではDataVaultモデリングの中心となるHub/Link/Satelliteを生成するためのdbtのパッケージの1つであるautomate_dvについて紹介させていただきました。 automate_dvはSQLだけで、Hub/Link/Satelliteの生成クエリを実装するのに比べて、非常に少ない記述量で各種テーブルの実装を行うことができます。 当たり前ですが、マクロによって生成されるSQLがどんなことをしているのかを理解するのが、使いこなす上で重要になってくると思います。 次回は、BusinessVaultや今回の記事で名前だけ出てきているEffectiveSatelliteやStatusTrackingSatelliteの紹介や、ビジネスキーがNullだった場合の対応方法などについて紹介させていただければと思います。 参考資料 automate_dvドキュメントページ 記事を書き始めた時はdbtvaultでしたが、気づいたらautomate_dvに変わってました... ↩
はじめに アプリケーションの構成 Electron Vue.js TypeScript 新規アプリケーションのハンズオン 事前準備 Vue CLIの導入 Vueプロジェクトの作成 Electronの組み込み アプリケーションの起動 preloadスクリプトの作成 メインプロセスへのipcMainモジュールの追加 preloadスクリプトの作成 住所検索画面の作成 終わりに はじめに こんにちは、開発本部第2開発部、Webフロント第2グループでフロントエンド開発をしている伊原です。 私が開発を担当している Safie Entrance2 にて、認証に用いる顔認証端末のセットアップができるアプリケーションが欲しい、という要望が上がりました。 そこでVue3とElectronを利用して簡素なアプリケーションを作成しましたので、本記事ではVue3+Electronのアプリケーション構築方法のハンズオンを記載します。 当時は状態管理に Pine を利用していましたが、今回のセットアップでは状態管理まではせず、アプリケーションの基本的な部分までをセットアップします。 アプリケーションの構成 本記事執筆時点での情報をもとに記載しているため、フレームワークのアップデートによりセットアップが異なる可能性があります。その場合は各技術で紹介している公式ドキュメントをご確認ください。 アプリケーションに採用した各技術について簡単に説明します。 Electron 次の説明は公式サイトより引用です。 Electron は、JavaScript、HTML、CSS によるデスクトップアプリケーションを構築するフレームワークです。 Electron は Chromium と Node.js をバイナリに組み込むことで、単一の JavaScript コードベースを維持しつつ、ネイテイブ開発経験無しでも Windows、macOS、Linux で動作するクロスプラットフォームアプリを作成できます。 https://www.electronjs.org/ja/docs/latest Electronを利用することで、Webフロントエンドで利用されている技術をそのまま利用してデスクトップアプリケーションを作成する事ができます。 今回はVueフレームワーク+TypeScriptを利用します。 Electronの特徴として、以下2つのプロセスで動作しており、それぞれ役割が異なっています。 メインプロセス  アプリケーション全体の挙動の制御をする。Node.jsのAPIを利用できる。 レンダラープロセス  ウェブコンテンツをレンダリングする。 この2つのプロセスはお互いに独立しており、レンダラープロセスからNode.jsのAPIを直接利用してローカルにログを出力したりする事はできないようになっています。 これはセキュリティ上の理由からで、Webサイトの画面上からローカルのあらゆるものが操作可能、というのは問題がありますよね。 また、レンダラープロセスでAPIを呼び出す場合、API提供側がCORSを有効にしている場合、CORSエラーが発生します。 これを回避するため、メインプロセス側でAPI呼び出し後、結果をレンダラープロセスに渡してあげる必要があります。 メインプロセスでレンダラープロセスの通信結果を受け取りたい場合には、プロセス間通信と呼ばれる方式によってデータをやりとりします。 詳細は割愛しますが、後ほどのハンズオンで説明するpreloadスクリプトを利用して通信します。 各プロセスの詳細な説明は次の公式サイトをご確認ください。 https://www.electronjs.org/ja/docs/latest/tutorial/process-model https://www.electronjs.org/ja/docs/latest/tutorial/tutorial-preload Vue.js Web ユーザーインターフェース構築のための、親しみやすく、パフォーマンスと汎用性の高いフレームワークです。 今回は、公開されている最新バージョンの Vue3 を利用します。 TypeScript 有名な型付け可能なaltJSです。 Electron、 VueともにTypeScriptをサポートしているため、JavaScriptではなくTypeScriptを利用します。 新規アプリケーションのハンズオン それでは、これまで紹介した技術を使ってアプリケーションの雛形を作ってみたいと思います。 事前準備 事前に以下の環境を構築しておきます。 Node.js: v16.20 npm: 8.19.4 Electron+Vueの開発では、セットアップを簡単にするVue CLI Plugin Electron Builderというプラグインが提供されています。そちらを利用してVue Cliにてセットアップを進めていきます。 Vue CLIの導入 アプリケーションのセットアップのため、Vue-CLIを導入します。 Vue CLI Plugin Electron Builderでは執筆時点では4が推奨されていますので、4.Xの最新版であるv4.5.19をインストールします。 $ npm install @vue/cli@4.5.19 Vueプロジェクトの作成 Vue-CLIでプロジェクトのテンプレートを作成します。 $ vue create my-electron-prj 作成時に表示されるオプション選択は、初めのvue3-tsを選択します。 Electronの組み込み Vue CLI Plugin Electron Builderを利用してelectronをプロダクトに組み込みます。 先ほど作成したプロジェクトのディレクトリに移動し、electron-builderを追加します。 $ cd my-electron-prj $ vue add electron-builder 途中の質問には以下のように回答します。 ここで1つ問題があり、インストールされるElectron13.0.0はEOLとなっているため、Node.16に対応したサポート期間中のElectronにアップデートします $ npm install electron@22.0.0 このままビルドするとvue-loaderのエラーで怒られるので、v16をインストールします。 $ npm install vue-loader-v16 アプリケーションの起動 ここまで環境構築が終わったら、実際にアプリケーションを立ち上げてみましょう。 $ npm run electron:serve 以下のような画面でアプリケーションが起動すれば成功です。 ここまででアプリケーションの大枠は作成できました。ひとまずお疲れ様でした。 アプリケーションの雛形は作成できましたが、現状ではメインプロセスとレンダラープロセス間で通信するための仕組みがまだ実装されておらず、メインプロセスでの処理結果を画面側に表示する、というような事はできません。 次以降でメインプロセスとレンダラープロセスを通信させるためのpreloadスクリプトを作成していきます。 preloadスクリプトの作成 メインプロセスとレンダラープロセスのプロセス間通信のため、preloadスクリプトを作成します。 今回は試しに、公開されている 住所検索API を利用して取得した結果を画面上に表示してみようと思います。 メインプロセスへのipcMainモジュールの追加 src/background.tsにメインプロセスからレンラダープロセスへ非同期で通信するためのモジュールであるipcMainモジュールをelectronからインポートします。 import { app , protocol , BrowserWindow , ipcMain } from "electron" ; インポートと共に、住所取得APIの処理を追加します。 追加前に、APIリクエストのためにHTTPSリクエストライブラリとしてaxiosをインストールしておきます。 $ npm install axios zipcode(郵便番号)を引数として住所情報を取得する処理を追加します。 ipcMain.handle ( "searchZipcode" , async ( _event: IpcMainInvokeEvent , zipcode: string ) => { const response = await axios. get( "https://zipcloud.ibsnet.co.jp/api/search" , { params: { zipcode } , } ); return response.data ; } ); preloadスクリプトの作成 レンダラープロセスからメインプロセスで追加したAPIを呼び出し、結果を受け取るためにipcRender, contextBridgeをインポートします。 ipcRenderはメインプロセスの指定のチャネルにイベント・引数を送信し、処理結果を受け取ります。contextBridgeはwindowにAPIを注入し、window[apiKey]にてAPIにアクセスできるようになります。 以下、preload.tsのファイルです。 import { contextBridge , ipcRenderer } from "electron" ; contextBridge.exposeInMainWorld ( "api" , { searchZipcode: ( zipcode: string ) => { return ipcRenderer.invoke ( "searchZipcode" , zipcode ); } , } ); pathをインポートし、作成したpreload.tsのパスをbackground.tsのcreateWindow()関数のBrtowserWindow()をnewしているところのwebPreferencesに追加します。 import path from "path" ; webPreferences: { nodeIntegration: process .env .ELECTRON_NODE_INTEGRATION as unknown as boolean , contextIsolation: ! process .env.ELECTRON_NODE_INTEGRATION , preload: path.join ( __dirname , "preload.js" ), } , これでpreloadスクリプトの追加はOK…と言いたいところですが、このままだと1つ問題があり、ビルド時にpreload.jsがバンドルされません。 対策は次で紹介されている通りで、ルートディレクトリにvue.config.jsを作成し、preloadスクリプトのパスを記述します。 vue-cli-plugin-electron-builder/docs/guide/guide.md at master · nklayman/vue-cli-plugin-electron-builder · GitHub module . exports = { pluginOptions: { electronBuilder: { preload: "src/preload.ts" , } , } , } ; 住所検索画面の作成 これでAPIを呼び出す準備はできたので、住所検索画面を作成していきます。 簡単な動作確認が目的なので、テンプレートを作成した際のviews/About.vueを差し替えて作成してしまいます。 < template > < div class= "search-zipcode" > < div > < input v-model = "zipcode" / > < button :disabled = "isFetching" @click = "onSearchZipcode" > 検索 < /button > < /div > < div > < span v- if= "isFetching" > 取得中 < /span > < span v- else> 住所情報: {{ result }} < /span > < /div > < /div > < /template > < script lang = "ts" > import { defineComponent , ref } from "vue" ; export default defineComponent ( { name: "SearchZipcode" , setup () { const zipcode = ref ( "" ); const result = ref ( "" ); const isFetching = ref ( false ); const onSearchZipcode = async () => { try { isFetching.value = true ; const response: { message: string | null ; results: [ { address1: string ; address2: string ; address3: string ; kana1: string ; kana2: string ; kana3: string ; prefcode: string ; zipcode: string ; } ] ; } = await ( window as any ) .api.searchZipcode ( "4340016" ); const { address1 , address2 , address3 } = response.results [ 0 ] ; result.value = address1 + address2 + address3 ; } catch ( error ) { console .log ( error ); } finally { isFetching.value = false ; } } ; return { onSearchZipcode , zipcode , result , isFetching } ; } , } ); < /script > 郵便番号の入力フォームと検索ボタン、それと検索結果表示用の欄のみがあるシンプルなページです。windowインターフェースに追加されたsearchZipcodeに入力した郵便番号を渡し、受け取った結果を結合して住所情報として表示します。 試しに、セーフィー株式会社の本社郵便番号「141-0033」を入力して検索してみます。 住所情報に想定通りの情報が表示されています。これで、メインプロセスのAPI呼び出しとプロセス間通信が問題なく動作している事が確認できました。 ここまでのハンズオンでアプリケーションの雛形作成、API呼び出しからのプロセス間通信が実現できました。後は同様に作っていただければ、Electronのアプリケーションが作成できるはずです。 終わりに 本記事ではVue3 + TypeScript + Electronでのアプリケーションの開発方法の基本をご説明しました。皆さんも本記事のハンズオンを活用して、Electronのアプリケーションを作ってみてください!
こんにちは、セーフィーでエンジニアをしております伊林です。 今回は、Safieでリモートワークツールとして利用している Gather. Town とその活用方法を紹介していきます。 Gather. Townとは 導入のきっかけ SafieでのGatherの利用方法 常時接続して適宜相談する 緊急対策室の導入 休憩所を設ける   その他、導入して良かったこと 別チームの人の様子がわかるようになった、交流する機会ができた オフィスワークとリモートワークを似た感覚でできるようになった まとめ Gather. Townとは Gatherの様子   Gatherは、リモートオフィスツールの一種で、カスタマイズ可能な2次元のMap上でリモートメンバーと会話やチャットを行うことができます。 Safieでは、数年前からエンジニア組織内で活用しています。いくらか知見がたまってきたこともあり、紹介します。 導入のきっかけ 私は以前、Safieの機械学習エンジニアとして働いていたのですが、数年前にサーバーサイドエンジニアのチームへ異動を行いました。 当時は機械学習のアルゴリズム開発や、実地評価用の小規模なシステム開発などを担当していました。ただ、作成したアルゴリズムを製品に落とし込むのためには課題も多く、特にサービス開発の経験がたりないと感じたため、一度サーバーサイドエンジニアとしてサービス開発の経験を積みたいと思ったことが理由です。   異動したのはちょうどコロナの全盛期であり、多くの人がリモートで働いていました。 これにより、以下のような問題を感じることがありました。 リモートだと気楽に通話しづらい(気がする) フルリモートのメンバーと交流する機会がなくなる(気がする) 簡単な内容の相談であれば、チャットによる相談で済む場合が多いですが、相談内容が複雑な場合など、音声で説明した方が効率的な場合があると思います。 特にチームへ入りたての状況では、プロジェクトやチームに関する背景知識が欠けていることからコミュニケーションミスが発生しやすく、通話でやり取りした方が良い場合が多いと思います。 しかし、リモートの場合、相手の状態が把握しづらいこともあり、オフラインのように気楽に通話をお願いしづらく、つい二の足を踏んでしまうということがありました。 また、フルリモートだと昼食を一緒に食べるような機会は減るので、オフラインと比較すると交流の機会が減ってしまうと思います。   一方、フルリモートで働くことに対する課題がないか聞き込みを行ったところ、以下のような課題が見つかりました。 トラブルが起きた際に気づかない、気づくのに遅れる、特定のメンバーだけで情報共有されてしまう場合がある オフィスで働いている場合、”なにか”(サーバに問題が発生するなどの緊急事態)が起きている場合に、周囲の雰囲気で気付けることがありますが、フルリモートの場合はSlackのスレッドを確認する必要がありました。これにより気づくのが遅れたり、そもそもチャットを見逃してしまうというようなこともあったようです。   以上のような問題意識から、自チーム内でGatherを試験的に導入してみたことがきっかけでGatherを使い始めました。最近では、他チームでも利用してみたいという声が多かったこともあり、部署全体で利用しています。 SafieでのGatherの利用方法 常時接続して適宜相談する Google Meetのように通話が必要な場合にのみ接続するのではなく、基本的に業務中は常時Gatherに接続するようにしています。 また、各自ステータスを設定することで、話しかけていいかどうかを表明することにしています。これにより、相手に話しかけていいかどうか迷うことを少なくしています。 話しかけていい状態 話しかけて欲しくない状態 緊急対策室の導入 「トラブルが起きた際に気づかない、気づくのに遅れる、特定のメンバーだけで情報共有されてしまう場合がある」を解決するために導入しました。   障害対応を行うための部屋をGatherのMAP上に設けており、障害対応を行う際はここに集まって対応するようにしています。これにより、Gatherの画面を見れば何かの障害に対応していることが一目でわかるようになりました。   完全に音声通話のみで障害対応を行うと、作業履歴が残らなくて後で困るなどの問題もあるので、基本的にSlackによるテキスト投稿と併用しています。が、トラブルに気づく速度は導入前よりも改善された気がしています。 緊急対策室 休憩所を設ける 「フルリモートのメンバーと交流する機会がなくなる(気がする)」を解決するために、導入しました。 フルリモートの場合、休憩時間を一人で過ごすことが多く、職場の人と話す機会は減る傾向にあると思います。 そこで、MAP上に明示的に休憩所を設けることにしました。仕事の相談や雑談など、気が向いた時に誰かと話す場所として利用しています。   エンジニアの勉強会 以前から勉強会は開催されており、エンジニアの技術交流の場として利用されていました。 Safieには、AI、サーバー、フロント、モバイル、デバイスなど、さまざまなエンジニアが在籍しており、各チームで日々、さまざまな勉強会が開かれています。 これ自体はとてもいい取り組みだと思っているのですが、同じチームの人のみが参加しているものが多い印象で、別チームの勉強会であっても気軽に参加できるようにしたいと思っていました。 Gatherで勉強会を開催することで、あまり明るくない分野の勉強会でも気軽に参加できるようになるのではないかと思っています。   その他、導入して良かったこと 別チームの人の様子がわかるようになった、交流する機会ができた 導入前は、フルリモートの場合、自分が所属するチーム *1 以外の様子が見えづらいということがありました。 Gatherを導入してからは、ある程度誰と誰が会話している、会議しているなど別チームの動きがなんとなくわかるようになりました。   オフィスワークとリモートワークを似た感覚でできるようになった Gatherを導入する前は、 オフィスは、 会社の人と交流したり、複雑内容の相談をする場所 リモートワークは、仕事を家に持ち帰って作業すること という感覚でした。 Gatherを導入してからは、リモートで働く場合でもオフラインとある程度似たような感覚で仕事できるようになりました。   まとめ 今回は、SafieにおけるGatherの活用方法を紹介しました。ツール活用の参考なれば嬉しいです。     ja.gather.town *1 : Safieでは、主に5人程度の規模のチームで開発を行うことが多いです。
インフラグループ所属の佐伯です。 2023年4月20日、21日に開催された AWS Summit Tokyo で「18 万台のカメラが接続する Safie のモダナイゼーションへの取り組み」というタイトルで登壇してきました。 登壇資料 概要 補足 伝えたかったこと 登壇を終えて 登壇資料 概要 サービスが成長するにつれてトイルが増えつつある状況のなか、トイル削減のためにチームで取り組んだ改善内容についていくつかピックアップし内容と工夫した点などについて紹介させていただきました。 補足 P31〜36 の「Amazon RDS のデータ共有に AWS Lake Formation を採用」については全てを紹介すると時間が足りない可能性があったため、内容のほとんどを省略していますが、内容は去年の Safie Engineers' Blog! Advent Calendar に投稿した以下記事となります。 Step Functions と Lake Formation を使った異なる AWS アカウントへの Aurora のデータ共有 伝えたかったこと トイルはサービスの成長に伴い増える性質のものですが、サービスローンチ時の構成を維持するのみではトイルは増え続けてしまう可能性があるので、塩漬けにせず日々改善しましょう!というのが伝えたかったことです。 登壇を終えて 登壇慣れしていないにも関わらず、練習不足もあり本番ではかなり緊張してしまい噛み噛みだった、且つ作成したスライドを読むのに必死だったと記憶しており(動画も恥ずかしくてまともに見れていない)、反省しています...。この規模のイベントで登壇する機会がまたあるかはわかりませんが次回はがんばります。
はじめに Doxygen実行用コンテナの作成 AWS S3を使った静的ウェブサイトのホスティング GitHub Actions むすび はじめに こんにちは。AI Vision グループでリードエンジニアを務めている橋本です。この記事では、コードドキュメントの生成を GitHub Actions と Doxygen で自動化することで、手間をかけずにドキュメントを作成する仕組みを紹介したいと思います。以下のスクリーンショットは作成するドキュメントのイメージです。今回はC++で実装されたアプリケーションを対象に実施していますが、その他の言語であってもこの手法が参考になると思います。 みなさんは、ドキュメントが整備されておらず、コードの理解や、チームメンバーとのコミュニケーションに難しさを感じたことはありませんか?例えば、新たにプロジェクトに配属され、適切なドキュメントがないために既存の実装を解読しなくてはならず、キャッチアップに骨が折れる思いをしたなど。 そんなときに、ドキュメントが整備されていると、コードの理解がとても容易になります。実装仕様書などのドキュメントを別途作成することもできますが、コードは更新したのにドキュメントの更新を忘れてしまい、実装とドキュメントが乖離するいった問題が発生しがちです。また、毎回手作業でドキュメントを更新するのは面倒ですね。 そこで、ソースコードから自動的にドキュメントを生成するDoxygenというツールを使うと効率的です。Doxygen は、C++、Java、Python等、多数の言語をサポートし、コード内のコメントを抽出して、構造化された形式で参照ドキュメントを生成することができます。ドキュメントには、クラスやメソッドの使用方法、インターフェース、クラスの継承関係、ヘッダファイルの依存関係、コールグラフなどを含めることができます。 ソースコードから自動的にドキュメントを生成すると、最新のコードと対応したドキュメントが常に維持できるのでとても便利です。コードとドキュメント(docstring)が一体になっているので、コードレビューと同時にドキュメントがチェックできるという利点もあります。 今回の自動化で処理される内容は、次の通りです。 ブランチがmainにマージされたことをトリガーにして GitHub Actions workflow を起動する Doxygen を使ってソースからHTMLを生成する 生成した HTML を AWS S3 にアップロードし、S3の静的webサイトのホスティングを利用して公開する 次のセクションから、構築手順を具体的に説明していきます。 Doxygen実行用コンテナの作成 このセクションでは、Doxygen を実行してHTMLを生成するDockerコンテナの作成について説明します。Dockerfile は こちら から閲覧できます。 はじめに、必要なaptパッケージをインストールします。Doxygenでグラフを描画するために、graphviz パッケージが必要です。 # Install apt packages RUN apt-get update && apt-get install -y --no-install-recommends \ graphviz wget git make \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* 次に、Doxygen をインストールします。今回は、Ubuntu 22.04 LTS のパッケージリポジトリでインストール可能な最新バージョン 1.9.1 ではなく、より新しいバージョン 1.9.3 をGitHubから直接取得しています。Doxygen 1.9.1 では、一部の可視化(数式、ツリー表示)が非対応となっているので、ここでは1.9.3を選択しています。 # Install Doxygen 1.9.3 RUN wget -q --no-check-certificate \ https://github.com/doxygen/doxygen/releases/download/Release_1_9_3/doxygen-1. 9 . 3 .linux.bin.tar.gz \ && tar -zxvf doxygen-1. 9 . 3 .linux.bin.tar.gz \ && cd doxygen-1. 9 . 3 && make install && cd .. \ && rm -r doxygen-1. 9 . 3 .linux.bin.tar.gz doxygen-1. 9 . 3 最後に、カスタム CSS をクローンします。カスタムCSSを適用することで、モダンでオシャレなドキュメントを生成することが可能です。ここでは、 doxygen-awesome-css を利用します。 # Clone CSS for Doxygen RUN git config --global http.sslVerify false \ && git clone https://github.com/jothepro/doxygen-awesome-css.git Dockerfile を使用してイメージをビルドし、作成したイメージを ECR にプッシュします。 AWS S3を使った静的ウェブサイトのホスティング このセクションでは、生成したHTMLドキュメントをS3にホストする方法について説明します。 S3バケットを新しく作成し、静的ウェブサイトホスティングの設定に移動し、次のように設定を行います。記載されていない項目についてはデフォルトのままで問題ありません。 設定項目 値 静的ウェブサイトホスティング 有効にする ホスティングタイプ 静的ウェブサイトをホストする インデックスドキュメント index.html 次に、アクセス許可の設定をします。今回は、社内ネットワークからのみ閲覧できるようにするため、特定のIPアドレスからのみ明示的にアクセスを許可し、それ以外のIPアドレスからは暗黙に拒否するようにします。以下のJSONをバケットポリシーとして適用します。 { " Version ": " 2012-10-17 ", " Id ": " AccessToDoxygenBucket ", " Statement ": [ { " Sid ": " AllowIP ", " Effect ": " Allow ", " Principal ": " * ", " Action ": " s3:* ", " Resource ": [ " arn:aws:s3:::your-doxygen-bucket ", " arn:aws:s3:::your-doxygen-bucket/* " ] , " Condition ": { " IpAddress ": { " aws:SourceIp ": " xxx.xxx.xxx.xxx/xx " } } } ] } バケットポリシーの設定が完了したら、ブロックパブリックアクセスの設定に移動し、「パブリックアクセスをすべてブロック」のチェックを外すことで、バケットへのパブリックアクセスを許可します。これで、webサイトを公開するための設定は完了です。 次に、GitHub Actions が ECRからDockerイメージをpullできるように、以下のようにポリシーを作成します。 { " Version ": " 2012-10-17 ", " Statement ": [ { " Sid ": " AllowECRAuthorization ", " Effect ": " Allow ", " Action ": [ " ecr:GetAuthorizationToken " ] , " Resource ": " * " } , { " Sid ": " AllowPullFromECR ", " Effect ": " Allow ", " Action ": " ecr:* ", " Resource ": " arn:aws:ecr:your-region-name:xxxxxxxxxxxx:repository/your-repos-name " } ] } 加えて、GitHub Actions が S3にリソースをアップロードするためのポリシーも作成します。 { " Version ": " 2012-10-17 ", " Statement ": [ { " Sid ": " AllowS3Sync ", " Effect ": " Allow ", " Action ": [ " s3:PutObject ", " s3:GetObject ", " s3:ListBucket ", " s3:DeleteObject ", " s3:GetBucketLocation " ] , " Resource ": [ " arn:aws:s3:::your-doxygen-bucket/* ", " arn:aws:s3:::your-doxygen-bucket " ] } ] } 上記で作成した2つのポリシーをIAMロールにアタッチします。IAMロールのARNを次のセクションで説明する GitHub Actions workflow で使用します。 GitHub Actions このセクションでは、GitHub Actions の処理について説明します。ここで紹介するスクリプトは こちら から閲覧できます。 まず、GitHubのworkflow が実行される条件を設定します。今回は、featureブランチがmain ブランチにマージされたときに実行したいため、push をイベントトリガーに設定します。また、featureブランチの変更を一時的に確認することをを想定して、手動でイベントをトリガーできるように workflow_dispatch も設定します。 on : push : branches : - main workflow_dispatch : 続いて、ジョブの定義を行います。OpenID Connect トークンを発行し、それを用いてAWSの認証を行うため、id-token スコープには write 権限が必要です。また、contents スコープについては、read 権限があれば十分です。 jobs : doxygen : permissions : id-token : write contents : read ここからは、steps の内容を順番に見ていきましょう。リポジトリのチェックアウト、AWSとECRの認証を済ませ、Docker イメージのpull や S3へのアップロードを行う準備をします。 steps : - name : Checkout uses : actions/checkout@v3 - name : Configure AWS credentials uses : aws-actions/configure-aws-credentials@v2 with : aws-region : your-aws-region role-to-assume : ${{ secrets.YOUR_ARN_FOR_DOXYGEN_ROLE }} role-session-name : PublishDocsWithDoxygen - name : Login to ECR id : login-ecr uses : aws-actions/amazon-ecr-login@v1 次の、Pull image ステップでは、ECRから Docker イメージを取得します。このときのイメージ名と場所は環境変数で定義されています。取得したイメージ名を後続の処理で利用するため $GITHUB_OUTPUT ファイルに追加しておきます。 - name : Pull image id : pull_image env : ECR_REGISTRY : ${{ steps.login-ecr.outputs.registry }} ECR_REPOSITORY : $YOUR_ECR_REPOSITORY_NAME IMAGE_NAME : $ECR_REGISTRY/$ECR_REPOSITORY:latest run : | docker pull ${{ env.IMAGE_NAME }} echo "IMAGE_NAME=${{ env.IMAGE_NAME }}" >> $GITHUB_OUTPUT その次の、Build document ステップでは、Doxygenを実行してHTMLを生成します。 - name : Pull image id : pull_image env : ECR_REGISTRY : ${{ steps.login-ecr.outputs.registry }} ECR_REPOSITORY : $YOUR_ECR_REPOSITORY_NAME IMAGE_NAME : $ECR_REGISTRY/$ECR_REPOSITORY:latest run : | docker pull ${{ env.IMAGE_NAME }} echo "IMAGE_NAME=${{ env.IMAGE_NAME }}" >> $GITHUB_OUTPUT ここでは、カレントディレクトリが your-repo-name となっているため、一つ上のディレクトリに移動し、ディレクトリ構造を次のように整形します。リポジトリで管理しているDoxyfile(Doxygen設定ファイル)とリポジトリのロゴ(.png)をリポジトリと並列になるように移動します。 . ├── repo_logo.png # リポジトリのロゴ ├── Doxyfile # Doxygen設定ファイル └── your-repo-name # チェックアウトしたリポジトリ ホストのディレクトリ構造が準備ができたら、Dockerコンテナを起動して doxygenを実行します。カレントディレクトリをコンテナにマウントすると、コンテナのディレクトリ構造は次のようになります。Doxygenを実行すると、HTMLが /workspace/data/html 配下に生成されるようにDoxyfileで出力先を設定しています。 /workspace ├── data │ ├── repo_logo.png # リポジトリのロゴ │ ├── Doxyfile # Doxygenの設定ファイル │ └── your-repo-name # チェックアウトしたリポジトリ └── doxygen-awesome-css # カスタムCSS 最後のステップとして、生成したドキュメントをS3にアップロードします。 - name : Push document run : | cd .. aws s3 sync --delete --region ap-northeast-1 ./html s3://repo-document/ むすび この記事では、GitHub Actions と Doxygen を活用し、コードドキュメントを自動生成する方法を詳細に解説しました。使用したスクリプトは、 こちら で公開していますのでご活用ください。また、出力されるドキュメントの例としては、 Doxygen Awesome の Examples が参考になると思います。 紹介した方法によって、コードの理解が容易になるほか、ドキュメンテーションの維持管理も効率化できるため、開発チームがコーディングにより多くの時間を割くことが可能になり、製品開発のスピードと品質が向上することを期待しています。今後も、さまざまなツールや技術を活用した結果を紹介できればと思います。
サーバーサイドエンジニアの松木 ( @tatsuma_matsuki ) です。 セーフィーではいくつかのサービスでFastAPIを使った開発を行っています。FastAPIでは、Pydanticというライブラリを使ってリクエスト・レスポンスのモデルのバリデーションなどを実装することができます。このPydanticというライブラリですが、近いうちにメジャーバージョンアップのリリースが予定されており、これによりモデルのバリデーション処理が高速化されることがアナウンスされています! 以下のページによると、Pydantic v2はv1に比べて4倍~50倍の性能向上があると書かれているので、これは期待してしまいます。 https://docs.pydantic.dev/latest/blog/pydantic-v2/#performance そして、Pydanticを利用するFastAPIでも2023/6/21に 0.100.0-beta1 がリリースされ、このリリースでは、Pydantic v2を利用することができるようになっています!この記事の執筆時点では、Pydantic・FastAPIともにまだβ版ではありますが、試しに FastAPI + Pydantic v1 と FastAPI + Pydantic v2の性能を比較 してみましたので、その内容をこの記事で共有したいと思います。 Pydantic BaseModel Pydantic v2でモデルのバリデーションが高速化 環境セットアップ アプリケーションの実装 Locustで性能測定 性能測定の結果 まとめ Pydantic BaseModel PydanticのBaseModelについて簡単に説明します。BaseModelを継承したクラスでデータのモデルを定義することで、クラス変数に定義した型ヒントとインスタンス作成時に指定された値の型を比較し、正しい型なのかどうかをバリデーションしてくれます。型ヒント以外にも、独自のバリデーションルールを定義することもできます。以下がPydantic BaseModelを利用したバリデーションの例です。 from pydantic import BaseModel class Service (BaseModel): service_id: int name: str # 独自のバリデーションルール @ validator ( "name" ) def name_has_service (cls, v): if "Service" not in v: raise ValueError ( "name must have 'Service'" ) return v Service(service_id= 1 , name= "Service A" ) # > OK Service(service_id= "a" , name= "Service A" ) # > ValidationError Service(service_id= 1 , name= "A" ) # > ValidationError FastAPIを使っていると、必然的にこのPydantic BaseModelを使うことになるので、Pydantic は FastAPIで使うもの、というイメージを持たれるかもしれないですが、(私自身初めはそうでした)Pydantic の BaseModelは、FastAPI以外でも使うと結構便利です。 バリデーション以外にも、標準出力やdict型と相互変換、JSONシリアライズなどが簡単にできる他、インスタンス同士の比較もサクッとできてしまいます(単体テストなどを書く際に便利)。 from pydantic import BaseModel class Service (BaseModel): service_id: int name: str service_1 = Service(service_id= 1 , name= "Service A" ) print (service_1) # > service_id=1 name='Service A' print (service_1.dict()) # > {'service_id': 1, 'name': 'Service A'} print (service_1.json()) # > {"service_id": 1, "name": "Service A"} service_2 = Service(service_id= 1 , name= "Service A" ) print (service_1 == service_2) # > True service_3 = Service.parse_obj({ "service_id" : 1 , "name" : "Service A" }) print (service_1 == service_3) # > True Pydantic v2でモデルのバリデーションが高速化 Pydantic v2では、バリデーション部分の実装をRustで書き直し、 pydantic-core という別のパッケージに分離されています。これにより、v1と比較して、4~50倍バリデーション処理が高速化されたと Pydantic公式ページ で言及されています。 この記事では、このPydantic v1 → v2の性能向上がFastAPIで実装したAPIの応答時間にどの程度影響を与えるのかを調べるために、簡単なアプリケーション(APIサーバー)を実装して性能を測ってみます。 環境セットアップ 実際に簡単なAPIを実装して性能を比較するために、まずは環境のセットアップです。Python 3.10.6の環境で、まずはfastapiの最新バージョンをインストールします。 $ poetry add fastapi pydantic Using version ^0.98.0 for fastapi Using version ^1.10.9 for pydantic Updating dependencies Resolving dependencies... (0.2s) Package operations: 8 installs, 0 updates, 0 removals • Installing exceptiongroup (1.1.1) • Installing idna (3.4) • Installing sniffio (1.3.0) • Installing anyio (3.7.0) • Installing typing-extensions (4.6.3) • Installing pydantic (1.10.9) • Installing starlette (0.27.0) • Installing fastapi (0.98.0) Writing lock file 続いて、別の環境でpre-releaseのfastapiおよびpydanticをインストールします。pydanticが2.0b3、fastapiが0.100.0b1となっています。また、pydantic-coreというパッケージが増えていることも分かります。 $ poetry add fastapi pydantic --allow-prereleases Using version ^0.100.0b1 for fastapi Using version ^2.0b3 for pydantic Updating dependencies Resolving dependencies... (1.3s) Package operations: 10 installs, 0 updates, 0 removals • Installing exceptiongroup (1.1.1) • Installing idna (3.4) • Installing sniffio (1.3.0) • Installing typing-extensions (4.6.3) • Installing annotated-types (0.5.0) • Installing anyio (3.7.0) • Installing pydantic-core (0.39.0) • Installing pydantic (2.0b3) • Installing starlette (0.27.0) • Installing fastapi (0.100.0b1) Writing lock file アプリケーションの実装 今回は単純にあるオブジェクトのリストを返すようなAPIをアプリケーションとして簡単に実装します。今回Pydantic v2で性能向上が期待できるのはBaseModelによる変数のバリデーションの部分ですので、複雑な情報を持ったオブジェクトのリストを返すようなAPIが一番効果が大きいのではないかと予想をしています。 以下のような Service というモデルのリストを返すAPIを実装しました。conintを使ったり、datetimeを使ったりして少し複雑にしてみましたが、比較的シンプルな仕様のAPIかと思います。 Pydantic v1用の実装は以下の通りです。 from datetime import datetime from fastapi import FastAPI, Query from pydantic import BaseModel, Field, conint app = FastAPI() class Service (BaseModel): service_id: int = Field(..., title= "Service ID" ) name: str = Field(..., title= "Name of the service" ) version_number: conint(ge= 1 ) = Field(..., title= "Version of the service" ) version_name: str = Field(..., title= "Version name of the service" ) created_on: datetime = Field(..., title= "Date of the service was created" ) updated_on: datetime = Field(..., title= "Date of the service was updated" ) class ServiceList (BaseModel): offset: int = Field(..., title= "Offset of the list" ) count: int = Field(..., title= "Length of the list" ) total: int = Field(..., title= "Total count of the available services" ) services: list [Service] = Field(..., title= "List of the services" ) @ app.get ( "/services" ) async def get_services ( offset: int = Query(default= 0 , ge= 0 , title= "Offset of the list" ), ) -> ServiceList: services = [] for i in range ( 1 , 20 ): services.append( { "service_id" : i, "name" : f "service-{i}" , "version_number" : i, "version_name" : f "v{i}" , "created_on" : datetime.now(), "updated_on" : datetime.now(), } ) return ServiceList( offset=offset, count= len (services), total= len (services), services=[Service.parse_obj(s) for s in services], ) ただサーバー側でリストを適当に作って ServiceList のモデルとして返しているだけですので、負荷としてはほとんどがモデルのバリデーション部分だけなのではないかと思っています。 次のコードがPydantic v2用のコードです。ほとんど同じですが、Pydantic v2では、BaseModelのparse_obj() のメソッドの名前が変わっているため、そこだけ少し修正しています。 from datetime import datetime from fastapi import FastAPI, Query from pydantic import BaseModel, Field, conint app = FastAPI() class Service (BaseModel): service_id: int = Field(..., title= "Service ID" ) name: str = Field(..., title= "Name of the service" ) version_number: conint(ge= 1 ) = Field(..., title= "Version of the service" ) version_name: str = Field(..., title= "Version name of the service" ) created_on: datetime = Field(..., title= "Date of the service was created" ) updated_on: datetime = Field(..., title= "Date of the service was updated" ) class ServiceList (BaseModel): offset: int = Field(..., title= "Offset of the list" ) count: int = Field(..., title= "Length of the list" ) total: int = Field(..., title= "Total count of the available services" ) services: list [Service] = Field(..., title= "List of the services" ) @ app.get ( "/services" ) async def get_services ( offset: int = Query(default= 0 , ge= 0 , title= "Offset of the list" ), ) -> ServiceList: services = [] for i in range ( 1 , 21 ): services.append( { "service_id" : i, "name" : f "service-{i}" , "version_number" : i, "version_name" : f "v{i}" , "created_on" : datetime.now(), "updated_on" : datetime.now(), } ) return ServiceList( offset=offset, count= len (services), total= len (services), services=[Service.model_validate(s) for s in services], # parse_obj()は使えない ) Pydantic v1 → v2はかなり非互換のある変更が含まれており、移行には少なからずコードへの修正も合わせて必要になると思います。PydanticのページにMigration Guideが用意されていますので、詳しくは以下のページを参照ください。 https://docs.pydantic.dev/dev-v2/migration/ Locustで性能測定 さっそく、上で実装した簡易的なアプリケーションをLocustを使って性能測定したいと思います。 Locustのコードは以下のように書きました。(ChatGPTでほぼ同じコードが一瞬で生成できます) # main.py from locust import HttpUser, task, constant_throughput class ServicesList (HttpUser): host = "http://localhost:8001" wait_time = constant_throughput( 1 ) @ task ( 1 ) def get_services (self): with self.client.get( "/services" , catch_response= True ) as response: if response.status_code != 200 : 以下のようにPydantic v1, v2を使ったアプリケーションをそれぞれ起動します。 $ poetry add uvicorn[standard] ... $ poetry run uvicorn main:app --host 0.0.0.0 --reload --port 8000 INFO: Will watch for changes in these directories: ['/tmp/fastapi-0.100.0-beta1'] INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit) INFO: Started reloader process [14005] using WatchFiles INFO: Started server process [14050] INFO: Waiting for application startup. INFO: Application startup complete. Locustのコードは、以下のように実行します。引数でユーザー数を指定することで、リクエスト数をコントロールします。 $ poetry run locust -f main.py --headless --users 10 --spawn-rate 1 --csv=outputs/fastapi_pydantic_v1 -t 1m ... Response time percentiles (approximated) Type Name 50% 66% 75% 80% 90% 95% 98% 99% 99.9% 99.99% 100% # reqs --------|--------------------------------------------------------------------------------|--------|------|------|------|------|------|------|------|------|------|------|------ GET /services 13 16 18 20 27 31 38 41 45 45 45 555 --------|--------------------------------------------------------------------------------|--------|------|------|------|------|------|------|------|------|------|------|------ 性能測定の結果 測定結果です!今回は、FastAPIのサーバーの起動は、ローカルのLaptop PC上で行っています。 以下が測定結果をグラフ化した結果です。横軸はLocust実行時に指定した設定したユーザー数で、縦軸が応答時間(ミリ秒)です。得られた結果を各統計値(中央値、90パーセンタイル値、95パーセンタイル値、99パーセンタイル値)毎にラインにして描いてみました。 性能測定結果比較 結果は一目瞭然ですね!ざっくり言うと、 2~3倍高速化 されていると思います!モデルのバリデーション部分が4~50倍の性能向上があるという話でしたので、今回のようなバリエーション処理以外ほぼ何もない実装だともっと劇的に向上するかも?と思っていましたが、思ったほどではなかったです。 もちろん、今回の結果はあくまで一例であり、実際に商用運用されているアプリケーションではどうなるかやってみないと分かりませんが、見積もりとして2倍以上の改善が見込めるのであれば、多少苦労してでも、Pydantic v2に移行する価値はありそうです! まとめ 今回は、簡単なAPIを実装して応答時間を測定してみましたが、実サービスのAPIでは、データベース等へのアクセスが挟まるので、実際にはその部分が支配的になり、応答時間に対してはあまり効果が出ないかも?とは思っていますが、FastAPIは基本そのようなI/Oを非同期で処理するので、APIサーバーでの負荷は結構下がるのかもしれません。早く実サービスでも試してみたいです! Pydantic v1 → v2へのバージョンアップは非互換のある変更を多く含んでいますので、今後慎重にバージョンアップしていきたいと思います!
こんにちは。 セーフィーで知財のマネージャーをしている渡辺と申します。 セーフィーでは、2022年1月に知財部門を立ち上げ、それと同時に私が入社して知財の体制づくりを進めてきました。 そこから約1年半が経ち、知財の基本的な機能は整ってきて、最近では特許もいくつか登録されてきましたので、今回はそれら特許からセーフィーの技術のトレンドを読み解いてみようと思います。 トレンド① ◆特許7279241 ◆特許7265072 ◆特許7227423/特許7271803 トレンド② ◆特許7159503 さいごに トレンド① では早速、具体的な特許を見ていきましょう! ◆ 特許7279241 この件は、新製品の Safie One に搭載されたAI-Appの1つである「 立ち入り検知 」について狙ったものです。 特許のポイントは、「対象エリアに留まっている人の①数②時間を設定し、①②の条件を満たす場合に、その時のサムネイルを表示する(請求項1)/タイムライン上にオブジェクトを表示する(請求項7)」という点です。 ◆ 特許7265072 この件は、小売店の レジ返金不正 に関する現場の確認作業を効率化するためのものです。 特許のポイントは、「返金処理が行われたタイミングで、レジ前の顧客領域内に人がいなかった場合、タイムライン上にオブジェクトを表示する」という点です。 ◆ 特許7227423 / 特許7271803 この件は、飲食店の デシャップに出された料理 に関する現場の確認作業を効率化するためのものです。 特許のポイントは、「飲食店の所定領域に料理が置かれた場合、タイムライン上に料理の種類に応じたオブジェクトを表示する(特許7227423)/その時の写真を撮影し料理の種類ごとにフォルダ分けして格納する(特許7271803)」という点です。 ・・・もう気付いたかもしれませんが、これらは「タイムライン上にオブジェクトを表示する」という点で共通しています。 そこから見えてくるのは、「撮り溜めた映像の中で、ユーザーが見たい映像にいかに素早く&簡単にアクセスできるか」という技術のトレンドです。 トレンド② ただ、セーフィーの技術はこれだけではありません。 ◆ 特許7159503 この件は、強風や振動等でカメラの設置角度が変わってしまった場合でも、AIモデルを用いて対象を検出できるようにするためのものです。 特許のポイントは、「映像データに基づいてカメラの設置角度を推定し、その設置角度に基づいて検出モデルを生成する」という点です。 その他、実は出願済のものはまだ色々あるのですが・・・未だ公開されていないため、また別の機会にということで(笑) さいごに さて、ご覧いただいて如何だったでしょうか? クラウドカメラと聞くと、きっと多くの人が「特許なんてあまり生れないだろう」と思われるでしょうが、セーフィーではここ約1年半で上記の様な特許が確実に生まれています。 その理由は、クラウドカメラの用途が監視から現場のDXへと進化しつつある中で、セーフィーの社員一人一人が「現場の負」としっかり向き合い、その解決へ向けて「泥臭い試行錯誤」をしているからだと思います。 このプロセス、実は発明が創出されるプロセスそのものなので、私は入社当初から「セーフィーでは新しいものが必ず生まれる筈だ」と感じていました。 知財担当として、その仮説を特許を取ることにより証明し、社員に自信と希望を持ってもらうことが、自分に課された使命の1つだと思ってやってきました。 ちょっと真面目な話になってしまいましたが・・・日々、新しいものが生まれる環境で、熱量を持った社員の方々と一緒に仕事できて、とにかく楽しいです。 今自分がやっている特許の権利化のイメージを料理に例えるなら、良い食材が目の前にあるので、その良さを最大限に引き出すべく、味付けは塩コショウのみで余計なことはせず、火入れは徹底するという感じでしょうか(笑) このブログをご覧の皆様の中で、もし将来セーフィーのエンジニアになる方がいらっしゃいましたら、その時は是非一緒に特許を楽しみましょう! セーフィーでは、エンジニアが特許に関わる際には、なるべく本業である開発の邪魔にならないよう配慮しつつ、一方で特許の楽しみを味わえるような仕掛けもしていますので、ご安心ください。
こんにちは、はじめまして。セーフィー株式会社 業務システムグループの大山です。 業務系システムの開発・運用を行っております。 今回はセーフィーで使用している「DataSpider Cloud」についてお話しさせていただきます。 DataSpider Cloudとは できること 使い方 例 1. データ読み取り(クエリー) 2. CSVファイル書き込み 3. マッピング 4. 完成 最後に DataSpider Cloudとは DataSpiderは複数のシステム同士を「つなぐ」ことに長けているツールです。 異なるシステム同士のデータを連携するには、各システムを理解している専門の技術者が必要でしたが、DataSpider Cloudを使用すると専門的な知識やプログラミング技術がなくても、ポイント&クリックで設定ができます。 開発や運用がしやすい事から、内製化しやすい、というメリットもあります。 できること DataSpiderはとにかく様々なシステムをAPI経由で連携することができます。 (Salesforce、AWS、Google、Microsoft Azure、Microsoft Dynamics365等) クラウドシステム間はもちろん、オンプレミスとクラウドをDataSpider Cloudを経由して連携し、DataSpider内でデータを移行することも可能です。 使い方 例 Salesforceからデータをエクスポートし、CSVファイルに書き出し DataSpider Cloudの設定はデザイナ上で行います。こちらはデザイナ画面です。①のツールパレットからツールを探し、②のキャンバス上へドラッグ&ドロップして追加していきます。 キャンバスには「Start」と「End」がデフォルトで存在し、ツールパレットから追加したツールは、「Start」と「End」の間に配置し、最終的にすべてつなげる必要があります。 1. データ読み取り(クエリー) ツールパレットから「データ読み取り(クエリー)」を選択して、キャンバス上にドラッグします。 ドロップすると、データ読み取り処理に関する詳細設定画面が表示されます。 「接続先」にはデータ読み取り先のSalesforce組織を選択します。 「テーブル名」にはデータを読み取る対象オブジェクトを指定します。 「スキーマの作成」から、データを読み取る対象項目を選択します。 特定条件のデータのみ読み取りたい場合は、「SOQLの作成」から条件を設定します。 設定が完了したら、「完了」をクリックして「データ読み取り(クエリー)」が1つ完成です。 2. CSVファイル書き込み 読み取ったデータをCSVファイルに書き込むため、次はツールパレットから「CSVファイル書き込み」を選択して、キャンバス上にドラッグします。 ドロップすると、データ読み取り処理に関する詳細設定画面が表示されます。 「ファイル」には、CSVファイルの置き場所を指定します。 「列一覧」には、CSVファイルのヘッダーを作成します。「追加」ボタンで列を追加し、「列名」に各列のヘッダー名を入力します。 「1行目に列名を挿入」にチェックを入れます。 ※定期的に同じ処理を行う設定で、同じファイルに行を追加したい場合、「追加書き込み」や「ファイルが存在する場合は列名を挿入しない」にもチェックを入れます。 必要な設定が完了したら「完了」ボタンをクリックし、「CSVファイル書き込み」も完成です。 3. マッピング 「データ読み取り(クエリー)」と「CSVファイル書き込み」を設定しましたが、まだそれぞれ単体で存在しているだけなので、接続作業が必要です。 ツール同士を接続する時も、ドラッグ&ドロップで簡単にできます。 「データ読み取り(クエリー)」を「CSVファイル書き込み」にドラッグ&ドロップすると接続され、間にマッピング用のツールが自動で出現します。 「マッピング」をクリックすると、マッピングの設定画面が開きます。 「入力元」は「データ読み取り(クエリー)」、「出力先」は「CSVファイル書き込み」の情報になります。 「入力元」の「row」配下と、「出力先」の「row」配下が線でつながっていますが、これは「データ読み取り(クエリー)」で読み取る項目と、「CSVファイル書き込み」で書き込むヘッダーが自動マッピングされていることを表します。 ※「CSVファイル書き込み」のヘッダー名を、読み取り項目のAPI名と合わせておくと、自動マッピングされやすくなります。 ※自動マッピングされていない箇所があった場合、手動でのマッピングも可能です。その際は「入力先」から「出力先」の方向にドラッグ&ドロップします。 4. 完成 マッピング内容が問題なければ、最後に「Start」から「データ読み取り(クエリー)」、「CSVファイル書き込み」から「End」の方向につなげると、1つの処理設定が完成しました! 設定した処理は、即時実行はもちろん、「マイトリガー」という機能で実行スケジュールを設定することも可能です。 最後に 今回は非常にシンプルな処理のご案内で恐縮ですが、DataSpider Cloudは本当に様々な処理設定が可能です! 慣れてくると、複数のサービスが絡む複雑な設定もできるようになるので、使えば使うほどおもしろく感じるツールだと思います! ・・・と大きなことを言いましたが、私もまだまだ勉強中です。 セーフィーには、私のようなローコード開発経験者レベルから、高度な開発スキルを持つスペシャリストまで、幅広いエンジニアが在籍しています。 セーフィーではエンジニアを募集しています! 興味を持っていただけたらぜひ採用サイトも覗いてみてください。 safie.co.jp
こんにちは。セーフィーで画像認識エンジニアをやっている木村Y(緑コーダー)です。 セーフィーには競技プログラミング愛好者が複数在籍しており、社内勉強会の一つとして競技プログラミングの勉強会が開催されています! 競技プログラミングとは? 概要 会の内容 最後に 競技プログラミングとは? 競技プログラミングについて簡単に触れておきます。 競技プログラミングとは、決められた時間内で、出題中の与えられた要求を解決するコードを記述する競技です。問題は通常、アルゴリズムやデータ構造の知識をテストするために、数学、グラフ理論、文字列処理、動的計画法などの分野から選ばれます。 競技プログラミングのコンテストの代表的なものとして AtCoder 社が主催するものがあります。この勉強会では、このAtCoderのコンテストの過去問を中心に扱っています。 概要 毎週火曜日の業務終了後に勉強会を行っています。会議室を借りて行っていますが、リモート参加ももちろんOK。 青色コーダーの現主催者から、最近興味を持ち競プロを始めたばかりの方まで、さまざまな習熟度の方たちが参加しております。 また、今年からセーフィーには新卒の1期生が入社しましたが、すでにさっそく何人かの方に参加してもらっています(競プロ文化の浸透を感じます)。去年と比較して参加者が続々と増えてきており、盛り上がりが増してきている実感があります。 回によってまちまちですが、毎回3〜6名の参加者がおり、その内訳もデバイスやフロント、自分のような画像認識エンジニアまで、様々な分野のエンジニアが参加しています(過去には経理部の競プロerの方にも参加していただきました)。 このように競プロの問題を解いてみたい!という方なら、熟練者や初心者に関わらず、またエンジニア・非エンジニアに関わらず、誰でもウェルカムとなっています。 会の内容 基本的な会の流れとしては、AtCoderの過去のBeginer Contestから選んだ1つの回をA問題から順番に解いていくことが多いです(A問題が最も簡単で、B問題、C問題・・と徐々に難しくなってゆくのがAtCoderコンテストの特徴です)。 A問題 D問題 解いている問題について語りながら、雑談もしながら、時には無言で集中モードになりながら、ゆるく解いてゆきます。 参加者の習熟度によって解くスピードはまちまちになりますが、初学者が悩んでいる部分に、熟練者がヒントを出しながら進んでいく光景がよく見られます。さまざまなレベルの参加者で、一緒に考えて楽しんでいこうというスタンスが強いと思います。 このようにして問題を解いてゆき、ちょうど良い頃合い(疲れ具合)を見て、ホワイトボードなどを使って1~2問解説を行って終了となる流れが多いです。「自分はこうやって解いた」という議論も盛り上がりを見せます。 初心者の方にとっては新しい問題にチャレンジする機会や熟練者から教えてもらう良い機会になりますし、すでに問題を解いたことのある熟練者にとっても、(なかなかやる気の起きない)復習の良い機会になり、新しい言語でチャレンジしてみることもできます。問題の解説をすることでアルゴリズムへの理解が改めて深まったり、初心者の解き方を見て新しい視点が得られることもあるかもしれません。 毎回解く問題については決まっているわけではないので、この問題を解いてみたい!という希望に応じてフレキシブルに変更して行っています。また「動的計画法」など、テーマを絞って問題を解いてゆくこともあります。 最後に 様々な種類が存在するエンジニアの中で、競技プログラミングというのは共通言語になりうる数少ない題材の一つだと思っており、他部署のエンジニアとの交流を深めるとても良い機会にもなっていると思います。 おかげさまで参加者が徐々に増えてきており、さらに会を盛り上げるためにも、今後はテーマ別の強化週間を増やしたり、Heuristic Contestも取り上げてみたりなど、新しい試みを取り入れていければと思ったりしています。 熟練度によって非常に差がつきやすいという性質を持つ競技なので、初心者が参加しやすく、かつ熟練者にとって有意義なものにするために工夫してゆきたいです。 競プロ経験者の方を心待ちにしておりますので、興味のある方は採用ページをぜひチェックしてみてください! safie.co.jp
こんにちは。セーフィー株式会社に所属するサーバサイドエンジニアの河津です。 セーフィーにはクラウドカメラやユーザーアカウントを一括管理できる統合環境である「 Safie Manager 」というサービスがあり、主にエンタープライズのお客様にご活用いただいています。 safie.jp エンタープライズ企業のお客様に対してもっと使いやすく、もっと効率的な管理ができるよう日々開発をしており、2023年2月には「効率的な管理」を実現させるための機能として、 ディレクトリ連携機能のリリース を行いました。 今回は、ディレクトリ連携機能をどのように開発していったか、またそれを実現させる「SCIM」というものについての記事を執筆してみました。 ディレクトリ連携とは Safie Managerのディレクトリ連携 SCIMについて シーケンス エンドポイント設定 インターフェース例 負荷対策について まとめ ディレクトリ連携とは 昨今様々な企業で、社員情報を管理するためにActive Directoryのような仕組みを用いているケースが多くなってきていると思います。 Active Directory (アクティブディレクトリ) とはマイクロソフトによって開発されたオンプレミスにおけるディレクトリ・サービス・システムのことです。2013年にはそのクラウドコンピューティング版である Azure Active Directory が誕生しましたが、こちらを導入されている企業様は多いのではないでしょうか。 azure.microsoft.com Active Directory(以下AD)で主に行えることとしてはユーザー認証とアクセス制御ですが、ADで構築した制御設定(誰がどの機能を触れるか)を他サービスにも反映させたい(=管理の手間をAD上の設定だけにしたい)という需要が生まれました。 ADの設定をそのまま他サービスに連携する、というのがディレクトリ連携のざっくりした説明になります。 Safie Managerのディレクトリ連携 Safie Managerとは、「誰が」「どのカメラを」見ることができるかを統合管理するためのアプリケーションです。 ユーザーとカメラそれぞれを管理する際、一人一人に対してカメラの視聴権限を割り振っていくのは手間になってしまうため、ユーザーとカメラそれぞれをグループごとにまとめて、グループ同士を紐づけることで「Aユーザーグループに所属しているユーザーは、A'デバイスグループ所属のカメラ全てが見れる」といった権限制御を行うことができます。 このグループの中に別のグループを作ることができ、まるでディレクトリ階層のような構造で管理を行うことができるのですが、Azure ADなども同様な階層構造でユーザー管理を行えるものであり、おそらくですがユーザーの所属部署情報や担当現場店舗ごとにグループを切る使い方が多いのではないかと思っています。 Azure ADでのグループ情報およびそこに所属しているユーザーを、Safie Managerにグループ情報ごと同期(プロビジョニング)できますというのが、Safie Managerのディレクトリ連携機能の概要になります。 ※Safie Managerの階層構造権限などの詳細な仕様については割愛させていただきますが、もし興味ある方はまずはぜひ 資料のご請求 からお願いいたします。 SCIMについて いざディレクトリ連携を行うとなった場合、システム管理者の立場から見ると、どのような設定を行えば良いのでしょうか。ここで使用されるのが SCIM というものになります。 SCIMとは、Identity情報(ユーザーの認証情報や権限)を自動プロビジョニングするためのプロトコルです。プロトコルと言うとHTTPのような通信規則が真っ先に思いつくかと思いますが、SCIMは通信規則ではなく標準規格です。「こういったインターフェースでやりとりしてくださいね」という取り決めのことです。 以前 SAML認証を用いたSSO(シングルサインオン)を実装する記事 を書きました。 SSO(シングルサインオン)機能を実装するための取り決めとしてSAMLを使用する ディレクトリ連携機能を実装するための取り決めとしてSCIMを使用する 上記のような関係性となります。 SCIMはRFCにも仕様が定義されているため、SCIMを用いたディレクトリ連携の機能を開発していく上で困った時はこちらを確認すると良いと思います。 tex2e.github.io 詳細な仕様は上記RFCをご確認いただくとして、この記事では私たちセーフィーがSCIMを取り扱った際にやったことや感じたことなどについて記載させていただこうと思います。 シーケンス セーフィーがディレクトリ連携機能のサポートをしているIdP(Identify Provider)は、執筆時点ではAzure Active Directory(以下AzureAD)のみとなります。他IdPの場合では差異があるかもしれませんが、AzureADからセーフィーにプロビジョニングされるシーケンスを記載します。 上記シーケンスは Microsoft公式のドキュメント から抜粋させていただいております。 SP(Service Provider)側はユーザー情報の追加や更新・削除ができるCRUDのAPIを用意しておき、IdP側で情報の更新が起きた際に随時APIを叩くようなシーケンスとなります。 SCIMでのプロビジョニング実行が行われる際、一番初めにGETのAPIが実行されます。このGET APIの実行結果により、 IdP側で持っている情報と違いがないため何もしない IdP側には情報があるがSP側には無いため、POST APIを実行しSPに情報を作る IdP側とSP側で情報が異なる(例: 名前が違う)ため、PATCH APIを実行し情報を更新する IdP側には情報がないがSP側には情報があるため、DELETE APIを実行し情報を削除する 上記のような処理に分岐していきます。 エンドポイント設定 SCIMで同期可能な情報は、ユーザー情報とグループ情報です。(他の情報もありますがここでの説明は割愛します) SPで実装したSCIM用のエンドポイントをIdPに登録することで、IdPから一定のタイミングで同期のリクエストが送られてきます。 情報登録を行える画面はこのようになっています。 登録できるエンドポイントは1つです。このエンドポイントを起点に、 /Users とリソース名が指定されたエンドポイントがユーザー操作の、 /Groups と指定されたエンドポイントがグループを操作するエンドポイントになります。 また、認証時のトークンを指定することができるため、SP側で用意するエンドポイントではトークン認証の仕組みを用意する必要があります。 トークンは Authorization: Bearer xxx 形式で送られてきます。また、Content-typeヘッダが Content-Type: application/scim+json となってリクエストが来るのも特徴です。 インターフェース例 SP側で用意したエンドポイントに対して、IdP側の情報変更があった際に都度SPにAPIリクエストが送られるというのがSCIMの概要ですが、SCIMはこのAPIリクエストの形式およびレスポンス形式についても取り決めがあります。 SCIMを行う上でのリクエスト・レスポンス形式(インターフェース)例はRFCに記載されているのと、IdPの仕様ドキュメントとしても記載されている場合が多いと思います。AzureADの場合は下記のドキュメントをもとにインターフェースの設計と実装を行いました。 learn.microsoft.com ここからはインターフェースの例をいくつか紹介します。 まずPOST /Usersのケースの例を記載します。ユーザーを同期するために必要となる情報は、 AzureAD上のユニークID ユーザー名 メールアドレス などを必須項目としており、下記のようなリクエストを受け付けられるようにしています。 { "externalId": "1234-5678-90ab-cdef", "emails": [{"primary": True, "type": "work", "value": "test@user.com"}], "displayName": "test user", } ちなみに externalId などのキー名については別の名前を使用することもできます。 AzureADには属性情報のマッピングを行う機能があり、この画面にて「Azure AD内のどの情報が」「どのキー名でやりとりされるか」を定義することが可能です。この画面からユーザー情報とSCIMのキーを紐付けます。 この辺りの設定は、IdPを操作できるお客様自身に設定いただくことになるため、基本的にはデフォルトとして設定されている内容を利用しつつ、どうしても設定いただく必要がある箇所についてはマニュアルに記載しつつお渡しするようにしています。 また、SCIMでプロビジョニングできる情報はユーザー情報だけではなく、グループの情報もプロビジョニングすることができます。グループが登録される際は、POST /Groupsに下記のようなリクエストが送られます。 { "schemas": [ "urn:ietf:params:scim:schemas:core:2.0:Group", "http://schemas.microsoft.com/2006/11/ResourceManagement/ADSCIM/2.0/Group", ], "externalId": "1234-5678-90ab-cdef", "displayName": "test group name", "meta": { "resourceType": "Group", }, } グループ自体の登録だけでなく、グループの階層配下にユーザーやグループを追加することもでき、その場合はPATCH /Groupsに下記のようなリクエストが送られます。 { "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], "Operations": [ { "op": "Add", "path": "members", "value": [{"$ref": None, "value": "1234-5678-90ab-cdef"}], } ], } valueの中のIDがユーザーIDの場合はユーザーの追加、グループIDの場合はグループの追加となります。 負荷対策について 例えばIdPに10万件のユーザーを一度に登録した際、ディレクトリ連携されているとその10万件はSPに同期されます。 一気に10万件のリクエストが来るとなると負荷対策的に少し不安になりますが、その辺りはどうなっているのでしょうか。 Azure ADについては、送られるリクエストの間隔が仕様として決められています。 learn.microsoft.com 正常系についてはこの仕様通りの間隔でリクエストが来るはずです。後はそれ以上のリクエストが来る場合(誰かが悪意を持って実行しまくること)を考慮し、例えばリクエスト状況をサーバキャッシュで保持しつつ処理毎に判断し、上記仕様より高い頻度でリクエストが来ている場合は処理前段でエラーとするようにしておけば、少なくともDB負荷については対策の一つとなりそうです。 まとめ ここまでSCIMやディレクトリ連携について記載しましたが、試行錯誤しながら進めていった上で感じたことなどを記載します。 インターフェース仕様はRFCなどで定められているものの、開発を進める上ではIdPにエンドポイントを設定しつつ、どのようなリクエストが来るかログを見ながら仕様を固めていく方が間違いない。 実際のリクエストで判別がつきづらいケース(エラーの場合など)は、RFCの仕様に則って実装すると良い。 IdP側の仕様や挙動によって想定外の工数が発生する恐れがあるため、余裕を持った開発およびQAスケジュールが取れると安心だと思います。 SCIMについてはドキュメントも少ないと思われますので、この記事がどなたかの役に立てば幸いです。 セーフィーではエンジニアの採用を積極的に行っております。もし興味が出てきた際はぜひご応募いただけたらと思います。 safie.co.jp
こんにちは、セーフィー株式会社でサーバサイドのエンジニアをしている河津です。同時に、このSafie Engineers' Blog!の運営も行っています。 2022年12月にセーフィー株式会社初のアドベントカレンダーを実施し、大盛況のまま終わりました。 (だいぶ遅くなってしまいましたが、)この記事では、その様子について振り返りを行いたいと思います! 実施したアドベントカレンダーの基本概要は以前記事にしてまとめましたので、もし良ければこちらもご覧ください! engineers.safie.link 基本情報 モチベーション 運営側で意識したこと 執筆記事のピックアップ ピックアップその1:画像認識コンペについて(CTO森本のフリー素材化) ピックアップその2:PoC系の記事 ピックアップその3:サーバ・インフラに関わる記事 ピックアップその4:開発マネジメント系の記事 ピックアップその5:インターンシップについて 振り返り 基本情報 アドベントカレンダーはQiitaの機能を用いて行いました。 qiita.com 開催前から25枠全てが埋まり、一つの欠けもなく全ての記事が執筆・公開されました!改めて執筆いただいた方々には感謝しております。 モチベーション テックブログの更新などは少しずつ進めていたものの、アドベントカレンダーの開催は今回初となります。 他のテックカンパニーでは当たり前のように開催されており、アドベントカレンダーを開催しないこと自体がセーフィーにとってマイナスになると感じていました。 2022年度になってテックブログ運営チームの体制がようやく整ってきたため、満を持して開催することができました。 運営側で意識したこと まずは「開催して運営する」ことを目標とし、25枠埋めることは目標としませんでした。どちらかというと開催すること自体が最重要と思っていたのと、運営する上での課題は走ってみてフィードバックを集め来年に改善していくことを意識しました。(その上で25枠埋まったのはかなり嬉しいことでした) なるべく運営側の負荷がかかりすぎないよう、あえてレビューフローを短縮しリスクチェックのみとし、運営側が走りきれる体制を心がけました。(後の振り返りで、記事のクオリティアップのためにレビューは厚めにした方が良いかもというフィードバックを得ています。このフィードバックも、まずは実験的にやってみたことで得られたものだと思っています) 執筆記事のピックアップ ここからは、実際にアドベントカレンダーに寄せられたいくつかの記事についてピックアップしたいと思います。 ピックアップその1:画像認識コンペについて(CTO森本のフリー素材化) 執筆いただいた記事の中で、セーフィー社内で行った画像認識コンペをテーマに取り扱った記事が3記事ほど集まりました。 コンペの内容は、オフィス内に設置されているカメラにCTO森本が映ったらそれを検知し、メールやSlackなどに通知するまでを目標に開発を行う、というものです。 いずれも解析モデル構築について試行錯誤している記事になっています。 engineers.safie.link qiita.com qiita.com CTOの顔画像を自由に使って進めている雰囲気がとても良いと思い一番最初にピックアップさせていただきました。 ※アドベントカレンダーの記事ではないですが、最近も顔はめパネル作成をテーマにした記事内でCTOの画像を使用しつつ技術解説しています。 engineers.safie.link ピックアップその2:PoC系の記事 アドベントカレンダーに限らず、セーフィーのテックブログ内にはサービス内で使用されている技術や導入検討している技術の話などが多く集まる傾向にありますが、今回のアドベントカレンダーではそれに限らない様々な角度からの記事が集まりました。 そのうちの一つがPoC系の記事であり、「 whisper 」を用いた文字起こしや、今話題の「 ChatGPT 」について「やってみた/触ってみた」系の記事が集まりました。 いずれも多くの方に読まれた記事となりました。 qiita.com engineers.safie.link ピックアップその3:サーバ・インフラに関わる記事 AWSに関するインフラ周りをテーマにした記事が多く集まる傾向にありました。その中で特にAmazon Aurora MySQLのバージョンアップの話、AWSのコスト集計作業を効率化する話がぞれぞれ多く読まれた記事となったため、ピックアップさせていただきます。 engineers.safie.link engineers.safie.link ピックアップその4:開発マネジメント系の記事 テックブログには技術的な内容をテーマにしたものが集まる傾向にありますが、今回は開発マネジメント系の記事もいくつか集まりました。 マネジメント系と一括りに話しましたが、「スクラム開発」「プロダクトマネジメント」「エンジニアリングマネジメント」「新規事業開発」など様々な観点にフォーカスされた記事が集まりました。 engineers.safie.link engineers.safie.link qiita.com qiita.com ピックアップその5:インターンシップについて 2023年新卒入社予定でインターンシップに参加いただいた学生の方に、インターンシップの内容について記事にしていただきました。 志望理由やインターンシップで行ったこと・試行錯誤したポイント、学んだことなどを執筆しており、セーフィーへの新卒採用エントリーを検討している学生の方々には参考になる内容なのではないかと思っています。 engineers.safie.link 振り返り 初回での開催であり、運営側もノウハウがない中実施したため、いくつか運営側の課題が出てきました。一部を抜粋すると、 Qiitaのカレンダーを使用したが、Qiitaアカウントを持たない人に対して代理でURL登録を行う運用が手間になったため、 Adventar などの使用を検討する 参加ガイドラインの存在が伝わりきらず、ガイドラインの内容が長く分かりにくいからではないかという意見が挙がったためガイドラインの見直しを行う 告知と執筆者募集については早めに(10月初旬ごろには)行った方が良い などの意見が上がったため、改善につなげていきたいと思っています。 また、2023年度も実施を検討していますため、ぜひ楽しみにお待ちいただけたらと思います! セーフィーではエンジニアの採用を積極的に行っております。もし興味が出てきた際はぜひご応募いただけたらと思います。 safie.co.jp
こんにちは、セーフィー フロントエンドエンジニアの沖です。 こちらの記事でも紹介されてますが、セーフィーには様々な種類のエンジニアが在籍しています。 engineers.safie.link 組み込みソフトウェアエンジニア サーバーサイド / インフラ・SREエンジニア AI / 画像処理 エンジニア フロントエンドエンジニア iOS / Androidエンジニア 業務システムエンジニア QAエンジニア データエンジニア 各エンジニアは個人での技術研鑽はもちろんのことチームによっては定期的に勉強会を開いてその知見を共有しています。 チーム毎に勉強会の特色や目的が異なっていたりするので今回はその違いやどんな勉強会があるかについてお話したいと思います。 サーバーチームの勉強会 イメージングチームの勉強会 開発関連の勉強会 論文調査共有会 Webフロントエンドチームの勉強会 モバイルチームの勉強会 まとめ サーバーチームの勉強会 サーバーチームはサーバーサイド / インフラ・SREエンジニアが所属しているチームです。 サーバーチームでは、メンバー各々が持つ開発知見を共有しチーム全体の技術力を底上げすることを目的として隔週で勉強会が開かれています。 2名ずつ発表担当者を決めて、当日までにスライドを作成し約30分の持ち時間の中で発表と質疑応答をします。発表自体は20分で終了し、残り10分で質疑応答とすることが多いです。 こちらが勉強会の1例です。 「WebRTCについて」 内容:WebRTCの概要や通信の仕組み(シーケンス)などの解説。セーフィーの中でWebRTCは、カメラからのライブ映像を低遅延に閲覧するためや、セーフィーで提供しているウェアラブルクラウドカメラ「Pocket2」で使用できるグループ通話機能で使用されています。 開発に携わっている人以外のメンバーへの知見共有の意図もあり実施されました。 「Rustについて」 内容:Rust言語の概要やHello Worldまでの流れ、Rustの特徴である所有権についての解説。 セーフィーでは現状Rustでの開発はありませんが、新しい技術のキャッチアップの意図で開催されました。 「ディレクトリ連携について」 内容:Safie Managerというプロダクトに追加されたディレクトリ連携機能についての共有と、SCIMというものを用いて実装をした話をしました。 勉強会の様子 「Open Policy Agentについて」 内容:Open Policy Agentというポリシーエンジンを実験的に触ってみて、特徴や使用感などをデモと一緒に解説しました。 勉強会の様子 サーバーチームの勉強会では、実際に利用している技術の共有はもちろん、プロダクトには利用していない技術も勉強会のネタとして上がることが多いです。 イメージングチームの勉強会 イメージングチームは画像認識エンジニアが所属しているチームです。 イメージングチームで実施されている勉強会は2つあります。 1つは開発関連の勉強会、もう1つは論文調査共有会です。 開発関連の勉強会 開発関連の勉強会は、2週間に1回の頻度で開催されています。 テーマは、実際に開発しているプロダクトの内容や、各種ツールの紹介等が多くなっています。 例えば、以下のような勉強会が行われています。 「Yoloxの量子化とsnpeの使い方」 内容:SafieOne、AI-App: Store People Detection Pack(以降SPDP)で検討したネットワークであるyoloxの量子化技術、そしてSPDPで利用されるSNPE SDKの利用方法についての紹介 論文調査共有会 論文調査共有会も2週間に1回の頻度で開催されています。 画像認識エンジニアとして論文を読む習慣をつけることで最新の技術動向にキャッチアップすることを目的としています。 忙しい通常業務の中でも読む習慣をつけることが目的のため、共有のための資料を作るというようなことはしません。 紹介後には質疑応答があり、紹介者が分からなかった場合には皆で読みながら解決していきます。 以下が勉強会の1例です。 論文タイトル:「Rethinking Atrous Convolution for Semantic Image Segmentation」 内容:CVPR2022の2017年のセマンティックセグメンテーションの論文で、DeepLabV3として知られています。 こちらは業務でセマンティックセグメンテーションを扱う機会があり、その一環として扱いました。 イメージングチームの勉強会には論文調査共有会というアカデミックな内容が含まれているのが特徴です。 勉強会のネタだし会の様子 Webフロントエンドチームの勉強会 Webフロントエンドチームは、フロントエンドエンジニアが所属しているチームです。 既に実装されている機能の共有やチーム内のコミュニケーションの活性化を目的に毎週開催されています。 所属メンバーが順番に発表者となり話したいテーマを話していく形で進めています。内容の難易度は基本的に不問で発表すること自体を重要視しています。 スライドに関しては、発表者が要否を判断して発表を行いますが、スライドよりは実際のコードを見ながら議論するケースが多いです。 こちらが勉強会の1例です。 「認証の仕組み」 内容:フロントエンドで認証情報をどのように扱っているかの解説。 実際の実装を見ながらCORSやCSPなどの周辺知識を共有しながらディスカッションを実施しました。 「セーフィー独自で作成されたライブラリの説明」 内容:実装を簡略化するためにセーフィー内で実装されているライブラリの中身に関する説明。 利用する時の考慮点はもちろん内部実装を深堀ってディスカッションを行いました。 コードを映しながらディスカッション Webフロントエンドチームの勉強会では、実装している社員が変わっていくということを前提に既存のコードの深掘りを行うことが多いです。 メンバーによっては知っていることの復習になるケースもありますが、それは許容し発表することのハードルを下げるようにしています。 テーマは業務外の内容になることもありますが、比較的業務で利用している技術になることが多いです。 モバイルチームの勉強会 モバイルチームは、iOS / Androidエンジニアが所属しているチームです。 業務内外問わず個人が得た技術的知見をチームに共有することを目的に隔週で開催されています。 所属メンバーが順番に発表者となりテーマを決めてスライドを作成し20~30分で発表します。 その後質疑応答が10分~20分ほどあることが多いです。 どのようなテーマを取り上げるかはメンバーに任されています。 こちらが勉強会の1例です。 「RxSwiftについて」 内容:プロジェクトにRxSwiftを取り入れたタイミングで、実装者による初学者向けの解説を行いました。 「KMM(Kotlin Multiplatform Mobile)について」 内容:KMM自体を知らないメンバーにも分かりやすく概要から説明。 実際にチーム内で活用するならどうするか、どういったメリットがあるのか等を交えたディスカッションも行いました。 モバイルチームにはiOSエンジニアとAndroidエンジニアがいますが、勉強会は自分の担当しているOS以外の知見を広げられるいい機会となっています。 まとめ セーフィーの社内では数多くの勉強会が開かれていますが、チーム毎に課題と思っていることや求められているものが異なるため、勉強会の目的ややり方にも特徴が出ています。 また、勉強会自体も昔からあるものから新しく始まったものまで様々です。 セーフィーには、技術的にモチベーションが高いエンジニアが数多く在籍しているので、もし記事を読んでセーフィーに興味を持っていただいた方はぜひご応募ください。 article.safie.link
こんにちは。「 Safie Pocket2 」のプロダクトマネージャーをしている坂元です。 本記事では2022年11月に全てのSafieカメラで利用可能となった位置情報連携機能について紹介したいと思います。 位置情報連携機能とは 据え置きカメラにまで機能を拡大する理由 直感的なUI リリース後の反響 最後に 位置情報連携機能とは 位置情報連携機能は、2020年10月にGPSを搭載するウェアラブルカメラ「Safie Pocket2」に搭載された機能です。 Safie Pocket2が取得した位置情報をマップビューアー(Safie Viewerの地図アプリ)上に表示して、映像データと紐づけて表示することができます。 移動による位置情報の変遷を移動軌跡として表示が可能なため、Safie Pocket2の軌跡を追いながら映像を確認することが可能となりました。 この位置情報連携機能により、ユーザーはこれまでの時間軸による映像管理だけでなく、位置情報による映像管理が可能となり、映像データの利活用の幅を広げられます。 Safie Pocket2と位置情報連携機能は多くの現場で活用いただいています。 ある現場では、現場従事者の移動軌跡と映像データを、効率的な導線を定着させるための新人教育に活用されています。また、遠隔パトロール中のトラブル発生時には、管理者が位置情報を即座に共有し、応援を現場に向かわせるといったユースケースで採用されています。 Safie Pocket2の位置情報連携機能(イメージ) 据え置きカメラにまで機能を拡大する理由 Safie Pocket2のようにウェアラブルカメラは移動を伴うので、変化する位置情報と映像データの紐づけが、いかに有用かは想像しやすいと思います。 今回、移動することがほぼない「据え置きカメラ」になぜ位置情報連携機能を開発したかの背景を説明します。  映像データ+位置情報による映像データの活用を広げる Safie Pocket2で映像データに位置情報を紐づけることで、映像データの利活用の幅が大きく広がりました。据え置きカメラでも同じことを再現できると考えています。 管理者は据え置きカメラの位置情報と映像を基に、現場従事者にこれまで以上に適切な指示を出すことが可能になります。 例えば、河川管理の監視カメラ映像に位置情報と画角情報を紐づけるとします。すると災害発生時に、設置位置や、上流から見た映像か・下流から見た映像かを瞬時に判断して、適切な意思決定ができるようになります。 カメラの複数台管理を効率化させる Safie Viewerではカメラの映像を見たいときにはカメラ一覧からデバイスを選択する必要があります。 ユーザーはデバイス名を任意で付けることができるので、そのカメラを特定しやすいデバイス名を付けています。 複数のカメラを管理する場合、デバイス名で管理されている しかし、複数台のカメラを導入している場合、デバイス名は「〇〇工事現場南門( 北西側 から撮影)」「△△ビル カメラ 5号機 」のように、方角やナンバリング等でカメラを管理していることが分かりました。 カメラ設置した本人であればこれらのデバイス名でカメラを容易に特定できますが、現場を知らない場合、どこに設置され、どの画角で撮影しているカメラかを特定することが難しく、コミュニケーションコストが発生していたと推察しています。 こうしたカメラの複数台管理のコストを軽減させるために、地図上にカメラ設置位置の表示、画角の情報を表示させる手法は有効だと考えました。 直感的なUI Safie Pocket2と異なり、据え置きカメラの多くにはGPS機能が搭載されていない為、自動的に位置情報を取得することができません。 ユーザーが手動で位置情報を設定する必要がある為、カメラの位置情報をいかに簡単に設置させることに拘りました。 話はそれますが、Safieを選んでいただく理由でプロダクトの直感的な操作がよかったと言う声を多くいただきます。今回も多分に漏れず直感的に操作できるUIを目指しました。 カメラの位置と向きを設定する管理画面 マップビューアー(Safie Viewerの地図アプリ)の画面(イメージ) 管理画面の地図上にて、クリック&ドラックでカメラの位置を設定 カメラの位置は地図上をクリックすると表示されるカメラアイコンを 地図上でドラックするだけで容易に設定できます。 カメラの向きの簡単設定が可能 カメラ向きはマーカーをクリックして円周上で動かすと、マップ上にあるカメラの向きが連動して変わります。 マップビューアー内のカメラの視認性向上 機能の適用拡大により表示されるカメラの数が増えることから、地図上のカメラアイコンの表示内容やデザインを見直し、視認性を向上させました。   リリース後の反響 2022年11月のリリース後の1か月間で、位置情報連携機能の利用者は前月より 約60% 増えており(※)、据え置きカメラでの利用が進んでいます。 特に、建設現場等の屋外現場向けのSafie GOシリーズのユーザーの利用が多い傾向にあります。これは、広域の屋外現場に複数台のカメラを設置している建設業や土木業のニーズにマッチしている結果であると推察しています。 (※2022年10月と12月のマップビューアー利用者数の実績より) 最後に 2023年1月にSafie GOシリーズにGPS機能を持つ据え置きカメラ Safie GO PTZ Plus が追加されました。 これにより自動でカメラの設置位置を取得しマップビューアー上に表示することが可能となりました。(23年1月時点でSafie GO PTZ Plus以外の据え置きカメラは手動設定による位置情報設定が必要です) 「映像から未来をつくる」がセーフィーのビジョンです。 映像データの価値を上げるために、映像デバイスやAI解析技術の開発が進んでいます。今回の映像データ+位置情報もまた映像データの価値を上げ、ユーザーが映像データの利活用の幅を広げる一手であると思います。 今後もさらなる提供価値を高めていくため、映像データ+位置情報のブラッシュアップを継続していきます。
こんにちは!エンジニアの大林です。 先日セーフィーのエンジニアにアンケートを取ったので、今回はその内容をお届けします。 アンケートのテーマは「新卒エンジニアにおすすめの〇〇は?」ということで、以下の3つについて聞いてみました! 技術本 技術以外の本 本以外の学習媒体 新卒エンジニアにおすすめの技術本は? 新卒エンジニアにおすすめの技術以外の本は? 新卒エンジニアにおすすめの本以外の学習媒体は? 最後に 新卒エンジニアにおすすめの技術本は? たくさんの意見が寄せられましたが、中でも多かったのが以下の2冊でした。 1. リーダブルコード / Dustin Boswell、Trevor Foucher リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice) 作者: Dustin Boswell , Trevor Foucher オライリージャパン Amazon 美しいコードを見ると感動する。優れたコードは見た瞬間に何をしているかが伝わってくる。そういうコードは使うのが楽しいし、自分のコードもそうあるべきだと思わせてくれる。本書の目的は、君のコードを良くすることだ。(本書「はじめに」より) \エンジニアからのおすすめの声/ 仕事で書くコードは自分以外の人も読めて保守性が高いことは非常に重要です。 この本を読むことで他人が読めるコードを書くための書き方を理解できます。 読みやすいコードの何たるかを学べる。どんなエンジニアでも役に立つ! 最低限のコードルールを知っておいてほしいから。 コーディングする上でのTIPSが豊富! 私もエンジニアとして仕事を始める前にこの本を読みました。当たり前ですが、仕事でコードを書いていると、自分が書いたコードをチームのメンバーや未来の自分が読んだり、そのプログラムに改修を加えたりするので、コードの読みやすさがいかに大切かを実感します。読み物としてもとても読みやすい本だと思います! 2. Webを支える技術 / 山本陽平 Webを支える技術 ―― HTTP,URI,HTML,そしてREST WEB+DB PRESS plus 作者: 山本 陽平 技術評論社 Amazon 本書のテーマは,Webサービスの実践的な設計です。まずHTTPやURI,HTMLなどの仕様を歴史や設計思想を織り交ぜて解説します。そしてWebサービスにおける設計課題,たとえば望ましいURI,HTTPメソッドの使い分け,クライアントとサーバの役割分担,設計プロセスなどについて,現時点のベストプラクティスを紹介します。 \エンジニアからのおすすめの声/ Webの基礎が理解できるから。 開発全般で意識すること、周辺知識を理解できる! CSの基本的な内容。 この本を挫折しそうになった人には「プロになるためのWeb技術入門」がおすすめ! こちらの本もエンジニアとして仕事を始める前に読みましたが、文系出身でコンピュータサイエンスを全く学んでこなかった私には難しいと感じる部分もありました。ただ、業務をしていく中で「あ、これってあの本で言ってたアレのことか!」という風に点と点がつながる瞬間があったりして、たくさんインプットすることの大事さを感じていました。 また、この2冊ともをおすすめしたエンジニアからは「初学者であれば、平易で広い概念から入って、その後さらに専門領域の書籍に移っていくのが良さそう」という意見が出ていました! そのほかにもたくさんの技術本が挙がったのでジャンルごとにご紹介します。 様々なエンジニアが集まるセーフィーらしく、幅広いジャンルがおすすめとして挙がりました。 サーバー Amazon Web Services 基礎からのネットワーク&サーバー構築 Linuxとpthreadsによるマルチスレッドプログラミング入門 Linuxのしくみ SQL 第2版 ゼロからはじめるデータベース操作 SQLアンチパターン SREの探求 新しいLinuxの教科書 *達人に学ぶDB設計徹底指南書 言語・コーディング C++のためのAPIデザイン CODE COMPLETE 完全なプログラミングを目指して EffectivePython コーディングを支える技術 プログラミングTypeScript 実践ソフトウェアエンジニアリング 設計 Clean Architecture UNIXという考え方 オブジェクト指向でなぜつくるのか オブジェクト指向のこころ テスト駆動設計 データ指向アプリケーションデザイン ―信頼性、拡張性、保守性の高い分散システム設計の原理 コンピュータサイエンス CPUの創りかた RISC-VとChiselで学ぶはじめてのCPU自作 コンピュータシステムの理論と実装 プロセッサを支える技術 Web・ネットワーク Real World HTTP プロになるためのWeb技術入門 その他 JSTQB(資格) Team Geek ゼロからつくるDeepLearning ビッグデータを支える技術 達人プログラマー 新卒エンジニアにおすすめの技術以外の本は? こちらもかなり幅広く集まったので、一部ピックアップしてご紹介します。 ユニコーン企業のひみつ 起業のファイナンス 経営に終わりはない 武器としての決断思考 伝え方が9割 失敗の科学 失敗の本質 イシューからはじめよ イノベーションのジレンマ amazonのすごい会議 問いかけの作法 予想通りに不合理 若手育成の教科書 起業や経営に関わる内容のものが多くてベンチャーらしいなと思いました。また、急速に組織が大きくなっていることや、新卒採用を始めたばかり(23卒から)なことなどが影響していそうな本が多かったのもセーフィーらしいのかなと感じました! 新卒エンジニアにおすすめの本以外の学習媒体は? 本以外で学習するための媒体として何を活用しているかを聞きました。 podcast rebuild.fm fukabori.fm Udemy のような音声や動画で利用するものから、 プロコン(チーム形式) ハッカソン Atcoder 人! など、エンジニア同士のコミュニケーションを重視する声もありました。 最後に 新卒エンジニアに向けてのメッセージも募集しました! 技術本を読み漁ることも大切ですが、仕事に慣れない内はIPAの資格や業務関連資格を取得し、知識の体系化に努めると良いと思います。仕事に少し慣れてきたら、テック系の勉強会やイベントに参加すると良いと思います。社外の優秀なエンジニアから刺激を受け、勉強や仕事のモチベーションに繋がります。 誰でも初めは若葉マークと思っていますので失敗したことに対して落ち込みすぎないようにすると気が楽ですよ。挑戦してみての失敗は致し方なしですし、ボスが大体責任取ってくれますので今のうちに失敗をいっぱいしましょ! 数多ある企業の中で、せっかくできたご縁ですので一緒にいい仕事をしましょう! わからないことがあればなんでも聞いてください!技術トークしましょう! 周囲の常識や固定観念にとらわれずに、自分らしく伸び伸びとやってください! セーフィーではいろいろな技術領域を学ぶことのできるエンジニアとして成長できる環境です。ぜひセーフィーで成長し、新しい価値を社会にどんどん提供していきましょう。 セーフィーでは新卒エンジニアを募集しています! 興味を持っていただけたらぜひ採用サイトも覗いてみてください。 safie.co.jp
こんにちは。セーフィーで画像認識エンジニアをやっている柏木です。 今回はセーフィーで行った PoC (Proof of Concept) の一つである、商品棚の Semantic Segmentation について紹介いたします! 背景と課題 Semantic Segmentation DeepLabV3 データセットとアノテーション MMSegmentationを使った学習 データセットClassの作成 データセットConfigの作成 学習・テストConfigの作成 環境 学習 評価結果 終わりに 背景と課題 大手スーパーマーケット様より、商品棚の欠品状況を解析したいとのお話がありました。欠品の状況が解析できれば、品出しのタイミングを最適化し、機会損失を削減することができます。イメージングチームではこれらの課題を解決すべく、PoCを行ってみることとしました! こちらが実際の商品棚の写真になります。 Semantic Segmentation 欠品状況を解析するためのアルゴリズムとして、 Semantic Segmentation を選定しました。Semantic Segmentationとは、画素ごとにカテゴリ付けを行うニューラルネットワークのアルゴリズムです。自動運転や、医療用途で利用されています。 Semantic Segmentationは 境界をはっきり出すことができる 重なりがある物体や不定形の物体に強い ことが特徴のアルゴリズムになります。 別のアルゴリズム案として物体検出も考えましたが、重なった商品や小さな商品を検出するのが難しいことから、今回はSemantic Segmentationを採用しました。 DeepLabV3 DeepLabV3 はGoogleが2017年に発表したSemantic Segmentationアルゴリズムです。Atrous畳み込みを直列に何層も重ね、またAtrous rateを変えて並列に繋げたことが新規性のネットワークとなっています。論文は『 Rethinking Atrous Convolution for Semantic Image Segmentation 』です。 セーフィーではエッジAI機能を搭載したカメラである『 Safie One 』を提供しています。そこで、Safie Oneでも動作可能な軽量なモデルとしてDeepLabV3を使用することとしました。 DeepLabV3の公式リポジトリはTensorflow実装になっています。セーフィーではPytorchの知見が多いので、Pytorch実装である MMSegmentation を使用しました。こちらのリポジトリはDeepLabV3に限らず多くのモデルが実装されており、バックボーンも豊富で使いやすいです。 データセットとアノテーション データセットはスーパーマーケット様より提供いただきました。モデル自体が軽量ということや、固定カメラにおける利用を想定していることから多くの画像のバリエーションは不要と判断しました。そこで、Train(学習用)・Validation(検証用)・Test(テスト用)含めて200枚程度としました。 アノテーションツールは弊社が日頃から利用させていただいている『 FastLabel 』を使用しました。FastLabelは物体検出、物体追跡、Segmentationなど豊富なタスクをアノテーションすることができます。クラウドベースのサービスのため、アノテーションした結果をチーム内で共有することも容易にできます。またUIが日本語なので使いやすいです。 FastLabelが出力するアノテーション形式は選ぶことができます。今回はMMSegmentationで利用することが容易だったマスク画像を使用しました。 アノテーションにおけるクラスは background package person wagon の4種類としました。packageが実際の商品を表すカテゴリです。backgroundとpackageさえあれば欠品状況は解析できるのですが、人やワゴンが映り込むことでオクルージョン(隠れ)が生じることがあり、そのような状況は検出不能と扱う必要があるためのでperson、wagonと別カテゴリを用意しました。 マスク画像(黒がbackground, 赤がpackage) MMSegmentationを使った学習 データセットClassの作成 独自データセットで学習するためには、まずそのデータセットClassを作る必要があります。 mmseg/datasets/以下にデータセットの設定ファイルを作成します。 class SaladDataset (CustomDataset): """Salad dataset.""" CLASSES = ( 'background' , 'package' , 'person' , 'wagon' ) PALETTE = [[ 0 , 0 , 0 ], [ 0 , 0 , 192 ], [ 0 , 192 , 0 ], [ 192 , 0 , 0 ]] def __init__ (self, **kwargs): super ().__init__(img_suffix= '.jpg' , seg_map_suffix= '.png' , **kwargs) おもにクラス(カテゴリ)の名前CLASSES、推論結果の画像出力時の色であるPALETTEを変更すれば大丈夫です。 作成したら mmseg/datasets/init.py に作成したクラスを追加します。 データセットConfigの作成 configs/base/datasets/以下に学習・テストの設定ファイルを作成します。 dataset_type = 'SaladDataset' data_root = 'data/salad' img_norm_cfg = dict ( mean=[ 121.13 , 118.48 , 115.98 ], std=[ 3.14 , 2.97 , 3.15 ], to_rgb= True ) crop_size = ( 512 , 512 ) train_pipeline = [ dict ( type = 'LoadImageFromFile' ), dict ( type = 'LoadAnnotations' ), dict ( type = 'Resize' , img_scale=( 2048 , 512 ), ratio_range=( 0.5 , 2.0 )), dict ( type = 'RandomCrop' , crop_size=crop_size, cat_max_ratio= 0.75 ), dict ( type = 'RandomFlip' , prob= 0.5 ), dict ( type = 'PhotoMetricDistortion' ), dict ( type = 'Normalize' , **img_norm_cfg), dict ( type = 'Pad' , size=crop_size, pad_val= 0 , seg_pad_val= 255 ), dict ( type = 'DefaultFormatBundle' ), dict ( type = 'Collect' , keys=[ 'img' , 'gt_semantic_seg' ]), ] ~~ 省略 ~~ data = dict ( samples_per_gpu= 4 , workers_per_gpu= 4 , train= dict ( type = 'SaladDataset' , data_root= '/workspace/mmsegmentation/data/salad' , img_dir= 'train/images' , ann_dir= 'train/annotations' , pipeline=train_pipeline), 特に dataset_typeの指定 data_rootの指定 data augmentationのパイプライン(train_pipeline, test_pipeline) train, val, testのtype、data_rootの指定 に注意してください。 学習・テストConfigの作成 configs/ユーザー名/以下にネットワークモデルの設定ファイルを作成します。 _base_ = [ '../_base_/models/deeplabv3_r50-d8.py' , '../_base_/datasets/salad_data.py' , '../_base_/default_runtime.py' , '../_base_/schedules/schedule_20k.py' ] model = dict ( backbone= dict ( type = 'ResNet' , depth= 18 ), decode_head= dict (in_channels= 512 , channels= 128 , num_classes= 4 ), auxiliary_head= dict (in_channels= 256 , channels= 64 , num_classes= 4 )) 特に データセット設定ファイルの指定('../base/datasets/salad_data.py') クラス数(num_classes) 学習スケジュール('../base/schedules/schedule_20k.py') の指定に注意してください。 環境 Dockerfileからイメージを作成し、コンテナを立ち上げます。ただ元のDockerfileではmmsegmentationをgitでcloneしてインストールしていますが、今回は独自データセットを利用するためにコードを変更しています。そのため、git cloneではなく現在のディレクトリをマウントするようにする必要があります。また、データセットがあるフォルダをマウントしてください。 学習 学習はtools/train.pyで可能です。今までの設定で準備は終わっているので、学習・テストのconfigファイルを引数で指定すればOKです! 学習時間はGPUのNVIDIA GeForce RTX2080 Tiを使用して1時間ほどで終わりました。 評価結果 tools/test.pyで評価することができます。推論だけではなく評価もスクリプトに含まれているので、そのまま表示されます。 IoU は正解の領域と推論した領域の重なり具合を評価する指標です。一方で Accuracy はカテゴリを正確に推測できている画素の割合の指標になります。 Class IoU Acc background 95.31 97.35 package 85.38 92.61 person 87.1 97.14 wagon 78.65 86.24 クラス別の値をみるとbackgroundとpersonで高い値が出ています。一方wagonは少し苦手なようです。 aAcc はすべての画像でAccuracyを出したときの値、 mIoU は画像ごとのIoUの平均値、 mAcc は画像ごとのAccuracyの平均値です。 aAcc mIoU mAcc 96.13 86.61 93.33 精度という観点でみると9割以上を達成してます。勿論、今後さらなる改善に向けた精度向上は必要ですが、欠品検知という課題解決の目的では十分な性能と言っていいのではないかと考えています。 終わりに 今回はセーフィーで行っている、商品棚のSemantic Segmentationに関する取り組みを紹介しました。これ以外にもお客さんの課題を解決するためのPoCをどんどん行っていく予定ですので、興味がある方は是非チェックしてみてください! また一緒に働く仲間も募集しています!採用ページは こちら にあるので、よろしくお願いします!