TECH PLAY

株式会社エブリー

株式会社エブリー の技術ブログ

385

はじめに こんにちは!トモニテにて開発を行っている吉田です。 今回は API を本番のデータにつなぎながら確認できるようステージング環境を作成したのでそのことについて書いていきます! 目的 本番環境へのリリース前には、さまざまなケースを考慮したテストを行うことが不可欠です。しかし、開発環境で考えられる限りのケースを網羅しても、どうしても考慮漏れが発生することがあります。このようなリスクを軽減するために本番環境を利用したステージング環境を構築しました。 これにより、実際のデータを使用しながら運用時を想定したテストを行うことが可能になりリリース前に潜在的な問題を早期に発見し、サービスの品質を向上が期待できます。 ステージング環境の作成 現在のトモニテのインフラ構成は簡単に図に起こすと下記のようになっています。 今回は ECS を新たに作成しリスナールールを追加することでステージング環境の構築を実現をします。 図だと赤枠で囲っているところが新たな構成になるイメージ(下図)です。 ステージング環境の作成でのゴールは以下のように設定しました。 エンドポイントを叩いて動作確認ができる 開発ブランチへのマージをトリガーにステージング環境のサーバーにもデプロイ エンドポイントを叩いて ステージング できるようにする 弊社ではインフラの管理に terraform を利用しています。以下は、AWS インフラを terraform を用いて構築するためのリソース定義のサンプルです。 (各リソースについて一部省略しています) 1.タスク定義 resource "aws_ecs_task_definition" "stg_server" { family = "stg-server" requires_compatibilities = [ "FARGATE" ] network_mode = "awsvpc" cpu = 256 memory = 512 execution_role_arn = <タスク実行のARN> task_role_arn = <IAMロールのARN> // 他のAWSサービスを呼び出すため container_definitions = jsonencode ( [ { command = [ ... ] cpu = 0 // 指定がなければ自動で割り当てられる environment = [ ... ] essential = true // タスク内のいずれかのコンテナが停止したときにすべてのコンテナを停止するか image = <ecrのイメージを指定> logConfiguration = { logDriver = "awslogs" options = { awslogs-create-group = "true" awslogs-group = "stg-server" awslogs-region = <region指定> awslogs-stream-prefix = "ecs" } } mountPoints = [] name = "stg-server" portMappings = [ { containerPort = 1323 hostPort = 1323 protocol = "tcp" } , ] secrets = [ ... ] volumesFrom = [] } , ] ) runtime_platform { cpu_architecture = "X86_64" operating_system_family = "LINUX" } } 2.ターゲットグループ resource "aws_lb_target_group" "stg_server_target" { name = "stg_server_target" port = 1323 target_type = "ip" protocol = "HTTP" vpc_id = <VPCのID> deregistration_delay = <ドレインするまでに待機する時間> health_check { path = "/healthcheck" interval = 30 timeout = 5 healthy_threshold = 5 unhealthy_threshold = 2 } } 3.クラスター resource "aws_ecs_cluster" "stg_ecs_cluster" { name = "stg_ecs_cluster" } 4.サービス resource "aws_ecs_service" "stg_server" { name = "stg-server" cluster = aws_ecs_cluster.stg_ecs_cluster.arn task_definition = "stg-server" desired_count = 1 deployment_maximum_percent = 200 deployment_minimum_healthy_percent = 100 launch_type = "FARGATE" enable_execute_command = true load_balancer { target_group_arn = aws_lb_target_group.stg_server_target.arn container_name = "stg-server" container_port = 1323 } network_configuration { subnets = <subnet指定> security_groups = <セキュリティグループ指定> assign_public_ip = true } lifecycle { ignore_changes = [ task_definition ] } } 5.ロードバランサー resource "aws_alb_listener_rule" "stg_server_rule" { listener_arn = <登録するリスナーリソース>.arn priority = <優先度> action { type = "forward" target_group_arn = aws_lb_target_group.stg_server_target.arn } condition { host_header { values = [ <マッチするホストヘッダーパターン> ] } } } 6.Route53 レコード resource "aws_route53_record" "stg_server" { name = <レコード名> records = <ELBのDNS名> ttl = "300" type = "CNAME" zone_id = <ホストゾーンのID> weighted_routing_policy { weight = 100 } } 7.ECR のライフサイクルポリシー resource "aws_ecr_lifecycle_policy" "stg-server-lifecycle_policy" { repository = <stg環境用のECRリポジトリ>.name policy = <<EOF { "rules": [ { "rulePriority": 1, "description": "最新の1イメージを残す", "selection": { // 今回はタグによる指定 "tagStatus": "tagged", "tagPrefixList": ["stg"], "countType": "imageCountMoreThan", "countNumber": 1 }, "action": { "type": "expire" } } ] } EOF } 上記の構成を既存リソースに加えることで新規に作成した API についてエンドポイントを叩いて動作確認することができるようになりました。 ※今回は認証について言及していませんが別途認証の設定等も必要になります。 注意点 ALB やホストゾーンなど既存のリソースを利用しているものについては記述を省略しています。 <...>で囲まれた部分は、実際の値に置き換えてください。 各リソースの設定は、具体的な要件や環境に応じて調整が必要です。 Terraform のバージョンや AWS プロバイダーのバージョンによって、リソースの属性や構文が異なる場合がありますので、公式ドキュメントを参照してください。 開発環境へのデプロイをフックにステージング環境のサーバーにもデプロイ トモニテでは、開発プロセスの効率化と品質向上を目指し、ビルドに AWS CodeBuild を利用しています。ステージング環境のサーバーへのデプロイは、開発ブランチへのマージをトリガーとして自動的に行う仕組みを構築しました。 これにより、開発者はコードをマージするだけで、ステージング環境に最新のビルドをデプロイすることができます。 以下は、AWS CodeBuild で使用する buildspec.yml の設定です。このファイルは、ビルドプロセスの各フェーズで実行されるコマンドを定義しています。 version : 0.2 env : variables : REPOSITORY_URI_BASE : <REPOSITORY_URI_BASE> DOCKER_BUILDKIT : "1" phases : install : commands : - GO_VERSION=$(cat .go-version) # Go のバージョンを取得 - REPOSITORY_URI=${AWS_ACCOUNT_ID}${REPOSITORY_URI_BASE}stg-server # Docker イメージのリポジトリ URI を設定 - TAG=stg - | # ecs-deploy ツールをダウンロードし実行可能にする echo "Setup ecs-deploy" curl -sL https://github.com/silinternational/ecs-deploy/archive/3.10.7.tar.gz | tar zxvf - mv ecs-deploy-3.10.7 ecs-deploy chmod +x ecs-deploy/ecs-deploy pre_build : commands : - docker login -u <USER_NAME> -p ${DOCKER_HUB_PASS} # Docker Hub にログイン - DATE=`date +%s` # 現在の日時を取得(イメージのタグに使用) build : commands : # Docker イメージをビルドしタグ付け - echo Building the Docker image... - docker build -f ./Dockerfile --build-arg GO_VERSION=$GO_VERSION -t $REPOSITORY_URI:$TAG . - docker tag $REPOSITORY_URI:${TAG} "${REPOSITORY_URI}:${TAG}.${DATE}" post_build : commands : - echo Logging in to Amazon ECR ... - aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}${REPOSITORY_URI_BASE # Amazon ECR にログイン - echo Pushing the Docker images... - docker push $REPOSITORY_URI:$TAG # ビルドした Docker イメージをプッシュ - docker push "${REPOSITORY_URI}:${TAG}.${DATE}" - | # ECS クラスターに新しいタスクをデプロイ echo "deploy start" ecs-deploy/ecs-deploy --cluster <CLUSTER_NAME> \ --task-definition-file <タスク定義>.json \ --service-name <SERVICE_NAME> \ --region <region指定> \ --timeout 600 \ --image ${AWS_ACCOUNT_ID}.${REPOSITORY_URI_BASE}/<FAMLIY>:${TAG} cache : paths : - $GOPATH まとめ 今回の記事では、ステージング環境の構築について説明しました。本番データに接続し実際の運用を想定したテストを行うことで、リリース前に潜在的な問題を早期に発見し、サービスの品質を向上させることを目的としています。 ステージング環境の作成により考慮漏れによるリスクを軽減できたと考えています。 一方、この環境は比較的ライトに構築したため、さらなる改良の余地があると考えています。今回作成したものをゴールとせず、より良い構成になるよう今後も改善を重ねていきたいと思います!
アバター
Cloud SQL for MySQL 5.7 から 8.0 移行計画 はじめに こんにちは、TIMELINE 開発部 Service Development をしている ほんだ です! つい最近 Aurora MySQL バージョン 3 対応したな...。というのはさておき。 今回は Cloud SQL for MySQL のデータベースバージョンを 5.7 から 8.0 に移行する際に行った方法について紹介します! 前提 皆さんご存知かと思いますが、2025年2月1日からCloud SQLではMySQL 5.7, 5.6の拡張サポートが開始し、2028年2月1日はサポートが終了します。そこでTIMELINE が提供しているサービスの一つで、Cloud SQL for MySQL 5.7を使用しているため8.0への移行を行うことになりました。 今回のシステムでは月に1回程度メンテナンスでサービスが停止するため、そのタイミングでインプレイスアップグレードを行うことにしました。以下では5.7から8.0へ移行する際の事前準備、インプレイスアップグレードの手順、切り戻し方法について説明します。 事前準備 5.7と8.0の差分を確認 MySQLの公式 を参考に8.0での差分を確認しました。 今回はデフォルト認証プラグインが caching_sha2_password , character_set_server のデフォルト値が utf8mb4 に変わったことや collation_server のデフォルト値が utf8mb4_0900_ai_ci に変更されたことが差分として上がりました。 Cloud SQLの設定の確認 Cloud SQLはデータベースフラグというものを用いてMySQLパラメータの調整を行っているので、その確認も必要です。データベースフラグのデフォルトやどの値が設定できるかは こちら のサイトで確認ができます。 今回のデータベースでは上述5.7と8.0の差分で上がった default_authentication_plugin , character_set_server と collation_server をデータベースフラグを用いて設定することになりました。 手順 手順は下記の通りになります。 順番 手順 1 インスタンスのクローン 2 インプレイスアップグレード 3 MySQL versionの確認 4 データベースフラグの設定 ここからは実際に実行するコマンドも踏まえて説明していきます。 1. インスタンスのクローン インプレイスアップグレードを実行する際に、バックアップが作成されますが、こちらはあくまでデータのみのため別途手動でインスタンスのクローンを行います。 Cloud SQLインスタンスの名前は変更することはできないので、アップグレードに失敗した際に切り替えて問題ない名前にしとく事をお勧めします。 $ gcloud sql instances clone < SOURCE_INSTANCE_NAME > < DESTINATION_INSTANCE_NAME > --project =< PROJECT_ID > 2. インプレイスアップグレード 下記コマンドを用いて既存のインスタンスに対してインプレイスアップグレードを実行します。上述のように、インプレイスアップグレード開始時と終了時にデータのバックアップが作成されます。 今回は8.0.33にアップグレードするため database-version に MYSQL_8_0_33 を指定します。 $ gcloud sql instances patch < INSTANCE_NAME > --database-version = MYSQL_8_0_33 インスタンスのバックアップについては下記コマンドで確認することができます。 $ gcloud sql backups list --instance =< INSTANCE_NAME > インスタンスのアップグレード状況は下記コマンドで確認することができます。 $ gcloud sql operations list --instance =< INSTANCE_NAME > $ gcloud sql operations describe OPERATION 3. MySQL versionの確認 Cloud SQL Auth Proxyを用いてCloud SQLに接続しMySQLのversionを確認します。8.0系になっていることが確認できたらOKです。 $ cloud-sql-proxy --port < PORT_NUMBER > < INSTANCE_CONNECTION_NAME > $ mysql -u < USER_NAME > 127 . 0 . 0 . 1 -P < PORT_NUMBER > -p mysql > select version () ; 4. データベースフラグの設定 上述の通り、MySQLのdefault設定と異なる部分があるので、Cloud SQLのデータベースフラグを更新を行います。 $ gcloud sql instances describe < INSTANCE_NAME > $ gcloud sql instances patch < INSTANCE_NAME > --database-flags = character_set_server =utf8, collation_server =utf8_general_ci, default_authentication_plugin =mysql_native_password 下記コマンドにてデータベースフラグが更新されていることを確認します。 $ gcloud sql instances describe < INSTANCE_NAME > 念の為インスタンスに接続しMySQL commandでも確認すると良いです。 mysql> show variables like '%char%' ; 切り戻し方法 インスタンス名が変わっていることに注意して、手順1. で作成した既存のインスタンのクローンにそれぞれのサーバーの向き先を変更します。 まとめ 以前Aurora3への移行を行った際はconsoleベースで、アップグレードを行ったのですが今回gcloud commandを用いて行うことで、以前 rymiyamotoの記事 でもあった通りメンテナンスの再現性や、レビューが格段に容易になったと感じました。 また、今回の手順に記したものを実際の値に変えた手順書を作成しているため、当日はコピペするだけで作業が完了するので実行者の負荷やヒューマンエラーも軽減することができました!(筆者はタイポが多いです) 今回Cloud SQLのアップグレードを行うにあたって、明確な原因は追求できていませんがsidecar containerとして実行している、Cloud SQL Auth Proxyのimage versionが古すぎて、インスタンスに接続できないという事象が発生したので、定期的にメンテナンスを行うことは大切だと実感しました。 Cloud SQL for MySQLのインプレイスアップグレードを行おうとしてる人や今後本システムの開発に携わる人の参考になれば幸いです。
アバター
タイトル - リテールハブ開発部の新設とDELISHKITCHEN開発部長の交代 はじめに 皆さん、いつもお世話になっております。CTO/開発本部の今井です。 本ブログでは、あたらくリテールハブ開発部を立ち上げたことおよび自分が兼務で務めていたDELISHKITCHEN開発部長の交代についてお話させていただきます。 リテールハブ開発部の設立 このたび、10/1でリテールハブ開発部を新設し、そこの開発部長を私が務めることになりました。 リテールハブ開発部は、これまでDELISHKITCHEN開発部で手掛けてきた小売向けのサービス開発に注力をする新しい部門となります。 先んじて、ビジネス側はすでにリテールハブカンパニーとして切り出されていたところに合わせて、開発部も切り出すことを決断しました。 私たちの会社は、これまで多岐にわたる分野でサービスを提供してきましたが、小売業界は特に大きな成長が期待される分野の一つです。 お客様のニーズが日々変化する中で、より迅速で柔軟な対応が求められるようになりました。 そこで小売業界向けのサービス開発にさらに集中し、その分野におけるリーダーシップを強化するため、リテールハブ開発部を新たに立ち上げました。 組織図 この新しい部門では、既存の「ストアDX」「ネットスーパー」「小売アプリ」の開発を推進し、 『retail HUB』として統合ソリューションの提供を行うとともに、リテールメディアの構築を行っていくことに注力します。 私自身も、これまで培ってきた経験を最大限に活かし、チームと共により良いサービスを提供していけるよう努力していきたいと思っています。 新任DELISHKITCHEN開発部長の紹介 私がこれまで担当してきたDELISHKITCHEN開発部の新しい部長には、村上が就任します。 村上は、新卒で弊社に入社して以来、驚異的なスピードで成長を遂げてきた期待のエンジニアです。 彼の優れた技術力とリーダーシップは、すでに多くのプロジェクトで発揮されており、私たちのチームにとってなくてはならない存在です。 短期間で当時MAMADAYS開発部(現在のトモニテ開発部)のサーバーサイドのマネージャーに就任したのちに、DELISHKITCHEN開発部の副部長として主に広告商品の開発を一手に引き受けるなど大きな役割を果たしてきました。 彼の強みは、単なる技術力だけでなく、チームを引っ張るリーダーシップと、メンバーの意見を尊重しながらも、全体の方向性を見極めて決断する判断力にあります。 DELISHKITCHEN開発部は、新たなリーダーのもとで、さらに力強いチームへと進化していってくれると確信しています。 開発部長を彼に引き継げることは、私自身とても嬉しく思っています。 DELISHKITCHEN開発部の今後 今後村上が率いることになるDELISHKICHEN開発部は、これからも私たちの会社にとって軸となるサービスを運営・発展させるという重要な役割を担い続けます。 特に、小売向けのサービス開発をリテールハブ開発部に引き継いだことで、AIの活用をはじめとし、よりチャレンジングな領域でのイノベーションを生み出すことが期待されています。 村上のリーダーシップのもと、DELISHKITCHEN開発部は新たな成長を遂げ、多くのお客様に価値あるサービスを提供していけるよう邁進していきます。 また、組織は別れたものの、DELISHKITCHENとリテールハブは、それぞれの開発部が独立しているわけではなく、 システム連携や、リソースの共有や協力を行うことでより良いサービスを提供することができると考えています。 両部門の緊密な連携により、相乗効果を生み出し、「食」の領域をリードしていくことを目指していきます。 最後に 今回の新しい開発部の設立および部長交代を通じて、私自身も新たなステージで挑戦を続けていく決意を新たにしています。 リテールハブ開発部での取り組みを通じて、小売業界に革新をもたらし、より多くのお客様に価値あるサービスを届けていけるよう尽力していきます! また、どちらの部門も進めたいことが増えていく中で、一緒に取り組んでいける仲間がまだまだ足りません! ぜひ一緒に日本の「食」の領域をリードするサービスを作っていきましょう! corp.every.tv
アバター
はじめに こんにちは。DELISH KITCHEN 開発部 RHRA グループ所属の池です。 2024年6月、エブリーは5つの小売アプリの運営について事業譲渡を受け、『 retail HUB 』へ移管しました。 prtimes.jp この事業譲渡において、私はシステムに関するデューデリジェンス(以下、システムDD)を担当しました。 今回 retail HUB へ移管したシステムは具体的には 5つプロダクトそれぞれにおける、iOS/Androidのネイティブアプリと、入稿管理画面の Web アプリケーションサーバー、アプリ向け API サーバー、それらを構成するシステム(AWS環境、ロードバランサー、データベース、バッチサーバーなど)です。 システムDDは譲渡されるIT資産の把握やリスクの確認を行う重要なプロセスですが、方法や手順が明確に決まっているようなものではなく、どう進めるべきか迷うことが多くありました。 そこで、本記事では、今回私が行ったシステムDDの具体的な手順の実例について紹介します。進め方に明確な正解のないシステムDDについて、手順の実例を共有することで、同様のプロセスを進める方々の参考になれば幸いです。 システムデューデリジェンスとは システムデューデリジェンスとは、事業譲渡やM&Aの際に、譲渡対象となるシステムやIT資産の現状を詳細に調査し、リスクを特定するプロセスです。主に、譲渡後に予期しない障害やコストの増加を防ぐために実施されます。 システムDDの手順実例 私が行ったシステムDDの手順を振り返ると、以下の手順に整理できました。 事前準備 前提・目的の認識合わせ 調査範囲の明確化 調査 システムの満たしたい状態の定義 ドキュメントの満たしたい状態の定義 システム評価 調査項目の洗い出しとグルーピング アプリケーション(iOS/Android/サーバー) インフラストラクチャ(AWS) 現場エンジニアへのヒアリング 調査後 見つかった課題・リスクにおける対応の整理 ここからはそれぞれの手順について詳細を紹介します。 事前準備 前提・目的の認識合わせ システムDDを進めるにあたり、まず最初に取り組んだことは事業譲渡における前提や目的の認識合わせです。 システムDDは社内でも初めても取り組みでもあることに加え、デューデリジェンスは実際にはその時の事業状況や、事業譲渡の目的などによって具体的な調査範囲・内容は変わり得るため、認識を合わせる必要があると考えました。認識合わせは当たり前なことですが忘れてはいけない大切な作業だと考えています。 今回の場合、事業譲渡がプロダクト単位で複数のプロダクトが譲渡対象であることや、各プロダクトの所有権や権利等の状況が異なる状況であること、のようなことが前提となります。 そして、主な目的は、譲渡対象となるIT資産の現状を把握し重大なリスクを特定することです。特に、譲渡後の運用が想定通りの工数で行えるか、また運用に影響を与える潜在的な課題やリスクを洗い出すことが重要でした。さらに、前提を踏まえると譲渡対象のソフトウェア資産を特定することも大事な目的になります。 調査範囲の明確化 以上の前提と目的を踏まえ、以下の点が重要となります。 プロダクト固有のリスクの評価 運用に要する工数の把握、および運用に影響するリスクの特定 譲渡対象のソフトウェア資産の洗い出しと特定 これらの点を踏まえて調査範囲を明確化し、システムDDを行いました。 調査 ここからは具体的に行った調査方法について紹介します。 システムの満たしたい状態の定義 運用保守への影響確認という前提の観点を踏まえて、譲渡後にエブリーが運用保守していくあたり、困難なく開発・保守・運用できるかどうかという観点で、システムの満たしたい理想状態を定義し、その状態との差分を確認していくことで調査および評価を行いました。 例えば、「バグ検知」という調査の大項目については以下のように定義しました。 エラー検知の仕組みが導入されていてバグやシステム異常の発生が即時に検知・通知されること 各システムの各種メトリクス監視 アプリのクラッシュ計測 など... このような定義との差分をドキュメントや、実際のソースコードおよびシステム構成の確認、現場エンジニアへのヒアリングなどを通して確認していきました。 また、満たしたい状態を定義することで、調査の際に確認すべきポイントが明確になります。 システム状態定義のイメージ ドキュメントの満たしたい状態の定義 システムの状態定義と同様に、ドキュメントについても運用保守という観点を踏まえて期待する状態を定義し、その状態との差分を確認していくことで調査および評価を行いました。 例えば、「DB設計書・ER図」というドキュメントについては以下のように定義しました。 下記の内容を把握でき、結果としてDBの運用・改修が可能な状態 各物理データベース 用途 スペック DBMS ソフトウェアバージョン 各種メトリクスの閲覧方法 異常時の検知手法・対応手法 各論理データベース 各テーブルの構成、ER 図 各テーブルやビュー・ストアド等の用途 各テーブルやビュー内のカラムの定義 以下のようなドキュメントについて、同様の状態定義を行い、評価していきました。 外部システム連携仕様書 機能一覧 要件定義署 機能仕様書 API設計書 DB設計書・ER図 ログ設計書 各種アカウント情報 環境構築手順書 リリース手順書 管理画面操作手順書 画面一覧・画面遷移図 デザイン バッチ処理概要 レポート作成マニュアル など... ドキュメント状態定義のイメージ システム評価 システム評価では、上記の満たしたい状態を踏まえつつ、調査項目を洗い出してグルーピング行い、それぞれの調査項目についてGitHubやAWS環境などに招待いただいて閲覧操作することで評価を行いました。 アプリケーション(iOS/Android/サーバーなど) アプリケーションの評価は、以下のグルーピング項目に基づいておよそ40項目ほどの調査項目を確認していきました。 確認対象のリポジトリは約30個ほどあり、膨大な作業量であったため、現場エンジニアへのヒアリングを交えながら効率的に調査を進めました。 評価のグルーピング項目 ソースコード品質 設計 プロセス セキュリティ パフォーマンス 構成要素 具体的な調査項目(一部抜粋) ソースコード品質 コードベースの長さ コードベースの複雑さ テストコードのカバレッジ エラーハンドリング 適切なコメントの有無 一定期間内のエラー件数 既知の不具合の数 コーディング規約の有無 言語/FW/ライブラリのバージョン など... 可能であれば、全てのソースコードを実際に動作させて運用保守への影響を確認することが望ましいです。 アプリケーション評価項目のイメージ インフラストラクチャ(AWS) インフラストラクチャの評価は、AWS環境に関する以下のグルーピング項目に基づいて行いました。AWSアカウントに招待していただき、各項目に沿って確認しました。 アプリケーション評価と同様、現場エンジニアへのヒアリングを交えながら調査を進めました。 評価のグルーピング項目 アーキテクチャ設計 セキュリティ コスト管理 運用・保守 具体的な調査項目(一部抜粋) アーキテクチャ設計 VPC設計 ネットワーク構成 データベース設計 コンテナ化の有無 IaCの有無 CI/CD構成 ログ収集とモニタリング セキュリティ IAMポリシーとロールの設定 セキュリティグループとネットワークACLの設定 データの暗号化 ログ管理と監視 現場エンジニアへのヒアリング 上述の調査での不明点や把握しきれない点について、現場エンジニアの方々からヒアリングを行いました。 現場のエンジニアの方々は、システムの現状をよく理解しているため、ヒアリングを通して価値のある情報を多く早く得ることができます。 ヒアリングは必須の作業として行うと望ましいです。できればシステムDDの早い段階で行い、かつ定期的に行うことで効率的に調査を進めることができると思います。 見つかった課題・リスクにおける対応の整理 以上の調査を行った結果、見つかった課題・リスクを一覧化し、それぞれに対して影響範囲を整理しました。 その内容をもとに先方と対応方針について協議を行いました。 やっておけばよかったこと 譲渡後に運用保守を始めから振り返ってみて、システムDDでいくつかやっておけばよかったと感じたことがありました。主に運用してみて想定外の工数に繋がったものです。 システムの移管作業を想定した調査観点の追加 既知の不具合に対する詳細と影響範囲の把握 など... 譲渡契約後にシステムを当社に移管する作業を行う進め方をしましたが、実際に移管作業を始めると、移管するための事前作業で多くの工数を要するものがあることがわかりました。 例えば、iOSアプリの移管について、キーチェーンやAppleでサインイン機能を利用しているiOSアプリを別組織に移管すると、それらの機能が一時的に利用できなくなることがわかりました。これらをユーザー影響なく移管するには多くの工数を要するため、譲渡時には想定していなかった想定外の工数となります。 また、把握していた既知の不具合が譲渡後に想定外の影響を及ぼし、改修するのに多くの工数を使ったケースがありました。事前に不具合の詳細と影響範囲の把握を行っていれば、多少は事前に織り込めたと思います。 まとめ 今回の経験を通じて、事業への影響を最小限に抑えるために事前にシステムDDを行うことの重要さを再認識したとともに、観点やノウハウなど知見を貯めることができました。 また、5つのプロダクトを同時に移管するという稀有な経験から、私自身の成長として、未知で決まった答えのない領域に対して試行錯誤しつつ最大限の決定を繰り返して推進していく能力が向上したと感じました。 本記事が少しでもどなたかのお役に立てれば幸いです。
アバター
はじめに こんにちは、トモニテ開発部ソフトウェアエンジニア兼、CTO 室 Dev Enable グループの rymiyamoto です。 この度、エブリーは 2024 年 10 月 19 日(土)に開催される『Vue Fes Japan 2024』に、ゴールドスポンサーとして協賛することになりました! vuefes.jp エブリーでは、DELISH KITCHEN を現在 Nuxt.js(Vue.js)で構築しており、2018 年から採用しています。 今回の協賛を通して、さらなる Vue.js コミュニティの発展に貢献できればと考えております。 近年は Vue.js 周辺のエコシステムでは新たな潮流も生まれつつあり情報交換や交流を通じて、新たな出会いや気づきを得ることができるでしょう。 ぜひ、タイムテーブルをご覧いただき、気になるセッションに参加してみてください。 vuefes.jp エブリーにおける Nuxt.js(Vue.js) の活用 Nuxt.js 導入の背景 エブリーでは、DELISH KITCHEN の Web を最初 Riot.js という SPA ライブラリと クローラー向けには静的な HTML を返すため、Express を使って SSR を行っていました。 この構成上コードが二重管理されており、新規ページや機能の開発、運用コストが大きくなっていました。 このような課題を解決するために、Universal アプリケーション(SSR + SPA)の開発が可能な技術を検討し、Angular Universal、Next.js、Nuxt.js の中から Nuxt.js を選択しました。 選定の決め手は以下の通りです。 バックエンドに強いチーム構成で、Web フロント開発に長けたメンバーが少なかったため、Nuxt.js のような薄いフレームワークが取り掛かりやすいと考えた。 チーム内に Vue.js の開発経験者がいた。 Riot.js の SFC(Single File Component) の構造が Vue.js のコンポーネントと似ていたため、システムリプレースが容易だった。 移行後のメリットとして、ユーザー向けとクローラー向けのコードの二重管理がなくなり、一元管理が可能になりました。 これにより開発や QA のコスト削減が実現され、Nuxt.js(Vue.js)の豊富なドキュメント、ライブラリ、活発なコミュニティの恩恵を受け、開発の問題解決が容易になりました。 詳細については、以下の記事をご参照ください。 tech.every.tv 他にもエブリーのテックブログでは、Vue.js に関する記事を随時公開しています。 Nuxt3 化に向けた取り組み 2018 年から Nuxt.js を採用してきたエブリーでは、現在 Nuxt3 化に向けた取り組みを進めています。 こちらも合わせてご覧ください。 tech.every.tv tech.every.tv tech.every.tv Vue.js の開発効率化 Vue.js を活用して開発開発効率化を図るために、VueUse を扱った内容も公開しています。 tech.every.tv tech.every.tv 皆様とお会いできることを楽しみにしています! 私たちのブースでは、Vue の活用事例等をご紹介する予定です。 またブースには素敵なノベルティもご用意しております。詳細はまた追ってお知らせいたしますので、ぜひ、お気軽にお立ち寄りください! Pre Party を共同開催します! エブリーでは、Vue Fes Japan 2024 を盛り上げるべく、同じくスポンサーである OPTiM さんと共同で Pre Party を開催します! optim.connpass.com LT(ライトニングトーク)が主体のカジュアルなイベントとなっております。 募集枠は全部で 4 つありますので、ぜひ奮ってご応募ください! Vue Fes Japan 2024 の参加予定の方や、惜しくも参加できない方も、ぜひご参加ください! こんな方におすすめです! Vue Fes Japan 2024 に向けて、知り合いを増やしたい方 Vue Fes Japan 2024 のプロポーザルがリジェクトされた、敷居が高いと感じた方で LT で発表してみたいなと思っている方 Vue に興味があるエンジニアや学生の方 Vue での開発に従事されている方 OPTiM、エブリー がどのように Vue を活用した開発をしているのか興味がある方 OPTiM、エブリー のエンジニア組織について興味がある方 Vue を中心に知識と交流の輪を広げましょう! ※本勉強会/LT会はVue Fes Japan公式のものではなく、スポンサー同士の有志のイベントとなります。そのため本勉強会へのお問い合わせをVue Fes Japan様へ行うことはご遠慮ください。 エブリーでは、ともに働く仲間を募集しています。 エブリーでは、ともに働く仲間を募集しています。 テックブログを読んで少しでもエブリーに興味を持っていただけた方は、ぜひ一度カジュアル面談にお越しください! corp.every.tv X ではテックブログや登壇情報、インタビュー記事などエブリーのエンジニアに関する情報を発信していますので、ぜひご覧ください。 https://x.com/every_engineer 最後までお読みいただき、ありがとうございました!
アバター
はじめに こんにちは、DELISH KITCHEN でクライアントエンジニアを担当している kikuchi です。 昨今 AI がますます普及し業務で AI を活用する事例も増えてきましたが、Google が提供している Gemini が Android Studio の一機能として提供されていることをご存知でしょうか? 2024/9/13 時点ではまだプレビュー版ではありますが無料で公開されていますので、今回は実際に使用した際の環境構築手順、使用感などをまとめてみたいと思います。 Gemini の詳細は今回割愛しますが、Gemini は大きく分けて無料版の Gemini、有料版の Gemini Advanced と分かれており、現時点では Android Studio は無料版の Gemini と連動しているようです。 事前準備 Android Studio と Gemini を連携する場合は Google アカウントが必須となりますので、事前に作成をしておいてください。 なお、プレビュー版では Gemini Advanced への加入状況は反映されないため、Gemini Advanced への加入は不要です。 環境構築手順 公式サイト から Canary 版 (2024/9/13 時点では Ladybug) をダウンロードし、Android Studio を起動します プロジェクトを生成後、右上にある Gemini のアイコンをクリックします 「Log in to Google」をクリックするとブラウザが起動するためブラウザで認証を通し、完了後に「Next」をクリックします プライバシーポリシーを確認し、「Next」をクリックします 利用規約を確認し、チェックを付けて「Next」をクリックします プライバシー設定を確認し、任意の項目にチェックを付けて「Finish」をクリックします 以上で Gemini の機能を使用できる準備が完了しました。 操作方法について 操作方法はコメント入力欄に質問内容を入力し 「Submit」 をクリックすると AI が回答をしてくれるという形で、通常のブラウザ上で操作するものと大きな違いはありません。 以下は 「ログイン画面のソースファイルとレイアウトファイルを作成して」 と質問した時の回答です。 AI のサービスを使用したことがある方は違和感なく使用できるかと思います。 ファイル操作 Android Studio の Gemini では通常のブラウザ上で使用するようなチャット形式でやり取りが出来る点に加え、生成したソースコードに対して特殊な操作が行えるようになります。 上記は実際に Android Studio 上の Gemini で生成したソースコードの直下に表示されるアイコンですが、左から順に Copy → ソースコードをコピーする Insert at Cursor → カーソルが当たっている箇所に該当のソースを挿入する Insert in New Kotlin File → ソースコードを新規ファイルとして生成する (レイアウトファイルの場合はレイアウトファイルとして新規で生成する) Explore in Playground → 生成されたコードを Playground 上で確認する といった操作が行えます。 1、2、4 についてはソースコードのコピーやコピー&ペーストを一括で実施してくれる機能となりますが、3 についてもう少し細かく動きを見てみたいと思います。 Insert in New Kotlin File の実施例 実際に回答結果で出力されたソースファイルの「Insert in New Kotlin File」を実行してみたいと思います。 ファイルを追加したいディレクトリを選択した状態で「Insert in New Kotlin File」のアイコンをクリックします。 実行後、ファイルが自動的に追加された上、 androidx.appcompat:appcompat:1.7.0 のライブラリが不足していることを検知し、追加するかの提案もしてくれます。 「Add」 をクリックすると build.gradle に追加してくれるだけでなく、Version Catalog でライブラリを管理している場合は libs.versions.toml にも変更を加えてくれます。 1 つ注意点としては、AndroidManifest.xml には自動的に定義が追加されないため、手動で activity の定義を追加する必要があります。 1 つ手動操作が必要なものの、ソースコードはファイル生成からライブラリの導入までスムーズに行うことが出来ました。 では引き続きレイアウトファイルの生成を行ってみます。 レイアウトファイルの場合はアイコンの説明が「Insert in New Layout File」になるため、そちらのアイコンをクリックします。 レイアウトファイルの場合はここで 2 つほど問題が発生してしまいます。追加後の状態は以下の画像のようになります。 問題の 1 つ目、どのディレクトリを選択していても res ディレクトリ直下に「new.xml」というファイル名で生成されてしまうため、手動でリネームとパス移動が必要になります。 そして 2 つ目、自動生成されたレイアウトファイルは ConstraintLayout を使用していますが、このプロジェクトでは androidx.constraintlayout:constraintlayout のライブラリを設定しておらず、 こちらはソースコードの場合と違ってライブラリが不足していることを検知しませんでした。 レイアウトファイルの自動生成については、手動操作、エラーの解析、ライブラリの追加が必要となってしまいます。 本項でファイルの自動生成を試してみましたが、ソースファイルの生成は容易なものの、レイアウトファイルの生成にはまだまだ課題がありました。 コード補完 前項ではチャット形式でのやり取りについて説明しましたが、実際のソースコードのドキュメント作成や補完を行えるコード補完の機能についても触れたいと思います。 先ほど自動生成したファイルのメソッドで右クリックをすると以下の機能を選択できます。 こちらは上から順に Document Function "<メソッド名>" → メソッドの KDoc を作成する Comment Code → 処理にコメントを付ける Explain Code → 処理の解説をする Suggest Improvements → 処理の改善案を提示する Rethink variable names → 無反応のため不明 (直訳だと変数名の再定義) となっており、どれも該当のソースコードに特殊なプレフィックスを付けてチャット欄に貼り付けるものでした。 最終的には通常のチャット形式でのやり取りになるため、3 のみ試してみたいと思います。 Explain Code の実施例 実際にメソッドの部分で Explain Code を選択すると以下の警告が表示されます。 解析のために Gemini のサーバーにソースコードを送信してよいかという質問になります。機密情報を含むソースコードの取り扱いには注意が必要なため、適切な判断をする必要があります。 今回は先程 Gemini で自動生成したソースコードのため、そのまま送信をしてみます。 プレフィックスとして Explain the following code: というものが付いて、後は丸々ソースコードがチャット欄に貼り付けられる、という挙動になりました。 あとは「submit」をクリックします。 ソースコードの解析結果がチャット欄に出力されました。 こちらはチャット欄で「コードを解析して」といった文章を入力したり、ソースコードをコピー&ペーストする手間を省く効果が期待できそうです。 注意点 おそらく 1 時間に 10 回程度質問のやり取りをしたところで上限に達してしまいました。1 時間程度待てば再度使用できるようになりましたが、ここは現時点で無料版を使用しているための制限になるかと思います。 実際の使用例 色々と試した中で一番便利に感じたのは、ファイル操作の「Insert at Cursor (カーソルの位置に自動挿入)」と「Insert in New Kotlin File (ファイル自動生成)」を組み合わせて使用する形でした。 具体的な例を記載してみます。 「ログを出力する Utility クラスを作成して」と質問し、出力結果で「Insert in New Kotlin File」を実行しファイルを自動生成する 「verbose のログを出力するメソッドを作成して」と質問し、出力結果で「Insert at Cursor」を実行しクラスを拡張する 手動で実装することなくチャットによるやり取りのみでクラスの生成、クラスの拡張を行うことが出来ました。 ビジネスロジックを含むようなクラスの生成は難しいかもしれませんが、Utility クラスや data クラスなど単純なメソッドの組み合わせになるようなクラスであればこちらの方が速く実装できる可能性があります。 まとめ 今回 Android Studio の Gemini 機能を使用してみましたが、ブラウザと Android Studio を行き来する手間もなくなり、ファイルの自動生成など Android Studio の操作の手間をかなり軽減し、 また最低限の知識さえあればソースコードをほぼ書かずに実行ができる状態まで作れるため、開発のサポートとしては非常に強力だと感じました。 ただし、前述の通りまだプレビュー版のため、無料版の Gemini を使用していることで回答が返るまでに 10 秒程度かかってしまう点、レイアウトファイルの操作は完全に自動化されていないなど、 まだまだ課題も目立っています。 今後どう改修されていくかは分かりませんが、結局は Android 開発の正しい知識は必須であることには変わりないため、あくまで開発の補助に留めて使用するのがベストかと感じました。 今回紹介した内容が少しでも皆さまのお役に立てれば幸いです。
アバター
【2024最新】AWS Data Firehoseを使った際の4つの問題とその解決策 背景 こんにちは、開発本部 DELISH KITCHEN Retail HUB NetSuperグループに所属するフルスタックエンジニアをやらせていただいています、ホーク🦅アイ👁️です。2024/2/9、 Amazon Kinesis Data Firehose から Amazon Data Firehose に名称変更されてから半年ほど経過しておりますが最新の設定情報などが公開されていることが少ないと感じたため今回記事を書くに至りました。 前提 今回の記事を書くにあたっての前提条件は以下のようになっております。 ECS Fargate上にWEBアプリケーションコンテナが存在し、そのコンテナは標準出力にアクセスログを出力している アクセスログは、JSON形式ログとスペース区切り形式ログが混在している # JSON形式ログ例 {"message": "access", "key": "value"} # スペース区切り形式ログ例 127.0.0.1 - - [09/Sep/2024:01:54:49 +0000] "GET /healthcheck HTTP/1.1" 200 236 "-" "curl/7.74.0" TaskDefinitionで logDriver を awslogs に設定しその標準出力はCloudWatch Logsに常に転送されている 主な作業を調査・実施した年月日は2024年7月12日 要件 要件1 WEBアプリケーションログはJSON形式ログとスペース区切り形式ログが混在しているのでそれぞれを別のS3プレフィックスに保存したい 要件2 JSON形式ログは{unique_code}/日付でパーティションをして1行ずつ改行されるようにS3保存させたい スペース区切り形式ログはノンパーティションで1行ずつ改行されるようにS3保存させたい 但し、これらは自前のLambdaを使わずに行うこと! 実装手段 要件1に対する実装 CloudWatch Logsのサブスクリプションフィルターを使うとログの種類を2つに分けることができます。 コンソール上にも注意書きがありますがサブスクリプションフィルターはLog Groupごとに2個までしか設定できないので注意してください。以下、Terraformで3つ目を適用した時のエラーです。 Error: putting CloudWatch Logs Subscription Filter (XXXX): operation error CloudWatch Logs: PutSubscriptionFilter, exceeded maximum number of attempts, 25, https response error StatusCode: 400, RequestID: XXXX, LimitExceededException: Resource limit exceeded. Terraformを使って反映 # pointだけ絞ってピックアップ resource "aws_cloudwatch_log_subscription_filter" "application_log" { name = "application-log" role_arn = var.subscription_filter_webserver_arn log_group_name = aws_cloudwatch_log_group.webserver.name destination_arn = var.kinesis_firehose_stream_applicationlog_arn filter_pattern = "{$.message = \"access\"}" } resource "aws_cloudwatch_log_subscription_filter" "access_log" { name = "access-log" role_arn = var.subscription_filter_webserver_arn log_group_name = aws_cloudwatch_log_group.webserver.name destination_arn = var.kinesis_firehose_stream_accesslog_arn filter_pattern = "\" \"" } スペース区切り形式ログのfilter_patternは半角スペース1文字で可能でTerraformで記述するときはダブルクォーテーションで括る必要があります。 要件2に対する実装 Terraformを使って反映 # pointだけ絞ってピックアップ dynamic_partitioning_configuration { enabled = "true" } processing_configuration { enabled = "true" processors { type = "RecordDeAggregation" parameters { parameter_name = "SubRecordType" parameter_value = "JSON" } } processors { type = "MetadataExtraction" parameters { parameter_name = "JsonParsingEngine" parameter_value = "JQ-1.6" } parameters { parameter_name = "MetadataExtractionQuery" parameter_value = "if .context.unique_code then {unique_code: .context.unique_code} else {unique_code: \"NONE\"} end" } } processors { type = "Decompression" parameters { parameter_name = "CompressionFormat" parameter_value = "GZIP" } } processors { type = "CloudWatchLogProcessing" parameters { parameter_name = "DataMessageExtraction" parameter_value = "true" } } processors { type = "AppendDelimiterToRecord" } } JSON形式ログの方は、Dynamic Partitioning(動的パーティショニング)を使えばJSONパースが行われてS3のプレフィックスにパーティションされます。この際、1ログごとに付与される改行コードがなくなってしまいます。そこで改めてDestination(送信先)データに改行コードを付与するためにProcessorsのAppendDelimiterToRecord typeを設定する必要があります。Terraformで適用するとコンソール上では「 New line delimiter(改行の区切り文字) 」がenabled(有効)になります。一方で、スペース区切り形式ログの方はDynamic Partitioningを使う必要がないのでTransform and convert records(レコードを変換および転換)機能を行うだけで改行コードが付与されているので改めてAppendDelimiterToRecordを設定する必要はありません。 問題 問題1 Dynamic PartitioningのJSONパースにおいて指定したパーティションキーにNULL値があるときパーティション作成に失敗していた "errorCode":"DynamicPartitioning.MetadataExtractionFailed","errorMessage":"partitionKeys values must not be null or empty" 問題2 Firehose自体のエラーログに以下のエラーメッセージが出てS3保存に失敗していた errorCode":"DynamicPartitioning.MetadataExtractionFailed","errorMessage":"Non UTF-8 record provided. Only UTF-8 encoded data supported" 問題3 terraform applyを実行すると以下のエラーメッセージが出て適用に失敗した operation error Firehose: UpdateDestination, https response error StatusCode: 400, RequestID: d7b3d246-ecb3-a51f-88ba-1557ca6eae2a, InvalidArgumentException: Enabling source decompression is not supported for existing stream with no Lambda function attached. Terraform provider hashicorpのバージョンを5.44->5.58(当時の最新版)に上げるも変化なし 問題4 Athenaでクエリ発行するときに、0件ヒットになってしまう JSONコンテンツのみデータ取得できていない感じ 結論、S3に保存しているデータはGZIP圧縮されているが拡張子が.jsonなのでそれを認識できていないため解凍せずにそのまま読み込もうとしている様子 拡張子を.gzにしたら読み込めた プレーンテキスト状態の.jsonファイルと圧縮状態の.gzファイルを混在しても両方同時に読み込めていた 解決策 問題1に対する解決策 JQのif-then-else制御構文を使うことでNULL値を特定の固定文字列に置換してしまえば解決できました。以下のようにTerraformでparameter_value値に記述して適用します。尚、この値は長文なので実際にコンソール上で設定画面のDynamic partitioning keys表示を見ても文字列全文は出てこないですが正しい挙動になるので問題ありません。 parameter_value = "if .context.unique_code then {unique_code: .context.unique_code} else {unique_code: \"NONE\"} end" 問題2に対する解決策 原因調査をするにあたって、ChatGPTに質問してその情報を足がかりに進めることにしました。 質問スクリプト "errorCode":"Lambda.ProcessingFailedStatus","errorMessage":"ProcessingFailed status set for record" というエラーが出て先ほど教えたJSONデータログをutf-8変換に失敗しました。なぜでしょうか? 回答 ・Firehoseに渡す前にLambda関数でutf-8に変換しておく ChatGPTの回答を受けて「結局Lambdaを間に挟まないとダメなのか?!」と疑問視しつつ、該当するBluePrintのLambda関数をReadingするとそもそもCloudWatch LogsからFirehoseに渡る過程でサブスクリプションフィルターを利用するとデータ構造が生ログではなくなっていることが判明しました。つまり、Firehoseに渡るデータは何もしないと圧縮データかつJSON構造変更、そして元ログデータ自体がBASE64エンコード化していました。故に、解凍、パース、デコード処理が必要になるということでした。 { " messageType ": " DATA_MESSAGE ", " owner ": " 123456789012 ", " logGroup ": " log_group_name ", " logStream ": " log_stream_name ", " subscriptionFilters ": [ " subscription_filter_name " ] , " logEvents ": [ { " id ": " 01234567890123456789012345678901234567890123456789012345 ", " timestamp ": 1510109208016 , " message ": " log message 1 " } , { " id ": " 01234567890123456789012345678901234567890123456789012345 ", " timestamp ": 1510109208017 , " message ": " log message 2 " } ... ] } 参考) Transform source data in Amazon Data Firehose - Amazon Data Firehose 便利なサブスクリプションフィルターを使わない選択肢はないので、これを使いつつパースするFirehoseの新たな機能が2024年2月27日にリリースされたようでその機能を使うことで自前でLambda関数を用意せずにFirehoseの機能だけで解決させることができました。 Amazon Data Firehose に解凍された CloudWatch Logs のメッセージ抽出機能を追加 具体的には、コンソールで言うところの特定Data Firehoseのconfigurationページを開いて「Transform and convert records」→「Decompress source records from Amazon CloudWatch Logs」と「Extract message fields only from log events」をONにします。 ちなみに、 Firehose新機能の内部構造は結局Lambda関数を使っているようです(メッセージ抽出機能自体は追加料金はありませんがおそらくCloudWatch Logsからのソースレコード解凍機能部における追加料金はLambda関数料金分なのではないかと推測)。 Amazon Data Firehose Firehose streams Configuration 問題3に対する解決策 結論としては、Terraformでresourceのattributeであるprocessing_configuration内でDecompressionとCloudWatchLogProcessingの2つのtypeのみを設定すれば解決策2の設定が適用されます。しかし原因はよくわからないですがTerraformでDecompressionとCloudWatchLogProcessingを設定せずにFirehoseリソースを新規作成した後に設定変更という形で前述の2つのtype設定をTerraformで更新適用しようとするとエラーになった(plan時はエラーは出ない)ので途中で変更する場合は工夫が必要ということが判明しました。以下、具体的な工夫手順です。 まず適当に既存のLambda関数を用意した上でそのLambdaを使って変換処理を行う記述も追記して適用 その後、Lambda関数による変換記述をコメントアウトなどしてそのprocessors部分だけ削除更新するterraform applyを実施することが可能なのでそれを適用 processors { type = "Lambda" parameters { parameter_name = "LambdaArn" parameter_value = "arn:aws:lambda:$ { region } :$ { account_id } :function:$ { parse_func_name } :$LATEST" } } 問題4に対する解決策 FirehoseでS3に転送する時に再圧縮して保存させているのでそのファイルが圧縮されたファイルであることをAthenaに認識させるためにはContent-Typeではなく拡張子を.gzにすることであると判明したため、その設定をTerraformに施し適用します。 resource "aws_kinesis_firehose_delivery_stream" "app_server_log" { name = "app-server-log" destination = "extended_s3" extended_s3_configuration { bucket_arn = var.access_logs_arn role_arn = var.iam_role_kinesis_stream_app_server_arn buffering_interval = 300 buffering_size = 64 compression_format = "GZIP" custom_time_zone = "Asia/Tokyo" file_extension = ".json.gz" # <= この部分! ここで、元々の経緯は、圧縮しているにもかかわらず。.json拡張子にしていた理由としてChromeブラウザでS3コンソールからダウンロードを実行した時、自動で圧縮ファイルを認識して解凍してローカルディスクに保存する。その時に拡張子.gzのままだとファイルを開いたときにエラーが出て開けないのでファイル名変更で拡張子を手動で.gz部分を削除して.json拡張子にさせてから開かないといけないという手間が発生することに起因します。 総括 結論 Amazon Data Firehose解凍機能のみを使ってCloudWatch LogsのログメッセージをS3に転送すること自体は他の記事でもありました。しかし、本記事のようにJSON形式ログの変換を追ってTerraformを使ってまとめている記事はなかったように思います。 Amazon Data Firehose解凍機能を使うべき理由は、公式DOCによるLambdaで提供しているblueprints関数テンプレートが非推奨(deprecated)となっていることにあります。ただし、2024年9月執筆時点では、未だLambda関数を作成するコンソールで該当のblueprintsを選択および作成できます。 Dynamic Partitioningを使う際に、JQのif-then構文を駆使してパーティションキーを柔軟に設定できました。 Amazon Data Firehose解凍機能をTerraformで適用する場合、注意点があることがわかりました。 現状の課題 Amazon Data Firehose解凍機能が有償である点ですが、blueprintsのLambdaを設置した時の料金と比較しても大差がないくらい安い点で現状は採用しております。もし完全に無償でこの辺りを構築する場合は、そもそもECS FargateでFluentdなどの外部ログエージェントを使って直接S3に転送するアーキテクチャを採用することになると思います。 みなさまの快適なログライフを! 参考 改行系記事 https://dev.classmethod.jp/articles/amazon-kinesis-data-firehose-transform-source-records-with-aws-lambda/ https://dev.classmethod.jp/articles/kinesis-data-firehose-dynamic-partitioning-json-parse/ https://dev.classmethod.jp/articles/the-idea-of-automatically-inserting-newline-codes-between-records-without-using-a-lambda-processor-in-amazon-kinesis-data-firehose-aws-cdk/ [アップデート] Amazon Data Firehose に CloudWatch Logs ログイベントからメッセージデータのみを抽出出来るオプションが追加されたので有効にしてみた | DevelopersIO インフラエンジニアが生成AIを活用してログ解析してみた 公式DOC Amazon Data Firehose に解凍された CloudWatch Logs のメッセージ抽出機能を追加 Dynamic partitioning in Amazon Data Firehose - Amazon Data Firehose Supported Lambda blueprints - Amazon Data Firehose Terraform関連 How to enable message extraction in the firehose cloudwatch decompression feature? Terraform Registry
アバター
はじめに 株式会社エブリーでソフトウェアエンジニアをしている桝村です。 本記事では、Nuxt 3 へのアップデートに向けて、Nuxt Bridge を使用して Nuxt 2 のアプリケーションへサーバーエンジン Nitro を導入したので、実施内容やそれによって得られた知見について紹介します。 この記事のゴールは、以下を想定しています。 Nitro の概要や、Nuxt 2 への Nitro 導入のメリットを把握する Nuxt 2 への Nitro 導入における変更点や考慮すべきポイントを把握する Nuxt 3 へのアップデートに関連して、Vuex の Pinia への移行については、以下の記事で詳しく紹介しています。 tech.every.tv サーバーエンジン Nitro とは サーバーエンジン Nitro とは、様々な環境で軽量な Web サーバーを構築できるライブラリのことです。 Vue や Nuxt 開発メンバーが中心のプロジェクト unjs が開発・メンテナンスしており、Nuxt 3 へデフォルトで組み込まれています。 UnJS は Unified JavaScript Tools の略で、JavaScript の開発をより効率的かつ柔軟に行うために設計された、一連のオープンソースライブラリおよびツールを提供しているプロジェクトです。 同様のライブラリとして、Nuxt 2 との互換性がある Express.js や Koa.js, Fastify などがあります。 詳しくは、以下のリンクをご参照ください。 nitro.unjs.io Nuxt での Nitro の採用 ここでは、Nuxt での Nitro の採用について、概要やメリットを整理します。 Nuxt での Server の構成や役割 Nitro が採用された Nuxt の Server の構成は以下のようになっています。 Nuxt の Server 構成 nuxt.com Server Engine: アプリケーションのサーバーを動作させるための基盤となる技術 Nuxt: Vue.js や SSR、状態管理 などの機能を提供する高レベルのフレームワーク Nitro: 軽量でポータブルな出力を生成するライブラリ h3: 軽量で高速な HTTP サーバーのライブラリ。Nitro の基盤技術 また、Nuxt の Server 側では以下をはじめとした責務を担っています。 サーバーのビルド・起動設定 API のルーティング初期化 HTTP リクエストの処理 初期 HTML のレンダリング 静的なサーバーサイドコンテンツの生成 (ex. サイトマップ) etc... 上記を踏まえると、Nuxt での Nitro の採用は、大きな変更であることが想定できます。 Nuxt への Nitro 導入のメリット Nuxt への Nitro 導入には以下のようなメリットがあります。 高速なサーバーレスポンス ハイブリッドレンダリングのサポート ホットリロードが高速 詳しくは、以下のリンクをご参照ください。 nuxt.com 実際の Nitro 導入による効果については、後述の結果と振り返りで紹介しております。 Nuxt 2 への Nitro の導入における変更点 前提 今回は、以下の技術スタックを持つ本番運用中のアプリケーションへの導入を想定しております。 Node.js v20.14.0 nuxt v2.17.2 @nuxt/bridge v3.0.1 express v4.17.1 本アプリケーションは、Nuxt Bridge を利用して Nuxt 2 の状態で Nuxt 3 への移行を進めており、今回は移行の一つであるサーバーエンジン Nitro の導入を行いました。 Nuxt Bridge とは、Nuxt 3 と上方互換性があり、Nuxt 3 の機能の一部を Nuxt 2 で利用できるようにするためのライブラリです。 nuxt.com 以降の内容は、公式の移行ガイドを参考にしながらも、実際に対応を進める中でハマったポイントや気づきを中心に紹介していきます。 開発サーバーの起動 Nuxt が Nitro を利用して開発サーバーを起動するには、CLI コマンド nuxi のインストール・利用が必要です。 前提として、 nuxi のインストール・利用には、Node.js のバージョン 18.0.0 以上が必要そうでした。 WARN Current version of Node. js ( 16 . 18 . 0 ) is unsupported and might cause issues. Please upgrade to a compatible version >= 18 . 0 . 0 . その上で、基本的には、以下のリンクを参考に進めていくことができます。 nuxt.com また、開発サーバーの起動設定について、コマンド nuxt では server オプションを利用していましたが、コマンド nuxi では devServer オプションの利用が必要なのもポイントでした。 export default defineNuxtConfig({ - server: { + devServer: { port: 3002, } }) nuxt.com 加えて、コマンド nuxt ではファイルの内容をバッファとして読み込んで渡す仕様でしたが、コマンド nuxi ではファイルのパスを文字列として直接渡す仕様に変更になっていました。より設定が簡潔になったと言えるでしょう。 export default defineNuxtConfig({ devServer: { port: 3002, https: { - key: fs.readFileSync(path.resolve(__dirname, 'server.key')), - cert: fs.readFileSync(path.resolve(__dirname, 'server.crt')) + key: './server.key', + cert: './server.crt' } } }) デプロイメント nuxi では build コマンドを実行することで、 .output ディレクトリにアプリケーションのビルド成果物を出力します。この成果物がサーバーを起動するためのエントリーポイントとなります。 また、デフォルトではポート 3000 でサーバーが起動するのと、上述の devServer オプションを参照しないので カスタマイズでポートを変更したい場合は、以下のように環境変数を利用してポートを指定する必要がありました。 PORT = 3002 node .output/server/index.mjs nuxt.com エンドポイント・ミドルウェアの設定 前提として、Nuxt 2 では express を利用してエンドポイントやミドルウェアを設定していたので、Nitro の導入にあたっては Nitro (h3) の API を利用するように書き換えることが基本的な方針でした。 Express.js から Nitro (h3) へ書き換えする場合 Nitro では h3 の defineEventHandler によりアプリケーションロジックを定義します。 コンテキストにあたる event インスタンスを受け取って、ロジックを実行する関数を定義することができます。 Express.js // server/api/test.ts export default ( req , res , next ) => { // ... Do whatever you want here next(); } Nitro (h3) // server/api/test.ts import { defineEventHandler } from "h3" ; export default defineEventHandler( async ( event ) => { // ... Do whatever you want here } ); nuxt.com 以下は、具体的な書き換え例になります。 Express.js import urlParse from "url-parse" ; export default ( req , res , next ) => { const host = req . headers . host ; const parsedUrl = new URL ( `https:// ${ host }${ req . originalUrl } ` ) ; const pathname = parsedUrl . pathname ; if ( pathname . match (/ . +\ / $ /)) { parsedUrl . pathname = pathname . replace (/ \ / $ / , "" ) ; res . writeHead ( 301 , { Location : urlParse ( parsedUrl ) . href }) ; res . end () ; } else { next () ; } } ; Nitro (h3) import { defineEventHandler , sendRedirect , getRequestURL , getRequestHost } from "h3" ; export default defineEventHandler (( event ) => { const host = getRequestHost ( event ) ; const parsedUrl = new URL ( getRequestURL ( event ) , `https:// ${ host } ` ) ; const pathname = parsedUrl . pathname ; if ( pathname . match (/ . +\ / $ /)) { parsedUrl . pathname = pathname . replace (/ \ / $ / , "" ) ; return sendRedirect ( event , `https:// ${ host }${ parsedUrl . pathname } ` , 301 ) ; } }) ; express では、リクエストオブジェクトから直接情報を取得しているのに対して、h3 では getRequestHost や getRequestURL のような関数を使用してリクエストから情報を取得しています。 それにより、関数の抽象化を通じてコードの可読性と保守性を向上させることに重きを置いていたり、Nuxt 3 で設計を刷新しようとしていることが垣間見えます。 Express.js のコードをそのまま利用する場合 express のコードでも、 fromNodeMiddleware() で変換することで Nitro (h3) でそのまま利用することができました。 Express.js // server/api/index.js import express from "express" ; const app = express () ; app . get ( "/api/test" , ( req , res ) => { res . send ( "Hello World!" ) ; }) ; export default app ; Nitro (h3) // server-middleware/api/index.js import express from "express" ; import { fromNodeMiddleware } from "h3" ; app . get ( "/api/test" , ( req , res ) => { res . send ( "Hello World!" ) ; }) ; export default fromNodeMiddleware ( app ) ; これにより、徐々に express から h3 ベースへコードの書き換えを進めることが可能です。 ただし、Nuxt として推奨されている機能ではないことは留意しておくと良さそうです。 nuxt.com 結果と振り返り 結果 Nuxt 3 への Nitro 導入した結果、体感の部分もありますが、今回のアプリケーションでは以下のような効果が得られました。 # ・ホットリロード: 約 20% 高速化 # ・開発サーバーの起動時間:約 30% 高速化 # ・サーバーのレスポンスタイム: 約 5% 高速化 サーバーのレスポンスタイムについて、今回は開発スピードを優先して主に express のコードをそのまま利用する方針で進めたので、モジュールの変換に伴うオーバーヘッドが影響している可能性があります。 なので、Nitro (h3) へ完全移行できるとさらに改善が見込めるかもしれません。 振り返り サーバーエンジン Nitro の導入に際して、Nuxt のサーバーの基盤技術の刷新でもあり、変更点が多くありました。 一方で、開発サーバーの起動時間やホットリロードの高速化など、開発効率の向上が期待できることもわかりました。 今後は、Nitro (h3) への完全移行を進めることで、更なるパフォーマンス向上や開発効率の向上を図っていきたいと考えています。 おわりに 今回は、Nuxt 3 へのアップデートに向けて、Nuxt Bridge を使用して Nuxt 2 のアプリケーションへサーバーエンジン Nitro を導入したので、実施内容やそれによって得られた知見について紹介しました。 これから Nuxt Bridge を使用して Nuxt 2 のアプリケーションへ Nitro の導入を検討している方にとって、参考になれば幸いです。
アバター
はじめに こんにちは。 株式会社エブリーの開発本部データ&AIチーム(DAI)でデータエンジニアをしている吉田です。 今回は、Text-to-SQLを実現するDatabricks Genieを紹介します。 Databricks Genie Databricks Genieは、自然言語を利用してデータ分析が行えるサービスです。 あらかじめデータ、サンプルクエリ、Genieへの指示を登録しておくことで、Genieに対して自然言語でクエリを投げることができます。 これにより、SQLに詳しくない人でもデータ分析を行うことができます。 AI/BI Genie Space とは何ですか? Genieを利用する Genieを利用するためには、以下の手順が必要です。 利用するデータをUnity Catalogに登録する Genie Spaceを作成する Genieをチューニングする 今回は弊社が提供しているレシピ動画サービス、DELISH KITCHENのデータを模したサンプルデータを用意し、Genieを利用してみます。 サンプルデータはランダムに生成したもので、実際のデータとは異なります。 サンプルデータをUnity Catalogに登録する サンプルデータは、以下のような構造です。 テーブル名 カラム 説明 user_master id age gender recipe_master id recipe_name is_premium プレミアムレシピかどうか viewed_video event_date user_id recipe_id seconds 動画視聴時間 referrer_screen 直前に見た画面 Unity Catalogへの登録 Genieではカタログのメタデータを利用してクエリを生成するため、テーブル/カラムのコメントを登録しておく必要があります。 登録にはAI Generate機能を利用することでデータの内容から適切なコメントを生成できるため、利用すると便利です。 コメントの自動生成 Genie Spaceを作成する Genie Spaceは、Genieの利用者がデータ分析を行うためのスペースです。 使用するテーブルやサンプルクエリ、使用するコンピュートリソースなどを指定して作成します。 Genie Spaceの初期設定 Genieをチューニングする Genieに対して、クエリの生成精度を向上させるためのチューニングを行えます。 ドメイン知識を追加したり、回答形式を指定する、あらかじめ質問とSQLをセットで登録し学習させるなど、Genieの精度を向上させる方法があります。 チューニング Genieでデータ分析をする 実際にGenieに対して、日本語で質問を投げてみます。 クエリの実行後、Show Generated Codeをクリックすると、Genieが生成したクエリを確認できます。 最初はシンプルな質問を投げてみます。 回答 よさそうです。 次はテーブルのJoinが発生する質問を投げてみます。 回答 よさそうです。 さらに複数のJoinが発生する質問を投げてみます。 回答 こちらも良さそうです。 では簡単な変換を伴う質問を投げてみます。 回答 うまくいきませんでした。 このように質問が正確に理解されない、または誤ったクエリを生成することがあります。 そういった場合はチューニングを行うことで精度を向上できます。 例えば以下のようなクエリを質問とセットで登録することで、Genieに正しいクエリを生成するよう学習させられます。 クエリと質問の登録によるチューニング この状態で同様の質問をしてみます。 回答 今度は正常にクエリが生成されました。 このようにチューニングを行うことで、Genieの精度が向上します。 まとめ 今回はDatabricks Genieを利用して、自然言語でデータ分析を行う方法を紹介しました。 Databricks Genieを利用することで、SQLに詳しくない人でも質問を入力するだけでデータ分析を行うことができます。 これにより、データ分析の敷居が下がりデータ活用が進むことが期待されます。
アバター
はじめに こんにちは。DELISH KITCHEN開発部の村上です。 直近は社内でAmazon Bedrockを使った RAG基盤の構築をしています。その中でちょうど先月AWSから発表された advanced RAG機能 の中のAdvanced parsing optionsを検証も兼ねて使用する機会があったので紹介します。 Advanced parsing optionsとは Knowledge baseではS3や他のデータコネクターを指定し、データソースを作成、同期することによってOpensearch ServerlessといったベクトルDBにデータを格納しています。データソースは さまざまなファイル形式をサポート していますが、今まではサポートしている形式であってもその解析精度に課題が残るものもありました。 今回のアップデートで追加されたAdvanced parsing optionsは有効化することによって、今まで課題であったPDFファイルのテーブルや表、グラフなど非テキスト情報も基盤モデルを使って解析してベクトルDBに埋め込むことができるようになります。 設定可能な値は二つのみでほぼ有効化のみですぐに試すことができます。 使用する基盤モデル 『Claude 3 Sonnet v1』 or 『Claude 3 Haiku v1 』 parserの指示プロンプト 英語のデフォルトプロンプトが設定済み 長いので折り畳みますが、デフォルトプロンプトはこのように記述されています。 プロンプト内容 Transcribe the text content from an image page and output in Markdown syntax (not code blocks). Follow these steps: 1. Examine the provided page carefully. 2. Identify all elements present in the page, including headers, body text, footnotes, tables, visualizations, captions, and page numbers, etc. 3. Use markdown syntax to format your output: - Headings: # for main, ## for sections, ### for subsections, etc. - Lists: * or - for bulleted, 1. 2. 3. for numbered - Do not repeat yourself 4. If the element is a visualization - Provide a detailed description in natural language - Do not transcribe text in the visualization after providing the description 5. If the element is a table - Create a markdown table, ensuring every row has the same number of columns - Maintain cell alignment as closely as possible - Do not split a table into multiple tables - If a merged cell spans multiple rows or columns, place the text in the top-left cell and output ' ' for other - Use | for column separators, |-|-| for header row separators - If a cell has multiple items, list them in separate rows - If the table contains sub-headers, separate the sub-headers from the headers in another row 6. If the element is a paragraph - Transcribe each text element precisely as it appears 7. If the element is a header, footer, footnote, page number - Transcribe each text element precisely as it appears Output Example: A bar chart showing annual sales figures, with the y-axis labeled "Sales ($Million)" and the x-axis labeled "Year". The chart has bars for 2018 ($12M), 2019 ($18M), 2020 ($8M), and 2021 ($22M). Figure 3: This chart shows annual sales in millions. The year 2020 was significantly down due to the COVID-19 pandemic. # Annual Report ## Financial Highlights * Revenue: $40M * Profit: $12M * EPS: $1.25 | | Year Ended December 31, | | | | 2021 | 2022 | |-|-|-| | Cash provided by (used in): | | | | Operating activities | $ 46,327 | $ 46,752 | | Investing activities | (58,154) | (37,601) | | Financing activities | 6,291 | 9,718 | Here is the image. これまでとの挙動の比較 実際にデフォルトの有効化されていない状態と比較しながら、Advanced parsingがどのようにPDFを解析しているのかを確認していきます。 今回はサンプルデータとして 情報通信白書令和5年版 のPDFをS3に入れて解析を行っています。 以下は使用している設定値です。 基盤モデル: Claude 3 Sonnet v1 parserの指示プロンプト: デフォルト グラフの解析 まず、p9の棒グラフを解析してみます。 PDFの中のグラフは、デフォルトの設定だと以下のように解析されました。デフォルトでも大きく崩れてはいませんが、それぞれの文字同士のつながりがわかりにくく、ひとつのグラフとして解釈するのは難しいかもしれません。 違法・有害情報センターへの相談件数の推移 0 1,000 2,000 3,000 4,000 5,000 6,000 7,000 平成22 平成23 平成24 平成25 平成26 平成27 平成28 平成29 平成30 令和元 令和2 令和4令和3 (年度) (件) 1,337 1,560 2,386 2,927 3,400 5,200 5,251 5,598 5,085 5,198 5,407 6,329 5,745 (出典)総務省「令和4年度インターネット上の違法・有害情報対応相談業務等請負業務報告書(概要版)」 Advanced parsingだとこのグラフはマークダウン形式で解析され、それぞれの年と数値の関係がわかりやすく表現されています。(※これ以降ではわかりやすく改行コードで改行を入れていますが、実際には一行にまとまっています。) ## 違法・有害情報センターへの相談件数の推移\n | 年度 | 件数 |\n |-|-|\n | 平成22 | 1,337 |\n | 平成23 | 1,560 |\n | 平成24 | 2,386 |\n | 平成25 | 2,927 |\n | 平成26 | 3,400 |\n | 平成27 | 5,200 |\n | 平成28 | 5,251 |\n | 平成29 | 5,598 |\n | 平成30 | 5,085 |\n | 令和元 | 5,198 |\n | 令和2 | 5,407 |\n | 令和3 | 6,329 |\n | 令和4 | 5,745 |\n (出典) 総務省「令和4年度インターネット上の違法・有害情報対応相談業務等請負業務報告書(概要版)」\n 他にシンプルな円グラフでも同じような検証を行いましたが、同じ結果でAdvanced parsingの方がより構造化されて解釈がしやすくなっていました。では、もう少し複雑なものだとどうでしょうか。 こちらはp7にある似たような折れ線グラフですが、先ほどの棒グラフと違い、細かい各年度の数値が書かれていません。 これをデフォルト設定で読み込むと、先ほどと同じくテキストは読み込みますが、大事な中身のグラフに対する内容が抜け落ちてしまっています。 主要プラットフォーマーの売上高の推移 0 100 200 300 400 500 600 (10億ドル) Google Amazon Meta Apple Microsoft Baidu Alibaba Tencent Holdings 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022(年) (出典)Statistaデータを基に作成 一方のAdvanced parsingだとそれぞれの数値が詳細に出されていないことを判断して、無駄なテキスト情報を省き、グラフが示唆する内容を簡単にまとめて解説しています。惜しいのは内容の抜粋になってしまっているので、RAGとして質問した時にそれ以上の回答はできなそうです。ただ、現状はデフォルトプロンプトを使っているので、カスタマイズすることによって精度向上は期待できるかもしれません。 ## 主要プラットフォーマーの売上高の推移\n この画像は、主要プラットフォーマー企業の過去10年間の売上高の推移を示すグラフです。縦軸は売上高(10億ドル)、横軸は年を表しています。グラフには、Google、Amazon、Meta、Apple、Microsoft、Baidu、Alibaba、Tencent Holdingsの売上高の推移が示されています。全体的に右肩上がりの傾向が見られ、特にGoogleとAmazonの売上高の伸びが顕著です。\n (出典) Statistaデータを基に作成\n 次にp9にあるよりテキスト情報が多く、それぞれの対応関係を正しく把握しないといけないようなものを見てみます。 デフォルト設定では同じように構造化されていない文字の抽出だけで複雑になった分だけよりわかりにくくなっています。 インターネット上の偽・誤情報への接触頻度 毎日、またはほぼ毎日 最低週1回 月に数回 ほとんどない 頻度はわからない 一度も見たことがない そもそも何がフェイクニュースなのかがわからない 19.1 12.0 16.6 18.6 20.2 2.9 10.7 19.5 19.5 21.7 10.7 22.2 5.0 1.4 令和3年度インターネット上のメディア (SNSやブログなど) 令和3年度まとめサイト Advanced parsingだと先ほどのようにマークダウン形式で解析され、一見すると綺麗に整ったように感じられます。 しかし、よく見るとそれぞれに対応する数値が違っていたり、欠損が目立っており正しく解析はできていないようで一部はハルシネーションにつながりそうな結果となりました。 ## インターネット上の偽・誤情報への接触頻度\n | | 令和3年度インターネット上のメディア(SNSやブログなど) | 令和3年度まとめサイト |\n |-|-|-|\n | 毎日、またはほぼ毎日 | 19.1% | 10.7% |\n | 最低週1回 | 12.0% | 19.5% |\n | 月に数回 | 16.6% | 19.5% |\n | ほとんどない | 18.6% | 21.7% |\n | 頻度はわからない | 20.2% | 10.7% |\n | 一度も見たことがない | 2.9% | 22.2% |\n | そもそも何がフェイクニュースなのかがわからない | | 5.0% |\n | | | 1.4% |\n (出典) 総務省「令和3年版 国内外における偽情報に関する意識調査」 画像の解析 p39の埋め込まれた画像を解析してみます。 デフォルト設定では画像は完全な非テキスト情報となり、その部分だけが情報として抜け落ちてしまいました。 図表2-1-4-1 校務・学習データの可視化(Microsoft) (出典)Microsoft 一方でAdvanced parsingでは、画像内で表現されていることを解析して、説明が加えられています。内容も大きく異なることを言っているわけではなく、かなりいい精度で解析ができています。 ## 図表2-1-4-1 校務・学習データの可視化(Microsoft) この図は、Microsoftによる校務・学習データの可視化の概要を示しています。左側には、Microsoft Teamsやアンケート・出欠管理システムなどの学校で利用されるシステムが列挙されています。中央には、これらのシステムから収集されたデータが蓄積されていることが示されています。右側には、蓄積されたデータを可視化し、学校全体、クラス、児童・生徒個人のレベルで分析できることが示されています。 表の解析 p7の表を解析してみます。 デフォルト設定の結果は今までのグラフと同じく、文字の抽出のみです。 プラットフォーマーが取得するデータ項目 データ項目 プラットフォーム Google Facebook Amazon Apple 名前 〇 〇 〇 〇 ユーザー名 - - 〇 - IPアドレス 〇 〇 〇 〇 検索ワード 〇 - 〇 〇 コンテンツの内容 - 〇 - - コンテンツと広告表示の対応関係 〇 〇 - - アクティビティの時間や頻度、期間 〇 〇 - 〇 購買活動 〇 - 〇 - コミュニケーションを行った相手 〇 〇 - - サードパーティーアプリ等でのアクティビティ 〇 - - - 閲覧履歴 購買活動 〇 - 〇 - コミュニケーションを行った相手 〇 〇 - - サードパーティーアプリ等でのアクティビティ 〇 - - - 閲覧履歴 〇 - 〇 - (出典)Security.org「The Data Big Tech Companies Have On You」 より、一部抜粋して作成 Advanced parsingでは、マークダウンでそのまま表形式を表現することでPDFと同じ構造を保てています。 ## プラットフォーマーが取得するデータ項目\n | データ項目 | Google | Facebook | Amazon | Apple |\n |-|-|-|-|-|\n | 名前 | ◯ | ◯ | ◯ | ◯ |\n | ユーザー名 | - | - | ◯ | - |\n | IPアドレス | ◯ | ◯ | ◯ | ◯ |\n | 検索ワード | ◯ | - | ◯ | ◯ |\n | コンテンツの内容 | - | ◯ | - | - |\n | コンテンツと広告表示の対応関係 | ◯ | ◯ | - | - |\n | アクティビティの時間や頻度、期間 | ◯ | ◯ | - | ◯ |\n | 購買活動 | ◯ | - | ◯ | - |\n | コミュニケーションを行った相手 | ◯ | ◯ | - | - |\n | サードパーティーアプリ等でのアクティビティ | ◯ | - | - | - |\n | 閲覧履歴 | ◯ | - | ◯ | - |\n (出典) Security.org「The Data Big Tech Companies Have On You」より、一部抜粋して作成\n テキストの解析 基本的にAdvanced parsingはこれまで述べてきた非テキスト情報の解析が大きな特徴になっていますが、テキスト情報でもどのような影響があるのかを最後にこちらの目次を解析した結果で確認します。 デフォルト設定では、チャンキングの設定により途中で切れてしまっていますが、表示されている通りに抽出を行っているため無駄にトークンを消費していそうです。 第1章 データ流通の進展 第1節 データ流通を支える通信インフラの高度化・・・・2 ■1  固定通信・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・2 ■2  移動通信・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・2 第2節 データ流通とデジタルサービスの進展・・・・・・・・5 ■1  片方向のデータ発信・ (Web1.0時代:1990年代~2000年代前半)・・・・・5 ■2 一方のAdvanced parsingでは書かれている内容を解析して、その意味を失わない範囲でマークダウンに整形しており、無駄がありません。 また、マークダウンで構造も表現できているため、文章の関係もこちらの方がわかりやすいです。 ### 第1章 データ流通の進展 #### 第1節 データ流通を支える通信インフラの高度化 * 1  固定通信 * 2  移動通信 #### 第2節 データ流通とデジタルサービスの進展 * 1  片方向のデータ発信 * (Web1.0時代:1990年代~2000年代前半) * 2  双方向のデータ共有 * (Web2.0時代:2000年代後半~) 運用上の注意点 以上のようにAdvanced parsing optionを有効化することによって完璧ではないものの全体の精度の向上が見込めそうなことがわかりました。一方で使っていく中で以下のような注意点があります。 基盤モデルで前処理を行う都合上、デフォルト設定よりモデルの利用コストが増加する ベクトルDBへの同期処理が遅いため、リアルタイムに文書を処理したい場合に適していない デフォルトプロンプトが英語だからなのか、そのままデフォルトで使うと一部の解析結果が英語になってしまう可能性がある データサイズ、読み込みファイル数に 制限 がある 特にフルにこの機能をRAG基盤で活用するにあたって大きな障壁に感じたのは、読み込みファイル数の制限です。現状では解析できるファイルの最大数は100ファイルで申請による調整もできません。工夫したとしてもナレッジベースあたりのデータソース数は5つなので、分割して全て使ったとしても500ファイル程度で上限に達してしまいます。もう一つの解決策としてファイルサイズの上限まで複数のファイルを繋げて、ファイル数を削減することですが、その手間を考えるとAWS側での今後の引き上げを期待したいところです。 まとめ 今回はここ1ヶ月ほどで出たAdvanced parsing optionsを活用したKnowledge baseの精度向上に関して、その挙動を見ていきながら機能を紹介しました。 リリースではこの他にもチャンキング戦略のアップデートやクエリ分割の機能など多くのアップデートがあり、Amazon Bedrockの改善のスピード感とAWSがかなり力を入れていることを日々感じています。現在同じようにRAG基盤を構築されている方はぜひ追加された機能も試してみてください。
アバター
概要 TIMELINE開発部の内原です。 本日は、改めてGo言語におけるgoroutine間での通信方法について整理してみました。 Go言語ではgoroutineを用いて簡単に並行処理を記述することができます。またその際、goroutine間で通信を行い、情報のやり取りをしたり互いに協調しつつ動作することもできます。 ただ、通信する手段自体は複数あり、それぞれ特徴がありますので、どのようなことを実現したいのかによって適切な方法を採用する必要があります。 いきなり結論 先に結論を書いておくと、以下のような切り分け方になりそうです。 やりたいこと 実装 goroutine間で値を共有したい sync.Mutex を使う goroutineから任意のタイミングで通知したい(通知のみでよい場合、かつ一度きり) sync.WaitGroup を使う goroutineから任意のタイミングで通知したい(通知のみでよい場合、かつ複数回) sync.Cond を使う goroutineから任意のタイミングで通知したい(なんらか値を返却したい場合) channel を使う 特定のタイミングでgoroutineを終了させたい context.Context を使う それぞれの実装を記載します。 それぞれの実装方法 goroutine間で値を共有したい sync.Mutex は排他制御を実現します。 Lock されている間は他の Lock をブロックします。ブロックを解除するには Unlock を呼び出します。なお sync.WaitGroup については後述します。 func increment(wg *sync.WaitGroup, mtx *sync.Mutex, cnt * int ) { mtx.Lock() defer mtx.Unlock() *cnt++ wg.Done() } func sampleMutex() { wg := sync.WaitGroup{} mtx := sync.Mutex{} cnt := 0 for i := 0 ; i < 10000 ; i++ { wg.Add( 1 ) go increment(&wg, &mtx, &cnt) } fmt.Printf( "waiting for goroutine complete... \n " ) wg.Wait() fmt.Printf( "completed, cnt: %d \n " , cnt) } 実行結果は以下です。 並行実行されていても正しく排他制御が行われ、想定した値に更新されていることが分かります。 waiting for goroutine complete... completed, cnt: 10000 もしmutexを使わないで実行した場合、以下のように想定した値に更新されないことがあります。 この場合いわゆる競合状態になっていることが分かります。このような状態だと場合によってはプログラムがクラッシュすることもあるため、gouroutine間で共有するリソースにアクセスする場合は排他制御が必要です。 waiting for goroutine complete... completed, cnt: 9382 なお、 sync.RWMutex というものもあり、こちらは書き込み用ロック Lock と読み込み用ロック RLock とが分かれており、読み込みロック同士ならば並行で実行できるという違いがあります。 goroutineから任意のタイミングで通知したい(通知のみでよい場合、かつ一度きり) sync.WaitGroup は Add された数と同数の Done が行われるまで Wait で待機します。これにより、呼び出されたgoroutine側の適当なタイミングで通知を行うことができます。 つまりこの機能における通知とは一方向かつ一度きりと言えます。このため、一般的には呼び出したgoroutineが終了したことを呼び出し元に伝える用途で使われることが多いと思います。 func procGoroutine(wg *sync.WaitGroup, n int ) { fmt.Printf( "goroutine: %d \n " , n) time.Sleep( 1 * time.Second) wg.Done() } func sampleWaitGroup() { var wg sync.WaitGroup for i := 0 ; i < 3 ; i++ { wg.Add( 1 ) go procGoroutine(&wg, i) } fmt.Printf( "waiting for goroutine to complete... \n " ) wg.Wait() fmt.Printf( "completed \n " ) } 実行結果は以下です。 呼び出したgoroutineが終了するまで呼び出し元が待機していることが分かります。 waiting for goroutine to complete... goroutine: 0 goroutine: 1 goroutine: 2 completed goroutineから任意のタイミングで通知したい(通知のみでよい場合、かつ複数回) sync.Cond はいわゆる条件変数で、goroutine間でなんらかのタイミングで通知のみを複数回行いたい場合に利用できます。 Wait した側は Signal または Broadcast されるまで待機することができますが、その際値の受け渡しはできません。 値の受け渡しが必要な場合、別途共有リソースを用意して値をやり取りするか、後述する chan を用いることになります。 以下はいわゆるProducer/Consumerの機構を実装したものです。 producerはデータを生産しますが、一定量になったらconsumerが消費するのを待ちます。 consumerはデータを消費しますが、存在しない場合はproducerが生産するのを待ちます。 func produce(cond *sync.Cond, messages *[] string , msg string ) { cond.L.Lock() for len (*messages) == 5 { fmt.Printf( "produce: messages full, msg: %s \n " , msg) cond.Wait() } *messages = append (*messages, msg) cond.Signal() cond.L.Unlock() } func consume(cond *sync.Cond, messages *[] string ) { cond.L.Lock() for len (*messages) == 0 { fmt.Printf( "consume: messages empty \n " ) cond.Wait() } msg := (*messages)[ 0 ] fmt.Printf( "consume: msg: %s \n " , msg) *messages = (*messages)[ 1 :] cond.Signal() cond.L.Unlock() } func sampleCond() { wg := sync.WaitGroup{} mutex := sync.Mutex{} cond := sync.NewCond(&mutex) messages := make ([] string , 0 ) wg.Add( 2 ) go func () { for i := 0 ; i < 10 ; i++ { produce(cond, &messages, fmt.Sprintf( "msg %d" , i)) } wg.Done() }() go func () { for i := 0 ; i < 10 ; i++ { consume(cond, &messages) } wg.Done() }() wg.Wait() } 実行結果は以下です。 producerとconsumerとがそれぞれ協調しつつ動作していることが分かります。 consume: messages empty produce: messages full, msg: msg 5 consume: msg: msg 0 consume: msg: msg 1 consume: msg: msg 2 consume: msg: msg 3 consume: msg: msg 4 consume: messages empty consume: msg: msg 5 consume: msg: msg 6 consume: msg: msg 7 consume: msg: msg 8 consume: msg: msg 9 goroutineから任意のタイミングで通知したい(なんらか値を返却したい場合) channelは、送信元と送信先とで任意のデータをやり取りすることができる機構です。その際、channelに読み込めるデータが存在するかどうかをあらかじめチェックすることも可能なので、さまざまな用途に利用することができます。 先ほどのProduder/Consumerをchannelで実装し直したものが以下です。 func produce(messages chan <- string , msg string ) { for { select { case messages <- msg: return default : fmt.Printf( "produce: messages full, msg: %s \n " , msg) time.Sleep( 100 * time.Millisecond) } } } func consume(messages <- chan string ) { for { select { case msg, ok := <-messages: if !ok { return } fmt.Printf( "consume: msg: %s \n " , msg) default : fmt.Printf( "consume: messages empty \n " ) time.Sleep( 100 * time.Millisecond) } } } func sampleChannel() { messages := make ( chan string , 5 ) go func () { for i := 0 ; i < 10 ; i++ { produce(messages, fmt.Sprintf( "msg %d" , i)) } close (messages) }() consume(messages) } 実行結果は以下です。 sync.Cond の時と同様の結果になっていることが分かります。 consume: messages empty produce: messages full, msg: msg 5 consume: msg: msg 0 consume: msg: msg 1 consume: msg: msg 2 consume: msg: msg 3 consume: msg: msg 4 consume: messages empty produce: messages full, msg: msg 5 consume: msg: msg 5 consume: msg: msg 6 consume: msg: msg 7 consume: msg: msg 8 consume: msg: msg 9 特定のタイミングでgoroutineを終了させたい context.Context を使うことで、呼び出し元での状態変化を呼び出し先に通知することが可能です。 一般的にはエラーハンドンリング時に用いられることが多いと思われます。goroutineの呼び出し元でなんらか異常やタイムアウトが発生した場合などに、呼び出し先でも同様に終了してほしいといったケースで利用できます。 例によってProducer/Consumerでcontextを組み込みます。 func produce(messages chan <- string , msg string ) { for { select { case messages <- msg: return default : fmt.Printf( "produce: messages full, msg: %s \n " , msg) time.Sleep( 100 * time.Millisecond) } } } func consume(ctx context.Context, messages <- chan string ) { for { select { case <-ctx.Done(): fmt.Printf( "consume: context cancelled \n " ) return case msg, ok := <-messages: if !ok { return } fmt.Printf( "consume: msg: %s \n " , msg) time.Sleep( 100 * time.Millisecond) default : fmt.Printf( "consume: messages empty \n " ) time.Sleep( 100 * time.Millisecond) } } } func sampleContext() { messages := make ( chan string , 5 ) ctx, cancel := context.WithTimeout(context.Background(), 300 *time.Millisecond) defer cancel() go func () { for i := 0 ; i < 10 ; i++ { produce(messages, fmt.Sprintf( "msg %d" , i)) } close (messages) }() consume(ctx, messages) } 実行結果は以下です。 consumeは時間のかかる処理になっていますが、途中でcontextの終了を検知して処理を中断していることが分かります。 consume: messages empty produce: messages full, msg: msg 5 produce: messages full, msg: msg 5 consume: msg: msg 0 consume: msg: msg 1 produce: messages full, msg: msg 7 produce: messages full, msg: msg 7 consume: msg: msg 2 consume: context cancelled まとめ 本日はGo言語の並行処理において、goroutine間で情報をやり取りする方法についてまとめました。 これらの機能は便利かつ簡単に使えてしまうのであまり深く考えずに使っていたりしたのですが、記事を書くにあたって改めて学び直すことでより理解を深めることができました。
アバター
参考 https://developer.apple.com/jp/videos/play/wwdc2024/10170/ https://github.com/swiftlang/swift-evolution/blob/main/proposals/0390-noncopyable-structs-and-enums.md https://github.com/swiftlang/swift-evolution/blob/main/proposals/0427-noncopyable-generics.md ~Copyableの導入 Swift 5.9でCopyableと~Copyableが導入されました。 全ての型が暗黙的にCopyableに準拠するので、今まで通りCopyableを前提にするなら特にCopyableと~Copyableを意識しなくても問題はありませんが、一方、~Copyableを使うと一意なリソースを表現でき、それによってより効率的なコードを書けたり、論理的に正しいコードを書くのに役立つ場合があります。 この記事では、~Copyable導入の利点についての調査と、弊社プロダクトへの導入の検討状況をご紹介します。 CopyableなStructとの比較 Structは値型です。値のコピーが複数作られるので、一意なリソースを表現するのには適していません。 また、値のコピーはメモリを占有しコピー作業に処理時間がかかるので、~CopyableなStructを使うことでコンピューティング資源の消費が少ない効率的なコードを書ける可能性があります。 Classとの比較 Classは参照型です。一意なリソースを表現することができます。 複数の箇所から同時に参照、変更が可能です。そのため、非同期的な処理でデータ競合の問題を起こしたり、参照元の管理のミスでメモリリークを発生させる恐れがあるなどコードを複雑化させる要因になります。 ~CopyableなStructを使うことでよりシンプルで安全なコードを書ける可能性があります。 所有権 ~Copyableな型の値の所有者を明確にするために、 所有権(ownership) という概念が導入されています。 値の受け渡しをするとき、値をコピーする代わりに所有権の移動もしくは共有が行われます。 所有権の取り扱い方には以下の3つの種類があります。 consume 値の所有権を移動する 所有権が移動した後、元の値は無効になる borrow 値の所有権を共有する 元々の所有者から値の所有権が剥奪されることはない 借りた側は値を参照だけできる。値を変更することはできない mutating (or inout) 値の所有権を一時的に移動する 操作が終わるまでの間、値の参照、変更ができる 操作終わったら元の所有者に所有権が戻る ~Copyableを使ったコードの記述例 ~Copyableな型の宣言 struct FloppyDisk : ~ Copyable {} 所有権の移動 値の代入操作をすると、値がコピーされる代わりに所有権が移動します。 system が持っていた所有権は消費され、使用できなくなります。 func copyFloppy () { let system = FloppyDisk() // error: 'system' used after consume let backup = system // consumed here load(system) // used here // ... } func load (_ disk : borrowing FloppyDisk) {} 関数の引数として~Copyableな型の値を渡す場合 関数の引数として~Copyableな型の値を渡す場合、 consuming , borrowing , inout のいずれかを指定する必要があります。 consuming 関数を呼んだ時点で値の所有権が移動します。 値は関数の中で消費される必要があります。 この例では、関数の呼び出し元ではすでに所有権を失っているのに値を消費する操作をしようとしているため、コンパイルエラーになります。 struct FloppyDisk : ~ Copyable { } func newDisk () -> FloppyDisk { let result = FloppyDisk() // error: 'result' consumed more than once format(result) // consumed here return result // consumed again here } func format (_ disk : consuming FloppyDisk) { // ... } borrowing 関数内では引数として受け取った値を変更できません。 この例では関数内で値を消費する操作をしようとしているため、コンパイルエラーになります。 struct FloppyDisk : ~ Copyable { } func newDisk () -> FloppyDisk { let result = FloppyDisk() format(result) return result } func format (_ disk : borrowing FloppyDisk) { // error: 'disk' is borrowed and cannot be consumed var tempDisk = disk // consumed here // ... } inout 関数内では一時的に所有権を持つため値を変更可能です。 関数の終了後は呼び出し元に所有権が戻ります。 この例では、format関数内で値を変更し、関数の終了後に変更された値を利用できています。 struct FloppyDisk : ~ Copyable { } func newDisk () -> FloppyDisk { var result = FloppyDisk() format( & result) return result } func format (_ disk : inout FloppyDisk ) { var tempDisk = disk // ... disk = tempDisk } ~Copyableな型のインスタンスメソッド ~Copyableな型のインスタンスメソッドには、 borrowing , consuming , mutating のいずれかを付与します。 borrowing がデフォルト値のため、明示しない場合は borrowing として扱われます。 ~Copyableの利用例 WWDCのセッション ( https://developer.apple.com/jp/videos/play/wwdc2024/10170/ ) では、~Copyableな型を使って一意なリソースを表現することで、静的に論理的に正しいコードを書く例を紹介しています。 BankTransfer(銀行振込)を題材にしています。 ~Copyableを使わない例 class BankTransfer { func run () { // .. do it .. } } func schedule (_ transfer : BankTransfer , _ delay : Duration ) async throws { if delay < .seconds( 1 ) { transfer.run() // A } try await Task.sleep( for : delay ) transfer.run() } ~Copyableを使わない記述例です。 このコードにはバグがあります。Aの箇所に本来returnが必要なところ、書き忘れています。これによってdelayが1秒未満の場合 transfer.run() が2回実行されてしまいます。 振込の実行(run)は一つのBankTransferに対して複数回行われてはいけないのですが、この例ではそのような制約を実装していないため、呼び出し側の誤った実装によってこのような問題が発生してしまいます。 BankTransferの状態管理を追加した例 class BankTransfer { var complete = false func run () { assert( ! complete) // .. do it .. complete = true } deinit { if ! complete { cancel() } } func cancel () { /* ... */ } } func schedule (_ transfer : BankTransfer , _ delay : Duration ) async throws { if delay < .seconds( 1 ) { transfer.run() } try await Task.sleep( for : delay ) transfer.run() } この例ではBankTransferの状態管理を追加し、run()の複数回実行を防いでいます。 ただし、assertによる動的な検証のため実行時にしか問題を検出できません。 適切なテストを書かない限り見つけられず(この場合、delayが1未満の条件のテストが必要)もしテストで見つけられないと、本番でクラッシュしてしまいます。 ~Copyableを使った例 struct BankTransfer : ~ Copyable { consuming func run () { // .. do it .. discard self } deinit { // .. do the cancellation .. } } ~CopyableなBankTransferを定義しています。 インスタンスメソッドに付与した consuming キーワードによって、run()が複数回実行されることを防いでいます。 func schedule (_ transfer : consuming BankTransfer, _ delay : Duration ) async throws { // error: 'transfer' consumed more than once if delay < .seconds( 1 ) { transfer.run() // consumed here } try await Task.sleep( for : delay ) transfer.run() // consumed again here } バグのあるschedule関数です。 transfer.run() が複数回実行される可能性があることをコンパイル時に検出できています。 func schedule (_ transfer : consuming BankTransfer, _ delay : Duration ) async throws { if delay < .seconds( 1 ) { transfer.run() return } try await Task.sleep( for : delay ) transfer.run() } returnを追加し、バグを修正できました。 このように、~Copyableな型を使って一意なリソースを表現することで、論理的な問題点をコンパイル時に検出できるようになりました。 問題を早期に発見でき、コードの品質向上に役立ちそうです。 弊社プロダクトで~Copyableを適用する 弊社プロダクトではまだ~Copyableを導入していませんが、以下のような箇所で導入することを検討中です。 ネットワーク接続 ネットワーク接続を一意なリソースとして表現する PDF出力 PDF出力ではサイズの大きいデータを扱うため、~Copyableを使うことで不用意なメモリの消費を防止する 広告表示 広告を一意なリソースとして表現する 広告リクエスト、インプレッション、クリックなどの一連の処理を~Copyableな型のインスタンスとして表現する
アバター
エブリーで小売業界向き合いの開発を行っている @kosukeohmura です。 エブリーでは retail HUB という複数のプロダクトからなる小売業界向けのサービスの開発を行っています。以前まではサービス開発チームは単一で、その中で複数のプロダクトを開発を行ってきましたが、今後複数のチームがプロダクトごとに分かれて開発を行うこととなり、その体制の変化に伴って Terraform のルートモジュールを分割した話をします。 分割前のモジュール構成と、その問題点 今回紹介する作業を行う以前は、環境 (dev, prd) ごとに存在する単一のルートモジュールから各プロダクト向けのリソースを含むモジュールを参照する形でした。ルートモジュールが単一の Backend 定義を参照し、State ファイルも単一です。 このような構成だとシンプルな反面ルートモジュールに含むリソースが肥大化しやすく、plan / apply 等にかかる時間が長くなりやすいのですが、DELISH KITCHEN などの他の弊社のプロダクトと比較して、向こう数年のリソースの増え方は限定的になりそうなことと、単一のチームでの作業に最も適した形をとりたくこの形を取ってきました。 ところが開発チームや予算管理を分けようと思うと次の問題点が生じ、構造を見直すことにしました。 他プロダクトの変更との競合への対処 Terraform の State と実際のリソースに差分が発生した場合、他チームへの差分の解消を依頼するなどのコミュニケーションコストがかかる 一括で AWS タグを付与することができず、リソースの管理者が不明確になる AWS タグの一括付与のためには AWS provider ブロックの default_tags を使う 必要があり、単一のルートモジュールだとこの方法が採れない AWS の料金をプロダクトごとに分けて確認できないことにもなる 具体的には次のようなディレクトリ構造をしていました: . ├── README.md └── aws ├── envs │ └── dev # dev 環境のルートモジュールや、また環境全体に関わる設定を配置 │ │ ├── backend.tf # AWS S3 を使用しています │ │ ├── main.tf # ルートモジュール。このファイルから aws/modules 配下に定義してある各 Child Module を読み込む │ │ ├── provider.tf │ │ └── variables.tf │ └── prd │ ├── -- 省略 -- │ └── modules ├── common # 特定のプロダクトに依存しないリソースを配置 │ └── dev # 特定のプロダクトに依存しない、dev 環境用のリソースを配置 │ └── cloudtrail │ ├── main.tf # output values 以外のリソースを定義 │ └── outputs.tf # 他 module で使用する output values を定義 └── product_A # product_A 用のリソースを配置 │ └── dev # product_A 用の、dev 環境用のリソースを配置 │ │ ├── ecs │ │ │ └── main.tf │ │ │ └── outputs.tf │ │ │ │ │ -- 省略 -- │ │ │ │ │ └── vpc │ │ ├── main.tf │ │ └── outputs.tf │ └── prd # product_A 用の、prd 環境用のリソースを配置 │ ├── -- 省略 -- │ └── product_B # product_B 用のリソースを配置 │ ├── -- 省略 -- Style Guide - Configuration Language _ Terraform _ HashiCorp Developer に記載例に近しく、あまり特殊な点はありません。Backend に AWS S3 を使用しており、Workspaces を使用せずにモジュールごとにディレクトリを分けて State ファイルも環境ごとに 1 つ存在する構造です。 Terraform ルートモジュールを分割することに 問題点の解消にあたり、プロダクトごとに AWS アカウントを分離し HCL を管理する Git リポジトリごと分割する事も考えましたが、プロダクト同士が将来的に連携/統合する可能性を考えたときに都合が悪くなることを避けたく、AWS アカウントは同一のまま Terraform ルートモジュールをプロダクトごとに分割することとしました。 この変更によって向き合いのプロダクト以外との競合を気にせずに作業を行うことができ、また AWS provider ブロックをプロダクトごとに定義できます。 よって各リソースにプロダクト別のタグを付与できるので プロダクトごとに料金を分けて確認できる ことにもなり、今回の問題点が解消できます。 変更後のディレクトリ構成は下記のようになりました: . ├── README.md └── aws ├── common # 特定のプロダクトに依存しないリソースを配置。cloudtrail など │ └── dev │ ├── cloudtrail │ | ├── main.tf │ | └── outputs.tf │ ├── backend.tf │ ├── main.tf │ ├── provider.tf │ └── variables.tf ├── product_A # product_A のリソースを配置 │ └── dev # 環境ごとにディレクトリを切る │ | ├── s3 # 各 Child Module は環境ごとのディレクトリ配下へ配置 │ | | ├── main.tf │ | | └── outputs.tf │ | | # 他 Child Module ごとのディレクトリがここに並ぶ │ | ├── backend.tf │ | ├── main.tf # product_A 用のルートモジュール │ | ├── provider.tf │ | └── variables.tf │ └── prd │ ├── -- 省略 -- │ └── product_B │ ├── -- 省略 -- 以下、その際に行った作業を簡単に説明します。 1. 新ルートモジュールへと移動予定の Child Module を State から取り除く すでに存在するルートモジュールの State から、新ルートモジュールへと移動予定の module を一旦 terraform state rm します。 $ terraform state rm \ module.cloudtrail \ module.s3 \ # 省略 2. ルートモジュールのファイルを分割し、ディレクトリ構造を変更 単一のルートモジュールだった main.tf を分割し、プロダクトごとにディレクトリを分けて main.tf もそれぞれ置きます。合わせて各 Child Module をそのルートモジュール配下のディレクトリへと移動します。 ディレクトリの移動作業は基本的にはディレクトリを単に mv し、ルートモジュールからの参照先のファイルパスを変更するような単純な内容ですが、他のルートモジュールへ移動してしまったモジュールの Output value への参照はできなくなり、その際は variable や locals を定義して対処療法的にハードコードしました。ここは(試せていませんが) terraform_remote_state Data Source を使用して他の State を参照するように出来ると思います。 3. 新ルートモジュールへと移動するリソースを import する まだ新ルートモジュールの State は空のままなので、 terraform import コマンドを使って先ほど terraform state rm したリソースを新ルートモジュールへと追加していきます。 $ terraform import module.api-gateway.aws_api_gateway_account.this api-gateway-account $ terraform import module.cloudtrail.aws_cloudtrail.default arn:aws:cloudtrail:<region>:<account_id>:trail/default # 省略 import したら、最後に terraform plan で差分がないことを確認して作業終了です。 リソースを State から削除する際は terraform state rm コマンドを Child Module ごとに実行できたためコマンドの数も少なくコマンドの内容もシンプルでした。一方 terraform import ではリソースごとにコマンドを実行する必要があり、また引数の指定方法もリソースの種別ごとに違いがあるためにこのコマンドの作成が割と大変です。 私達の場合は import 対象のリソースがまだ 30 個ほどであったため目立った問題にはなりませんでしたが、リソース量が多いとコマンドを作成・実行する量が膨大になると思います。 後日談: 次に同じことをやるなら、、 今回の作業の終わりかけの段階で、公式記事による State ファイルの分割方法を見つけました。 support.hashicorp.com 記事ではモジュール構成は変えず、単に terraform state mv コマンドの -state , -state-out オプションで Child Module ごとに State ファイルを移動させる方法が書かれています。 私達の場合はルートモジュール分割にあたり、 先に terraform state rm で新ルートモジュールにて管理したいリソースを既存のルートモジュールから取り除く 空のルートモジュールを作成する そこへ terraform import し、新ルートモジュールにて管理したいリソースを新ルートモジュールへ import しなおす ような作業を行いましたが、 先に State ファイルを記事記載の方法で分割する 次に新ルートモジュールを、分割済みの State ファイルを使う形で作成する あわせて、既存ルートモジュールから新ルートモジュールで管理するリソースの記述を削除する するアプローチを取れると、 terraform import コマンドをリソースごとに実行する必要がなく作業量を減らせたかもしれません。もしまた同じようなことを行うのであれば、その方法を試してみたいと思います。 さいごに ここまでお読みいただきありがとうございました。開発チームも複数になり、 retail HUB の開発もこれまで以上に活発になっています。そんなチームに興味がある方、ぜひ一度お話しましょう! corp.every.tv
アバター
はじめに こんにちは、株式会社 エブリー の24新卒の蜜澤、きょー、新谷です。 今回は、2024年5月から7月にかけて開催された日本CTO協会主催の合同新卒研修に参加した際の内容と学びについてご紹介します。 合同新卒研修とは 本研修は、日本CTO協会が主催する新卒エンジニア向けの合同研修です。新卒エンジニアが業界全体・企業横断で育てられる試みとして、今年から開催されました。 日本CTO協会が新卒エンジニアを業界全体・企業横断で育てる試み「新卒エンジニア向けの合同研修」を5月29日から実施 上記記事にも書いてありますが、開催に至る背景として 現状企業が経営・事業を大きくする、新たなチャレンジをする上でもエンジニアが常に足りない状況にある スタートアップや中小企業が新卒エンジニアを採用するには、採用コストだけではなく育成のコストも大きくかかる といった課題があることから、本研修が開催されています。 また講義は毎回オフラインで行われ、その後開催される懇親会では、他社の新卒エンジニア同士で交流を深めることができました。(懇親会では毎回お酒と軽食が用意されているので、和気あいあいとした雰囲気で交流ができました!) 研修内容 研修回 講義内容 講師 第1回 キャリア戦略・フォロワーシップとマネジメント 日本CTO協会 / 株式会社LayerX 第2回 Git基礎ハンズオン 株式会社Progate 第3回 BtoB SaaS開発基礎 株式会社アンチパターン 第4回 インテリアコーディネートで学ぶアジャイル開発 株式会社メンバーズ 第5回 BigQueryで始めるデータ分析入門 & 生成AIを活用した分析効率化 グーグル・クラウド・ジャパン合同会社 第6回 AWS研修 アマゾンウェブサービスジャパン合同会社 第7回 サーバ解体研修 GMOペパボ株式会社 第8回 生成AIに関する講義 日本マイクロソフト株式会社 第9回 日本CTO協会ISUCON新卒研修 + 解説 株式会社PR TIMES 24卒の新卒エンジニア3人でこの研修に参加させていただいので、1人1つずつ講義の内容をピックアップし、それぞれの講義内容と学びについて紹介します。 キャリア戦略・フォロワーシップとマネジメント 開発本部のデータ&AIチームの蜜澤です。 私からは「第1回:キャリア戦略・フォロワーシップとマネジメント」の紹介です。 講義を担当してくださったのは、日本CTO協会 理事 / 株式会社LayerXの松本CTO( @y_matsuwitter )です。 この講義は 前編:投資的に考えるキャリアのあり方 後編:フォロワーシップとマネジメント の2つのパートに分かれていたので、それぞれのパートについての学びを紹介します。 前編:投資的に考えるキャリアのあり方 信頼や信用、知識や経験、時間といった自分が持っている「人生の資産」を、自分の仮説や学びたい方向性に向けて効率よく投資し、資産を拡大させていくという「投資家的に考えるキャリアのあり方」について学びました。 今自身に必要な資産を考え、自分の手元の投資可能な資産(お金、時間、信用など)をどのように配分し、どのようなリターンを期待するのか、抱えるリスクはなにかというポートフォリオを作成し、資産を拡大していくことが重要とのことでした。 何を投資するのか? 新卒の私には、たいした資産(特に知識や信用)はありません。 いったい何を投資すれば、、、 「時間」です! 家庭がなく(現在独身・一人暮らし)、健康上の問題も特にない20代の今が人生で最も可処分時間が多いです。 この20代の時間を最大限未来に投資していくことの重要性を松本さんは強調されていました。 「ドキッ!」 とした人も多いのではないでしょうか。 私もその一人です。 可処分時間を未来に投資していくことの重要性を理解しても、私のような遅延評価(必要になったらやる)型の人間は、今必要でないことはサボってしまうことがあると思います、、、 そんな時に努力を習慣化し、投資を続ける助けとなるのが「コミュニティ」のようです! コミュニティの力 「コミュニティの平均値が自分」や「周りの5人の平均が自分」などの言葉はよく耳にします。 松本さんが所属していたコミュニティから次々とCTOが誕生したという話を伺い、より自分が所属するコミュニティの重要性を感じました。 そして、コミュニティにおいて特に重要だと思ったのが「健全な嫉妬」です。 仲間の昇進や技術的な成長を目の当たりにすると、私たちは「あいつには負けられない」という健全な嫉妬を感じます。 「健全な嫉妬心を努力の習慣化に繋げることで成長する」というのがコミュニティの力だと思いました。 幸いにも私の周りには同期や、CTO研修で出会った同世代のエンジニアといった切磋琢磨できる仲間がいます! この中で一番になるという気持ちで、努力(投資)を続けていかなければと考えさせられる前編でした。 後編:フォロワーシップとマネジメント マネージャーの仕事は、単に管理するだけではなく、チームの成果を最大限高めることだと学びました。 チームで大きな成果を出すために チームの成果は、一人一人のモチベーションと能力の掛け算の総和であり、全員がモチベーション高く、かつ専門性を発揮できると大きな成果が出ると松本さんは考えているようです。 そのモチベーションのために、一人一人が納得感と自信を持てるように、意思決定をし、内容を言語化して伝え、メンバーを導くのがマネージャーの重要な役割のようです。 役割は分かりましたが、実際にやるのとても難しそうだなと感じました。 メンバーのモチベーションの源泉を考え、納得感を持ってもらうためにメンバーのことをよく理解するとういう難しい役割を日々担っていただいているマネージャーには頭が上がりません。 マネージャーを支えるために私たちにできることも教えていただきました! こまめな自己開示 マネージャーとうまく連携するには、自己開示が重要だそうです。 自己開示のコツは、何が楽しいか、何を目指したいか、何がわからないか、何が目標でどのような状況かをこまめに伝えることだとわかりました。 わからないことは頻繁に聞きますが、「何が楽しいか」や「目標と今の状況」などはこまめに伝えられていないなと反省しました。 「私自身が自己開示したつもりになっていても、マネージャーには伝わっていなかった」ということはよくありそうだなと思い、自分のことをどれだけ理解してもらえているのか確認しつつ、こまめにコミュニケーションをとっていくことが大切だなと思いました。 マネージャーを理解する 自己開示だけではなく、マネージャーを理解することも大切だと教えていただきました。 「あなたのマネージャーの目標はなんですか?」と聞かれましたが、この時の私はわかりませんでした。 マネージャーを理解することで自分が何をすべきかわかると思ったので、1on1やチームでのランチ会の時に「マネージャーの目標」や「休みの日に何をしているのか」などを聞いてみました! ということで、少しだけ私のマネージャーについて紹介します!! 私のマネージャー 短期目標:マネジメントしながら手を動かすプレイングマネージャー的に技術を高めていきたい 長期目標:全体的に組織を見て、率いていきたい チーム目標:各自の専門性を深めつつ、専門性+αのことをできる組織にする 休日の過ごし方:お子さんと遊ぶことが多いが、最近は暑いので大変 まだまだ理解不足ですが、実際に聞いてみたことで、私はこれから専門性(データ系)に加えて、+α(バックエンドやインフラ等)の知識も身につけていく必要があるということを認識できました。 私のチームにはマネージャー以外にも上司が4人おり、マネージャー以外の方々のことも理解する必要があると思うので、コミュ二ケーションを頻繁に取っていこうと思います! フォロワーシップはマネジメントのスタートライン マネージャーを支えるとは、マネージャーを理解しようとすることで、その視点を手に入れるということは、自身がマネジメントへ進む準備運動であると学びました。 将来的にはマネジメントにも関わりたいと思っているので、フォロワーシップを磨き、準備運動を完了させようと思います! 今後に向けて この研修を5月末に受けたので、この研修から約3ヶ月が経ちました。 しかし、この研修で学んだ、可処分時間を有効活用し、最大限に未来に投資するというのができているかと聞かれると、まだまだできていないと思います。 この記事の執筆にあたり研修の内容を振り返る良い機会になったので、健全な嫉妬心を燃やし頑張ります! サーバー解体 こんにちは、開発本部 DELISH KITCHEN 開発部のきょーです! 僕の方からは GMO ペパボ株式会社様に講義をしていただいた「サーバ解体研修」について紹介していこうと思います。 講義の名前からわかるかもしれませんが、実際にサーバを解体していく内容です!とてもワクワクしますね!僕自身家にサーバもなければ自作 PC を組み立てたこともなかったので、この機会に物理サーバを解体しまくって理解を深めるぞ!と息巻いて講義に臨みました。 解体するぞ!! 参加者は何班かに別れた後、GMO ペパボの社員の方にサポートしていただきながら既に組み立てられているサーバを解体していきます。 最初は ↓ のような状態です。 天板を外すと ↓ のような感じに。ここにびっしり詰まっているそれぞれの部品を解体していいんだ、、、と思うと早く手を動かしたくなりました。 この時点でファンが 4 つ、CPU、メモリ、電源ユニットが 2 つあることがわかるかと思います。電源ユニットが 2 つある理由としては電源の供給元を分散させることによって 1 つの供給元が落ちたとしても稼働し続けられるように、とのことでした!いろんなことが考えられて設計されていることを学びました。(教えてくれたメンバーの方ありがとう!) ここからは班のメンバーで手分けしていきながら解体作業に移っていきます。最初はおっかなびっくり解体していたメンバーも ↓ のようにサーバが解体され尽くされる頃には「これって取れるかな?」と手を動かしていてとても面白かったのを覚えています笑 解体していく!! 解体しました。正直ここまで解体できるとは思っていなかった、、、達成感えぐい、、、(メモリの部分を外した時の写真取り忘れた) また解体しやすいように部品が配置されている設計にとても驚きました。外したいものだけを外せるし、戻したいときもすぐ戻せる、そのような部品の配置、ケーブルの配線になっていて感動しました。(ソフトウェアも交換したい部分だけを交換できるように設計することは大切ですよね。) 一つ一つの部品が組み合わさり PC が完成されていることを学べたとても貴重な機会だったと思います。 元に戻すまでが研修ということで、これから組み立て直していきます。 難しいかと思いましたが、解体しやすかった分元に戻すのも手順さえ覚えておけば簡単でした! 無事に元に戻せました!!おめでたい!! サーバー解体を終えて 解体から元に戻すところまで無事にできました。さてそれでは物理サーバはクラウド技術が発展してきた現代でどのように使われているのでしょうか? 全てクラウドを使ってサービスを運用することは可能です。しかし適切に物理サーバを用いることで運用コストを下げたり、パフォーマンスをあげたり、セキュリティ面を強固にすることができるとのことでした。例えば、クラウドのストレージは格納するデータ量によってコストがかかりますが、物理サーバにそれらを配置することでコストが抑えられるかもしれません。また、機密データ等は物理サーバに配置することでセキュリティの強化を見込めます。 適切にクラウドなのか物理サーバを使うのかを判断できるようになることで考えられる選択肢に幅を持たせられ、エンジニアとして成長できると講師の方が言っていました。クラウドしか触ったことがない自分にとっては実際に分解することで物理サーバの知識や興味を深められ、エンジニアとして成長できる一歩を踏めたと感じています! P.S 自分たちのサービスである DELISH KITCHEN であれば高解像度の動画や画像を取り扱っているので、それらを物理サーバに配置することで運用コストが下がるのではないか、、?と妄想したりしました。もし物理サーバを導入するとした場合、サーバの購入費や、どこにサーバを配置するのか、既存から乗り換えるのにどれくらいコストがかかるのか、など考えなければいけないことがたくさんありそうです。 また、懇親会の時に HDD も解体してきました!!元には戻せませんでした!!!! ISUCON 研修 DELISH KITCHEN 開発部でソフトウェアエンジニアをしている新谷です。私からは第9回のISUCON研修について紹介します。 ISUCON研修では、本番のISUCONのようにチームで与えられた問題に取り組む形式で行われました。 そもそもISUCONとは、「いい感じに スピードアップ コンテスト」の略称で、お題となるWebサービスを決められたレギュレーションの中でどれだけ高速化できるかを競う大会のことです。 事前準備 私はISUCONには参加したことはないのですが、事前にISUCON研修に参加する人たちで有志の勉強会が開かれたのでそれに参加していました。そこでは、ISCUONでよく使われるツールやテクニックについて知ることができ、研修当日に備えることができました。 ツールとしては以下のようなものがあり、研修当日に活用しました。 pt-query-digest slowqueryの解析に使うツール mysqlのログを解析してsqlが遅い順に表示される alp NGINXのアクセスログの解析に使うツール 遅いエンドポイント順に表示される 研修当日 研修当日は、計測ツールの導入、ボトルネックの特定、修正を高速に行いました。計測から修正までの一例としてDBへのインデックス追加を行った際の流れを紹介します。 まず pt-query-digest でslowqueryを解析します。以下は解析結果の一部です。上位に書いているクエリほど遅いクエリです。 # Profile # Rank Query ID Response time Calls R/Call V/M Ite # ==== ============================= ============== ===== ====== ===== === # 1 0x624863D30DAC59FA16849282... 497.2385 69.5% 1885 0.2638 0.02 SELECT comments # 2 0x422390B42D4DD86C7539A5F4... 165.4411 23.1% 2016 0.0821 0.01 SELECT comments # 3 0x100EC8B5C400F34381F9D7F7... 37.9935 5.3% 131 0.2900 0.00 SELECT comments 上記から、 SELECT comments が遅いことがわかりました。そのため、 comments テーブルにインデックスを追加しました。その後再度 pt-query-digest でslowqueryを解析します。 # Profile # Rank Query ID Response time Calls R/Call V/M Item # ==== ============================ ============= ====== ====== ===== ==== # 1 0x4858CF4D8CAA743E839C127... 48.3164 44.0% 933 0.0518 0.00 SELECT posts # 2 0x7A12D0C8F433684C3027353... 10.2537 9.3% 160 0.0641 0.00 SELECT posts # 3 0xDA556F9115773A1A99AA016... 10.1553 9.3% 126417 0.0001 0.00 ADMIN PREPARE # 4 0x396201721CD58410E070DA9... 8.3778 7.6% 58802 0.0001 0.00 SELECT users # 5 0xCDEB1AFF2AE2BE51B2ED5CF... 8.3270 7.6% 191 0.0436 0.00 SELECT comments # 6 0x19759A5557089FD5B718D44... 7.2387 6.6% 15215 0.0005 0.00 SELECT posts # 7 0x624863D30DAC59FA1684928... 4.1172 3.8% 23976 0.0002 0.00 SELECT comments # 8 0x422390B42D4DD86C7539A5F... 3.9942 3.6% 25103 0.0002 0.00 SELECT comments インデックスを追加したことで、 SELECT comments のクエリが大幅に高速化されました。このように、計測してボトルネックを特定し、ピンポイントで修正を行っていきました。上記の流れで、他にもNginxへのキャッシュやクエリのチューニング、N+1問題の解消などを行い、大方目立ったボトルネックを全て解消することができました。 個人的にN+1を時間内に全て解消することができたのは、自分にとって大きな成長だと感じています。今回のISUCONはGo言語を選択したのですが、普段業務でGo言語を書いていることもあり、比較的スムーズにN+1問題を解消することができました。 結果 最終的に、私たちのチーム「唐揚げ丼」は31チーム中2位という結果を残すことができました。 前半にツール導入に手間取ったりして反省点などありますが、ISUCON本番のような空気感を味わうことができ、とても有意義な研修だったと感じています。 ちなみに、1位の黒酢サンドさんのスコアは2,3位のチームのスコアの2倍以上差があり圧倒的でした。後日記事が公開されていましたが、学ぶべき点が多くありました。 【CTO協会研修記録】 未経験エンジニアがISUCONで圧倒優勝するまでの話 特にhtmlファイルのExecuteが遅かったため、htmlを全てバイト列で持ってレスポンスに書き込むといった点にはかなり驚きました。 研修全体を通して この研修を通して私たちが得たものは大きく分けて 2 つあると思っています。 1 つ目はエンジニアとして成長するためのきっかけです。 社内で利用しているような技術から触ったことがない技術まで本当に幅広く体験することができました。また、成長するためのキャリアについての考え方も学ぶことができました。ただ学んで終わりにするのではなく、学んだ技術を自分のものにし、どう会社に還元していくかを考え行動することが次のアクションとしてあると思います。そして研修を通していただいた分を何らかの形でこの業界に還元していきたいです! 2 つ目は研修を一緒に駆け抜けてくれた仲間です。 同じ年代の方達と会社の枠を超えて巡り会えたのは一番の財産だと思っています。ここで知り合った仲間とのコミュニティを大事にし、健全な嫉妬を与えたり、受けたりしてこれからも切磋琢磨していきたいです!最初はよそよそしかったですが、研修を通して仲を深め、今では輪読会を行うようにまでなりました! この研修を開いてくださった日本 CTO 協会の方々と、講義をしてくださった企業様、スポンサーをしていただいた企業様には本当に感謝しています。ありがとうございました!!
アバター
はじめに こんにちは、Retail Hub 事業部でエンジニアを務めている 羽馬 です。 この記事は、Vue.js 日本ユーザーグループ主催の Vue.js v-tokyo Meetup #21 で登壇した際の発表資料を元に、VueUse というライブラリを使って Vue.js 開発を効率化する方法をご紹介します。 登壇資料はこちら: speakerdeck.com VueUse とは VueUseは、Vue Composition APIのための包括的なユーティリティコレクションです。以下の特徴があります: 200以上の便利な関数を提供 Vue.jsアプリケーション開発の生産性を大幅に向上 ローカルストレージ、デバイス情報、スクロール位置、フォームバリデーションなど、幅広い機能をカバー 宣言的で再利用可能なコンポーザブル ライフサイクルフックの自動処理 必要な機能のみをインポート可能(tree-shaking対応) 高度にカスタマイズ可能なオプション vueuse.org VueUseを使用することで、複雑な機能を簡潔に実装でき、開発効率を劇的に向上させることができます。 VueUseが提供する主な機能 VueUseは非常に多くの機能を提供していますが、ここでは特に有用な機能をいくつか紹介します: 状態管理 useStorage : ローカルストレージやセッションストレージとリアクティブな状態を同期 useState : シンプルな状態管理 センサーと端末情報 useMouse : マウスの位置を追跡 useGeolocation : デバイスの位置情報を取得 useDeviceOrientation : デバイスの向きを検出 ブラウザ操作 useClipboard : クリップボードの操作 useDark : ダークモードの切り替え useFullscreen : フルスクリーンモードの制御 アニメーションとタイミング useInterval : 定期的な処理の実行 useTimeout : 遅延処理の実行 useTransition : スムーズな状態遷移 ネットワークとAPI useFetch : HTTPリクエストの簡易化 useWebSocket : WebSocketの操作 UI操作 useVModel : v-modelの簡易実装 useInfiniteScroll : 無限スクロールの実装 これらの機能を使用することで、一般的なWeb開発タスクを簡単に、そして効率的に実装することができます。 VueUseの具体的な使用例 VueUseの機能の中から、特に有用なものをいくつか抜粋して具体的に説明します: ローカルストレージの利用 ( useStorage ) ローカルストレージの利用 ( useStorage ) import { useStorage } from '@vueuse/core' const state = useStorage ( 'my-storage-key' , { count : 0 }) // stateを変更すると自動的にローカルストレージに保存される state . value . count ++ VueUseを使用しない場合: import { ref , watch } from 'vue' const state = ref ( JSON . parse ( localStorage . getItem ( 'my-storage-key' )) || { count : 0 }) watch ( state , ( newState ) => { localStorage . setItem ( 'my-storage-key' , JSON . stringify ( newState )) } , { deep : true }) // stateを変更する度に手動でwatchを設定する必要がある state . value . count ++ ダークモードの実装 ( useDark ) VueUseを使用する場合: import { useDark , useToggle } from '@vueuse/core' const isDark = useDark () const toggleDark = useToggle ( isDark ) // ダークモードの切り替えが簡単 toggleDark () VueUseを使用しない場合: import { ref , watch } from 'vue' const isDark = ref ( false ) const toggleDark = () => { isDark . value = ! isDark . value if ( isDark . value ) { document . documentElement . classList . add ( 'dark' ) } else { document . documentElement . classList . remove ( 'dark' ) } } watch ( isDark , ( newValue ) => { localStorage . setItem ( 'dark-mode' , newValue ) }) // 初期化時にローカルストレージから設定を読み込む isDark . value = JSON . parse ( localStorage . getItem ( 'dark-mode' ) || 'false' ) 無限スクロールの実装 ( useInfiniteScroll ) VueUseを使用する場合: import { useInfiniteScroll } from '@vueuse/core' const el = ref ( null ) const { arrivedState , reload } = useInfiniteScroll ( el , () => { // 新しいデータをロードする処理 }) VueUseを使用しない場合: import { ref , onMounted , onUnmounted } from 'vue' const el = ref ( null ) const isLoading = ref ( false ) const checkScroll = () => { if ( ! el . value ) return const { scrollTop , scrollHeight , clientHeight } = el . value if ( scrollTop + clientHeight >= scrollHeight - 50 && ! isLoading . value ) { isLoading . value = true // 新しいデータをロードする処理 // 処理が完了したらisLoading.value = falseにする } } onMounted (() => { el . value ?. addEventListener ( 'scroll' , checkScroll ) }) onUnmounted (() => { el . value ?. removeEventListener ( 'scroll' , checkScroll ) }) これらの例から分かるように、VueUseを使用することで、複雑な機能をより簡潔に、そして宣言的に実装できます。特に、ローカルストレージの同期やダークモードの実装など、頻繁に必要となる機能を簡単に実装できる点が大きな利点です。 一方で、VueUseを使用しない場合、より多くのボイラープレートコードが必要となり、ライフサイクルフックの管理やイベントリスナーの設定など、細かい実装の詳細に注意を払う必要があります。 ただし、プロジェクトの規模や要件によっては、外部ライブラリに依存せず、Vueの基本機能のみで実装することが適切な場合もあります。特に、使用する機能が限定的で、バンドルサイズを最小限に抑えたい場合などは、VueUseを使用しない選択肢も考慮に値します。 まとめ VueUseは、Vue.jsアプリケーション開発において非常に強力なツールです。以下に、VueUseを使用することの主な利点をまとめます: 開発効率の向上 : 複雑な機能を少ないコードで実装可能 頻繁に使用される機能が既に最適化されて提供されている コードの可読性と保守性の向上 : 宣言的なAPIにより、コードの意図が明確になる コンポーザブルの再利用が容易 学習曲線の緩和 : Vue.jsのベストプラクティスが組み込まれている 豊富なドキュメントとコミュニティサポート パフォーマンスの最適化 : ライフサイクルフックの自動管理 tree-shakingに対応し、必要な機能のみをバンドル可能 Vue.jsエコシステム互換性 : Vue.js 2、Vue 3、Nuxt.jsなど、様々な環境で使用可能 VueUseを活用することで、開発者はビジネスロジックの実装に集中でき、結果としてより高品質なアプリケーションを効率的に開発することが可能になります。ただし、プロジェクトの要件や規模に応じて、VueUseの使用を検討することが重要です。 今後のVue.js開発において、VueUseは重要なツールの一つとなることが期待されます。継続的な学習と実践を通じて、VueUseの可能性を最大限に活用していくことをお勧めします。
アバター
はじめに 背景 業務フロー 1. 分析の準備 KPIとログの設計 A/Bテストによる効果検証の準備 2. データの準備 エンジニアリング目線も加味しつつログ設計を最適化出来る データのニーズの変化に臨機応変に対応できる 3. データの分析 終わりに はじめに DELISH KITCHENでデータサイエンティストをやっている山西です。 今回は「データサイエンティストとしてプロダクト開発プロジェクトに積極関与した経験談」をお送りします。 背景 DELISH KITCHENをはじめとするプロダクトの開発/改善は、仮説検証サイクルによって日々推進されております。 仮説検証サイクルにより、各機能/改善のリリース後の効果の良し悪しが分析によって振り返られ、次の方策へと繋げられていきます。 本記事では、データサイエンティストの立場で、この仮説検証サイクルを支援するプロダクト改善フローを紹介します。 実践して学んだことや意識したこと等々もポエム調で織り交ぜていきます。 業務フロー プロダクトの仮説検証サイクルにおけるデータ職責としての業務フローは、 分析の準備 データの準備 分析および振り返り に大別できます。 以下、それぞれの業務感について簡単に紹介します。 1. 分析の準備 KPIとログの設計 プロダクトへの新機能追加/または改善を行う際、リリース後にその結果の良し悪しをKPI、特定の指標で観察することになります。 よって、 何を観測したいか →そのためにはどんな準備(分析設計/ユーザーの行動ログ)が必要か をPdM(プロダクトマネージャー)と連携しながら整理していきます。 その具体作業や心持ちは以下の記事に詳しくまとめています。 tech.every.tv 当たり障りの無い主張ではありますが、「目的、仮説をしっかり言語化し共通認識を持ったうえで具体(ログ、分析手法)を詰める」ことの大切さを、実践してみて改めて学ぶことが多々ありました。 ここが疎かになると、 KGIからズレたKPIを設定してしまう → 視点のすり合わせが出来ていない。アウトカムが意識出来ていない。 仕込んだログが宙に浮いたり一人歩きしたりする → ログが使われない、意図されない使われ方をしてしまう。 本来の目的とずれた分析をしてしまう → 統計、機械学習的にはリッチでも、ビジネスサイドから見たらSo What?な示唆になりがち 過剰な準備をしてしまう → ダッシュボード作ったけど見られない、使われない問題 など、苦い着地をしがちです。 企画時点から一蓮托生的にPdMと視点をあわせることが、より良い仮説検証サイクルを目指すため近道になると感じております。 A/Bテストによる効果検証の準備 プロダクトの開発/改善を振り返るための効果検証のアプローチとしては、A/Bテストを採用することが多いです。 過去にその推進の具体感や全体像についてそれぞれ紹介した記事があるので、よろしければご覧ください。 tech.every.tv tech.every.tv 2. データの準備 分析としての準備が出来たら、次はデータの準備に取り掛かります。 1.で策定した分析計画 の具現に向け、 ユーザーの行動ログ等のビッグデータ を目的に応じた形で利用可能な状態を作り出します。 そのために、 Spark, SQLを駆使しつつETLを実装 必要に応じて中間テーブルを実装 BIツール(ダッシュボード)による可視化、定期実行体制の構築 A/Bテストの設定 等々、いわゆるアナリティクスエンジニアリングの部分もデータ職責として一通り行います。 弊社は、データサイエンティストでも結構ETL等、データ処理をはじめとしたエンジニアリングのコードを書いている組織だと勝手に思っています。 ( データエンジニアと完全分業できるほど人員が多く無い」という裏事情も正直あったりはします。 ) これは個人のスペシャリスト志向、ジェネラリスト志向等々のキャリア感にも左右され賛否両論はあると思います。 が、個人的には「"データの成り立ち"や"ビッグデータとしての集計の勘所"を経験で裏打ちできる」ことで生まれるプラスの側面も大きいと考えております。 例えば以下のエピソードが挙げられます。 エンジニアリング目線も加味しつつログ設計を最適化出来る 前段の話に戻りますが、「1. 分析の準備」でログを設計する際には、 事業視点(観察したいKPIに対してどういうログが欲しいか) を考慮していました。 ここにさらに エンジニアリング視点(どういうログだと、効率的にETL, 集計管理できるか) を加味できるようになれば、双方の視点を踏まえたログ設計が可能になります。 データのニーズの変化に臨機応変に対応できる ビジネスサイドの要求は往往にして変化しがちです。 仮説に沿った分析、ログ設計計画を立てたとしても、「違う角度からデータを見たい」という要望は多々発生します。 こういう際、データエンジニアに頼らずとも「自前でデータの奥底にまである程度触ることができ、ETL手法や中間テーブルの持ち方等を再検討して新たなデータのニーズに対応できる」ことは、手の動かしやすさ、俊敏性に直結すると感じております。 3. データの分析 機能がリリースされ、データが観察可能な状態になったら、その経過を KPIの観察 、 BIツールの観察 等で見守ります。 そして、最終意思決定者であるPdMと共に結果や考察をまとめていきます。 仮説検証としては「1. 分析の準備」の内容の履行が主になりますが、必要に応じて追加の事後分析や探索的分析も行います。 例えば、「KPIの単純集計」や「シンプルな可視化」よりもさらに奥行きのある示唆(ユーザーニーズや、根底に潜むユーザークラスタなど)を得たい場合は、データと分析の特性に合った多変量解析やら機械学習手法を選定し実施します。 また、A/Bテストでは、解釈に癖のある「有意差」をどう評価するかなどなど、統計的な知見の解釈のサポートを行ったりもします。 そして、このようにして統計、機械学習の文脈で得られたデータ解釈を、PdM向けに還元することになります。 データの本質に着眼し、良い分析手法を選定し示唆を出すためのデータサイエンスのハードスキル面 その解釈結果をビジネスの言葉に翻訳し、PdMに還元するための言語化等々のソフトスキル面 など、意思決定を支援する計らいはさまざまな難しさはあるものの、新たな知見が生まれたり、成果に寄与したときは達成感があります。 終わりに 簡単ですが、プロダクト開発におけるデータサイエンティスト(アナリティクスエンジニアに近いかもしれません)の立ち回り例を紹介しました。 データ人材としてプロダクトの価値の増大に貢献できるよう、引き続き精進していきたいところであります。 似たような背景、課題感を抱えたどなたかに対して、この記事で何かしら響くものがあれば幸いです。
アバター
はじめに そもそもなぜメンテナンスが必要になってくるのか メンテの手順にどうやって再現性を持たせるか コード化して再現性を持たせる コマンドライン上で再現性を持たせる 事前に模擬メンテを行う アプリケーションコードの変更なしでメンテに追従できるようにする まとめ はじめに こんにちは、トモニテ開発部ソフトウェアエンジニア兼、CTO 室 Dev Enable グループの rymiyamoto です。 今回はバックエンド系を触っている人なら誰しもが一度は関わるであろうメンテナンス(以下メンテと省略します)の話をしていきたいと思います、 実際のメンテのための計画方法は以下の記事で紹介されていますので、気になる方はご覧ください。 tech.every.tv そもそもなぜメンテナンスが必要になってくるのか まずメンテが起きないように基本オンタイムでアプリケーションが更新されるように仕組みを作ることは大切です。 たとえば、CI/CD からブルーグリーンデプロイメントなどの手法を使って、アプリケーションの更新を自動化することで、サービス利用者にとっては透過的にアプリケーションが更新されることができます。 しかしアプリケーションが動いているインフラ周りの変更や、アプリケーションの大規模な仕様変更などが発生すると、どうしてもダウンタイムを伴うメンテナンスが必要になります。 メンテの手順にどうやって再現性を持たせるか サービスを止めるにあたって、メンテの手順を明確にしておくことが大切です。 しかしその手順は人間が行うものであるため、ヒューマンエラーが発生する可能性があります。 そのため、メンテ時には、アプリケーションの状態を元に戻すための手順を明確にしておくことが大切です。 例えば AWS の RDS でメンテンスを行う場合でも以下のような手順を踏みます。 (今回は例でインプレースアップグレードで話をします) バッチが走らないようにする アラートを一時的に無効にする サービスの停止を行う(ロードバランサーの切り替えて固定レスポンスを返す等) DB のバックアップを取る DB のアップデートを適用する アップデート後に動作確認を行う サービスを再開する(ロードバランサーの切り替えを元に戻す) アラートを有効にする バッチを再開する 必要であればバッチの再実行を行う ※実際に mysql8 化をした記事があるので気になる方はご覧ください。 tech.every.tv これらの手順だけでもかなりの数があり、毎回コンソール上から手動で実行するのはミスが発生しやすいです。 意図せぬ設定をしてどこで間違ったのかがわからないということが起こりがちです。 そのため、メンテ時には、アプリケーションの状態を元に戻すための手順を自動化出来るようにしておくことが大切です。 コード化して再現性を持たせる 例えば、Terraform を使って、インフラの状態をコードで管理することで、メンテ時にはコードを実行するだけでアプリケーションの状態を元に戻すことができます。 以下はロードバランサーのルールを変更するコードの例です。 こちらでは、ロードバランサーのリスナールールを変更して、メンテ中には固定レスポンスを返すようにしています。 メンテ解除時は変更差分を取り消して apply するだけで元に戻すことができます。 // メンテ用のルール resource "aws_alb_listener_rule" "server_maintenance_rule" { listener_arn = aws_alb_listener.lb_https.arn priority = 4 // priorityが競合しないように番号を調整しておく action { type = "fixed-response" fixed_response { content_type = "application/json" status_code = "503" message_body = jsonencode ( { message = "メンテナンス中です" } ) } } condition { host_header { values = [ "server.example.com" ] } } } // 通常のルール1 resource "aws_alb_listener_rule" "server_rule" { listener_arn = aws_alb_listener.lb_https.arn priority = 5 // 通常: 1 action { type = "forward" target_group_arn = var.server_tg_arn } condition { host_header { values = [ "server.example.com" ] } } } // 通常のルール2 resource "aws_alb_listener_rule" "dashboard_rule" { listener_arn = aws_alb_listener.lb_https.arn priority = 6 // 通常: 2 action { type = "forward" target_group_arn = var.dashboard_tg_arn } condition { host_header { values = [ "dashboard.example.com" ] } } } // 通常のルール3 resource "aws_alb_listener_rule" "web_rule" { listener_arn = aws_alb_listener.lb_https.arn priority = 7 // 通常: 3 action { type = "forward" target_group_arn = var.web_tg_arn } condition { host_header { values = [ "example.com" ] } } } コマンドライン上で再現性を持たせる また物によってはコード管理していないものもあると思います。 そこでは AWS CLI で事前にコマンドを作成しておくことで、メンテ時にはコマンドを実行するだけで適用することができます。 これは AWS CLI で RDS のインスタンスのエンジンバージョンを変更するコマンドの例です。 本来であればコンソール上でボタンを押して切り替えたり手で打つところがなくなるので、ヒューマンエラーを減らすことができます。 aws rds modify-db-instance \ --db-instance-identifier example-rds \ --apply-immediately \ --engine-version 8 . 0 . 35 \ --option-group-name default:mysql-8-0 \ --db-parameter-group-name mysql80-example-rds \ --allow-major-version-upgrade \ --enable-performance-insights 事前に模擬メンテを行う 実際に手順書を作成していざ本番だと、コマンド上のミスや考慮漏れなどが発生することがあります。 そのため可能であれば、開発環境や QA 環境等で事前に模擬でメンテを行うことで、本番でのミスを減らすことができます。 ただし本番とは異なる環境で行うため、実際のメンテ時には環境の違いを考慮して手順を確認することが大切です。 (実際にハマったものですが、DB アップデート時にインスタンスサイズの違いで開発環境では 15 分程度だったのものが本番では 30 分程度かかりました) そのため出来ることなら本番と近い環境で行うことが望ましいです。 アプリケーションコードの変更なしでメンテに追従できるようにする こちらは再現性の観点からは外れますが、アプリケーションコードの変更なしでメンテに追従すると、サービスへのデプロイをすることなく切り替えが瞬時に行えるようになります。 例えば API 通信をしているアプリケーション(クライアントアプリや web、dashboard など)で事前にメンテ用に特定レスポンス(503)の挙動を考慮しておくことで、メンテ時にはアプリケーションコードの変更なしでメンテに追従することができます。 const response = await fetch ( "https://api.example.com" ) ; if ( response . status === 503 ) { // メンテナンス中の場合の処理 // リダイレクトなり、画面に描画したり return; } // 移行通常の処理 まとめ ここまでメンテに関わってくることはそれなりにありましたが、メンテナンスはサービスを維持するために必要な作業です。 再現性を持たせたり手順を自動化することで、ヒューマンエラーを減らすことができ精神的にもとても楽になります。 完璧にすることは難しいですが、今後ともトラブルなくメンテをやりきれるよう普段から意識してサービス開発に取り組んでいきたいと思います。
アバター
はじめに こんにちは、Retail Hub 事業部でエンジニアを務めている 羽馬 です。 先日、Vue.js 日本ユーザーグループ主催の Vue.js v-tokyo Meetup #21 に登壇する貴重な機会をいただきました。本記事では、その経験を通じて得られた知見や、Vue.js 開発における効率化のヒントをご紹介します。 vuejs-meetup.connpass.com 登壇テーマ:VueUseで実現するVue.js開発の効率化 今回の登壇では、Vue.js 開発者にとって強力な味方となる「VueUse」というライブラリについて紹介しました。発表資料は以下からご覧いただけます speakerdeck.com VueUseとは VueUse は、Vue Composition API のための包括的なユーティリティコレクションです。200以上の便利な関数を提供し、Vue.js アプリケーション開発の生産性を大幅に向上させます。 vueuse.org なぜVueUseに注目したのか VueUseに注目した主な理由は以下の3点です 開発効率の劇的な向上 複雑な処理を数行のコードで実装可能 例: useLocalStorage でローカルストレージ操作が簡素化 コードの再利用性と保守性の向上 Composition API を活用したロジックのカプセル化 コンポーネント間での再利用が容易 Vue.js 開発スキルの向上 コミュニティで広く採用されているベストプラクティスの学習 ソースコードから Vue 3 と Composition API の効果的な使用法を学習 発表のハイライト 1. VueUse の特徴 200以上の多様なユーティリティ ローカルストレージ、デバイス情報、スクロール位置、フォームバリデーションなど幅広くカバー 2. 代表的な機能と使用例 // マウス座標の取得 const { x , y } = useMouse () // クリップボード操作 const { copy , copied } = useClipboard () // 非同期通信 const { data , error , loading } = useAxios ( 'https://api.com/data' ) 3. VueUse がもたらす価値 宣言的で再利用可能なコンポーザブル ライフサイクルフックの自動処理 必要な機能のみをインポート可能(tree-shaking対応) 高度にカスタマイズ可能なオプション 実践的な活用例 VueUseの実践的な活用例をいくつか紹介します レスポンシブデザインの実装 import { useMediaQuery } from '@vueuse/core' const isLargeScreen = useMediaQuery ( '(min-width: 1024px)' ) 無限スクロールの実装 import { useInfiniteScroll } from '@vueuse/core' const el = ref ( null ) const { arrivedState , reload } = useInfiniteScroll ( el , () => { // 新しいデータをロードする処理 }) ダークモードの実装 import { useDark , useToggle } from '@vueuse/core' const isDark = useDark () const toggleDark = useToggle ( isDark ) これらの例からわかるように、VueUseを使用することで、複雑な機能を簡潔に実装できます。 得られた学びと今後の展望 Vue.js v-tokyo Meetup #21 への参加を通じて、以下の点を再認識しました コミュニティの力:Vue.js コミュニティの活気と、知識共有の重要性 継続的な学習の必要性:常に新しいツールや手法を学び、実践することの大切さ 知見の共有:得た知識を社内外で共有し、フィードバックを得ることの価値 今後は、以下の取り組みを行っていきたいと考えています VueUseの社内導入と活用事例の蓄積 コミュニティイベントへの積極的な参加と貢献 社内勉強会やテックブログを通じた知見の共有 おわりに Vue.js v-tokyo Meetup #21 への登壇は、技術的な学びだけでなく、コミュニティとのつながりを深める貴重な機会となりました。イベントを運営してくださったスタッフの皆様、参加者の皆様に心より感謝申し上げます。 イベントの詳細なレポートについては、Vue.js 日本ユーザーグループの公式ブログ もご参照ください。 note.com 最後に、今回の経験を活かし、今後も Vue.js コミュニティへの貢献と、社内での知見共有を続けていきたいと思います。 今後も、このようなイベントや情報共有の場に積極的に参加し、Vue.js コミュニティの発展に少しでも貢献できるよう、努めてまいります。
アバター
はじめに こんにちは。DELISH KITCHEN開発部兼、CTO室 DevEnableグループの村上です。 エブリーでは今年から「社内外から憧れる開発組織へ」というのをミッションにDevEnableグループを設立し、社内活性化から広報・採用活動まで幅広い活動を現場のエンジニアが兼務しながら行っています。DevEnableの設立について詳細はCTOの今井がブログにしているのでぜひご覧ください。 tech.every.tv そのDevEnableの活動の中で今年はすでに Go Conference 2024 と Kotlin Fest 2024 への協賛を行いました。エブリーとしては初のスポンサー活動となったので、改めてこの半年を振り返っていきたいと思います。 なぜスポンサー活動を始めたのか スポンサー活動を始めたのは大きく以下2つの理由からでした。 エブリーという会社をより多くのエンジニアに知ってもらいたい 普段使っている技術のコミュニティへ貢献したい まず採用観点では、エブリーが提供しているサービスは知っていても会社自体は知らないという方も多く、まだまだ会社の認知度に課題がある状況でした。そんな中で、スポンサーとしての登壇やブース運営を通じて、多くのエンジニアとの接点を作りながらエブリーという会社を知ってもらえることへの期待は大きかったです。もちろんスポンサー活動自体、短期的にすぐに採用の成果が出るものではないですが、会社自体の認知度を上げることは転職活動を行うときの転職先の候補として挙げてもらいやすくなるので長期的な視点で見れば大切なことだと思います。 また採用だけではなく、会社としてはより技術コミュニティに貢献していきたいという思いがあります。それは私たちの普段の開発が様々なOSSや技術コミュニティの知見なしに行うことはできず、多くの恩恵を受けているからです。それをただ享受するだけではなく、エブリーとしても技術発信やスポンサー活動を行い、さらに技術コミュニティを盛り上げていくことはこの業界自体の活性化やより良いサービスが生まれていくことに繋がります。 こうした理由からエブリーでも普段使っている技術を中心に協賛するカンファレンスを年の初めに選定していきました。 スポンサー活動としての取り組み スポンサーブースの準備 開催の2ヶ月ほど前からスポンサーブースの企画などの準備をしました。特に今回は初の協賛となるので、エブリー自体がブースを出すのが初めての中で全て0から決めていく必要がありました。 1. テーブルクロス・バックパネル この2つはブースの印象を決める重要な役割となります。今回は広報、デザイナーと相談しながらエブリーが提供するDELISH KITCHENのサービスをテーマにしながら、カラーも含めたデザインを決めていきました。 テーブルクロスやバックパネルは多くの場合で一度作ったものを流用することが多くなると思います。カンファレンスによってブースの大きさも変わってくるので、机の大きさによって折り畳んでも違和感のないデザインにしたり、バックパネルもあえて2つに分けて、小さいブースでは片方のみを使うこともできるようにしました。 これらに加えて、運営Tシャツがあるとよりスポンサーとしてのまとまりも出て印象も強くなると思うのでおすすめです。 2. ノベルティ ブースにわざわざ足を運んでいただいた方に対して何か配れるノベルティがないか考えていきました。ノベルティを考える上で大事にしたのは以下の2点です。 エブリーらしさ、エブリーのサービスのファンになってもらえるようなもの なるべく嵩張らずにすぐに消費できるもの まずエブリーらしさでいうとエブリーが提供するDELISH KITCHENでは元々 キッチンツール の販売をしていたのもあったので、それを活用して抽選でプレゼントする企画を行いました。当日はDELISH KITCHENは使っていた方でもキッチンツールの存在を知ってもらえたり、これを使ってDELISH KITCHEN使ってみますという方もいてとてもいい機会となりました。 また、抽選とは別に全ての人に配れるものとして、コーヒーとクッキーを用意しました。オフラインでは多くの企業からノベルティをもらうのもあって、あまり大きいものだと邪魔になってしまう一方、カンファレンスでは長時間セッションを聞くので小腹が空いたりすることもあるので嵩張らずに消費できるものが良いと考えました。 ちなみにコーヒーは弊社CTOの今井がコーヒー好きというのもあって実は隠れたエブリーらしさがあったりします。いつかはCTOブレンドコーヒーをお披露目したいなとチームで企んでいます(笑) 最後にちょっとした失敗談からの学びとして、ノベルティを企画する際はそのカンファレンスのノベルティに関する注意事項もしっかりと確認しておきましょう。経験値的に個包装の飲食が問題ないと勝手に思って用意していたが、実は別のカンファレンスでは使えなかったということが弊社では発生してしまい急遽ノベルティを考えるということがありました。 3. ブースコンテンツの企画 ノベルティだけではなく、来場者とブースでどういったコミュニケーションを取っていくかも考えました。今回はいくつか候補をあげながら以下のようなテーマ別で企画を行いました。 会社・サービスを知ってもらう 実際にスーパーで設置しているサイネージでのDELISH KITCHENコンテンツの放映 アプリの紹介パネル カンファレンスにまつわる技術の会社内における活用状況を表すインフォグラフィックパネル 来場者同士のコミュニケーション カンファレンスにまつわる技術についてのアンケートボード 楽しんでもらえる参加型コンテンツ XフォローでのDELISH KITCHENツールのプレゼントキャンペーン まずは大前提としてスポンサーすることの目的が採用であったとしても採用色を全面に出すのはあまりおすすめできません。カンファレンスに参加するエンジニアはあくまでもその技術が好きでセッションを聞きにきています。私たちスポンサーはその熱をより盛り上げていくことがブースとしての役割の一つでもあると思っています。私たちはそれを前提に置いた上で今回はオフラインならではのスポンサーと参加者、そして参加者同士がコミュニケーションを取れるようにコンテンツを企画しました。 実際にアンケートボードやインフォグラフィックパネルをきっかけに多くのエンジニアと技術的な話が深くできたり、お互いのことが知れる機会を作ることができました。 エブリーブースにてKotlinのLinterに関するアンケートを行っています!✨ 現在はktlintが優勢です! ぜひブースにお立ち寄りください! #KotlinFest pic.twitter.com/wcVPHBj5vD — 株式会社エブリー 開発部 (@every_engineer) 2024年6月22日 x.com スポンサーセッションの準備 今回、エブリーではGo Conference2024にてスポンサーセッションをさせていただく機会をいただきました。ぜひ発表内容が気になる方はこちらをご覧ください。 speakerdeck.com 25分枠が与えられた中で、運営側で現場エンジニアへの登壇依頼と登壇内容のすり合わせを行いました。内容のすり合わせでは、会社としてこれまでの知見として溜まっていることを多少泥臭くても事例としてリストアップしていきながら登壇者とどういう切り口で進めていくかを会話していきました。基本的にこの準備段階では登壇者の方が大変ですが、私たち運営陣は今後も会社紹介スライドの準備や内容レビュー、発表練習などサポートできることは全力でやっていくスタンスを大切にしていきたいです。 カンファレンス当日の運営 当日はこれまで企画を進めてきた運営メンバーだけではなく、それぞれの技術カンファレンスに対して普段その技術を使っている現場のエンジニアにも声をかけて、参加してもらいました。 当日スタッフにはこれまでの企画背景を知ってもらうように事前に以下のような説明をしました。 ブース内容とその背景 当日のブース運営で意識して欲しいこと、注意点 各メンバーの役割 当日のシフトスケジュール ただこれは振り返ってみてですが、当日は予想外のハプニングがたくさん起きます。基本的な心得としてメンバーに共通認識として持って置いたほうがいいのはある程度それを想定して柔軟に動くことのように思います。実際に混雑予想も加味してシフトスケジュールを組んだが予想外の時間で混雑して急遽シフト外のメンバーがシフトに入ってくれたり、準備してた紙がなくなってチャット内で情報共有したりとそれぞれが主体的に動くことでなんとか乗り切ったことも多いです。 カンファレンス前後でコミュニティの熱をさらに盛り上げる スポンサー活動は当日のブース運営に注目が行きがちですが、スポンサーとしてはその前後でも技術コミュニティを盛り上げていくことも大事です。例えばエブリーでは今回このような取り組みを前後にやってみました。 カンファレンス前 社内のプロポーザル提出に向けてリストアップ、声かけを行う カウントダウンブログと題して当日までの数日間、関連する技術の知見を発信し続ける カンファレンス後 開催当日のカンファレンス参加レポート スポンサー同士でのアフターイベントの開催 カンファレンス前には社内外含めて、当日のカンファレンスに注目が集まるようにその技術に関する発信を強化し、カンファレンス後ではオフライン開催が増える中でどうしても参加できない方が多くなってしまうことが予想できたので、そういった方に向けてカンファレンスの盛り上がりを最速で届けるようなこともしました。 またアフターイベント的にスポンサー同士で技術イベントを開催して、カンファレンスきっかけでのコミュニティの輪を広げられるような取り組みにも初めて挑戦しました。結果として、カンファレンスとはまた違った一人一人との密なコミュニケーションをとることができたり、カンファレンスの感想をお互いに話せたりととてもいい取り組みだったので今後も定期的に続けていきたいです。 every.connpass.com every.connpass.com 反省点としては今回こうした取り組みの動き出しが遅かったことがあります。こうしたカンファレンス前後にも力を入れる場合、その設計も含めて2-3ヶ月前から動き出せると余裕を持って企画、進行ができ、より内容を充実させることができそうです。 半年間スポンサー活動をやってみて こうしたスポンサー活動自体、DevEnableグループだけが単独で動いただけでは到底実現することはできませんでした。専属のチームがいないからこそ、人事、広報、デザイナー、運営外のエンジニアに協力してもらいながら総動員で動けたからこそ実現することができたと思っています。 冒頭に話したように正直1~2回のスポンサー活動で短期的に何か大きな目に見える成果というのは感じにくく、そこは継続的に数年単位でやり続ける長期的な目線が大切になると思います。ただ今回初のスポンサー活動を行っていく中で、普段あまり外部のイベントに参加しないエンジニアも社内には少なくなかったですが、そうしたエンジニアからも参加するいい機会をもらえたという感想を言ってもらえたり、アフターイベントの取り組みで登壇機会を作ることで技術コミュニティに入っていく一歩目を支援できたりと社内のエンジニアの成長にも貢献できている実感がありました。実際にカンファレンスきっかけで技術更新が行われたりもしています。個人的には予想外にそうした採用、認知だけではなく、スポンサー活動を通じて、エブリーで働くエンジニアと技術コミュニティのつながりができ、そこから刺激をもらえる成長機会を作ることができることは気づきでした。 個人的にはこれからはこれをきっかけに社外の技術コミュニティと並行して、社内の技術コミュニティをもっと盛り上げていきながら、それぞれの分野でより知見がたまっていったり、発信が活発的に行われる状態を目指していきたいです。 最後に DevEnableグループではこうしたスポンサー活動以外にも社内活性化も含めて、幅広くエンジニア組織のための取り組みを強化しています。 DevEnableグループを面白そうと思った方や、そんな開発組織で働きたいと思った方はぜひお話しましょう! corp.every.tv
アバター
はじめに そもそも話題の背景 低下の要因たち 仕様変更に至るまでの経緯 計測方法の見直し テストファイルがあるものだけ抽出する(ホワイトリスト型) 除外したい pkg を名指しする(ブラックリスト型) まとめ はじめに こんにちは、トモニテ開発部ソフトウェアエンジニア兼、CTO 室 Dev Enable グループの rymiyamoto です。 最近はエルデンリングが再燃しています。 2024/06/18 に Go Conference 2024 の非公式イベントとして の Go BASH が開催されました。 andpad.connpass.com その際に go の ver 1.22 におけるテストカバレッジについて話をしてきたため、その内容を記事にまとめたものです。 speakerdeck.com そもそも話題の背景 go1.22 が出たタイミンで影響少なそうなリポジトリで試しにアップデートしてみたところ、CI を眺めているとカバレッジが大きく低下しているのを発見しました。 一瞬 go test -cover 壊れたのかと思いましたが、そうではなくリリースノートにも記載されている通り、カバレッジの計算方法が変わったことが原因でした。 tip.golang.org 内容をまとめると Go 1.22 以降、テストファイルのないパッケージでもカバレッジが表示される 関数がカバーされていない場合は 0.0%扱い 実行可能なコードが全くない場合は、「テストファイルがない」と報告される ex.) 構造体やインターフェースの定義のみで関数がない場合など のように、テストカバレッジの状況をより詳細に把握できるようになりました。 低下の要因たち テストを行っていない部分が影響を及ぼしており、それらを以下に列挙します。 自動生成系のもの エンドポイント生成(OpenAPI, GraphQL のジェネレータ) ORM 意図的に書いてない実装 テスト内容がテストコード作成時の労力に見合ってないもの 必要なテスト環境が複雑でチームやプロジェクト規模によっては Skip してるもの 自動生成系のものに関しては、提供されている pkg によってはテストコードの生成までしてくれるものもありますが、それでも全てのケースを網羅するのは難しいです。 また、意図的に書いてない実装は単純にテストをちゃんと書けばいいだけではありますが、プロダクトのフェーズやチームのレベル感次第となります。 一時しのぎとして 1.21 以前と同じ挙動にする場合は、テスト実行時に GOEXPERIMENT = nocoverageredesign を設定することで可能です。 GOEXPERIMENT =nocoverageredesign go test -cover ./... ただし、GOEXPERIMENT を指定すると予告なく使用できなくなる可能性があるため、別の対応策を考える必要があります。 仕様変更に至るまでの経緯 ここまでの話を踏まえて、なぜこのような仕様変更が行われたのか気になり、その原因となった issue を探してみました。 github.com 概要としてはカバレッジレポートの出力方式について改善の余地あるのでは?というものです。 議論自体は 2018/03/18 から行われており、当時は go1.10 時代です。 go1.10 のリリースノートにテストに関する変更が記載されており、その中で一度に複数パッケージのカバレッジが取れるようになったことが記載されています。 go.dev この変更の際に、カバレッジを計測できないテストがないものは、go1.21 と同じように計上されていないままでした。 そのため、カバレッジは計測範囲に対しての全体の割合を出したいという要望が出てきたようです。 中では以下のことが議論がされていました。 テストファイルのないパッケージについてカバレッジ(0%)を出力するべきか 出力した方が未テストの関数があることが分かりやすい 出力しないと総合カバレッジ率が不自然に下がる 総合カバレッジにテストファイルのないパッケージを含めるべきか 含めた方が未テストの関数が明確になる go test -cover と go test -coverprofile でカバレッジ出力を統一するべきか 統一しないとユーザーに混乱を招く可能性 統一すると要件によっては不自然な動作になる可能性 テストファイルがない=未テストなのか、それとも単にテストが書かれていないだけなのか 未テストと見なすべきか コードに応じて判断が分かれる可能性 [no test files]と 0.0%のどちらがユーザーにとってわかりやすいか 決定的な決め手はなかったようですが、やはり明示的に引数で対象とするパッケージを指定している以上レポートに含まれるべきという最初の要望が最終的に採用されました。 上記の話を踏まえて、以下の方針で計測方法を見直すことにしました。 意図的にテストないものをカバレッジ入れたくはない 注視したいのは自分たちが実装している部分 ビジネスロジック周り そもそも不要なものまで計測対象にしているのが良くない 自動生成系、意図的に書いてないところ 引数で対象 pkg は指定して、入れたくないものは除外しておく go test -cover ./… → go test -cover pkg1 pkg2 計測方法の見直し 基本方針をもとにホワイトリスト型とブラックリスト型の 2 つの方法を試してみました。 テストファイルがあるものだけ抽出する(ホワイトリスト型) 専用の抽出スクリプトから絞り込むようにしたものです。 この際 *_test.go があるディレクトリが対象とすれば自動生成系は巻き込まれません メリット これまでと同じような挙動に出来る デメリット コード全体の質を測るうえではテストがないと隠蔽されてしまう listup_test_pkg.sh #!/bin/bash # 現在のディレクトリからすべてのGoパッケージを検索 for pkg in $( go list ./... ) ; do # 各パッケージのディレクトリを取得 pkg_dir = $( go list -f ' {{.Dir}} ' $pkg) # *_test.goファイルがそのディレクトリに存在するか確認 if [[ $( ls $pkg_dir /*_test.go 2 > /dev/null ) ]] ; then # 存在する場合はパッケージ名を表示 echo $pkg fi done go test -cover $( ./listup_test_pkg.sh ) 除外したい pkg を名指しする(ブラックリスト型) 除外対象 pkg を一元管理して、テスト時に除外するようにしました。 管理は何かしらのファイルでできればいいので、yaml や json 等で記載します。 書き方によってはファイル名やディレクトリ構成に規則性があれば管理は容易となります (他の手として go test -cover の結果を grep -v で引き抜く手もありますが余計なテスト実行となるので採用しませんでした) メリット 除外したいものが明示的 今回の変更の背景を考えると現実的な落とし所になりそう デメリット 除外箇所の羅列が面倒で手間がかかる testcnf.yml ignore: - github.com/rymiyamoto/example-api/internal/app/ignore1 - github.com/rymiyamoto/example-api/internal/dashboard/ignore2 - github.com/rymiyamoto/example-api/internal/external/ignore3 exclude_patterns = $( yq e ' .ignore[] ' testcnf.yml | sed ' s/^/-e / ' | tr ' \n ' ' ' ) go test -cover $( go list ./... | grep -vE $( echo $exclude_patterns)) まとめ ここまで go1.22 移行後のテストカバレッジについて話してきましたが、その取り扱い方はプロダクトやチームによって異なると思います。 今回のように仕様変更による影響がある場合は、その変更の背景を知ることで今後の方針を決めるのに役立つはずです。 また、テストカバレッジはあくまで一つの指標であり、それが全てではないことを忘れずに、テストの質やカバレッジの意味を考えることが大切だと感じました。 今後も go のアップデートに合わせてプロダクトの品質向上に努めていきたいと思います。
アバター