every Tech Blog

株式会社エブリーのTech Blogです。

Vue Fes Japan 2024 でゴールドスポンサーとして協賛します!

はじめに

こんにちは、トモニテ開発部ソフトウェアエンジニア兼、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

最後までお読みいただき、ありがとうございました!

Android Studio での Gemini 連携について

はじめに

こんにちは、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 への加入は不要です。

環境構築手順

  1. 公式サイト から Canary 版 (2024/9/13 時点では Ladybug) をダウンロードし、Android Studio を起動します

  2. プロジェクトを生成後、右上にある Gemini のアイコンをクリックします

  3. 「Log in to Google」をクリックするとブラウザが起動するためブラウザで認証を通し、完了後に「Next」をクリックします

  4. プライバシーポリシーを確認し、「Next」をクリックします

  5. 利用規約を確認し、チェックを付けて「Next」をクリックします

  6. プライバシー設定を確認し、任意の項目にチェックを付けて「Finish」をクリックします

以上で Gemini の機能を使用できる準備が完了しました。

操作方法について

操作方法はコメント入力欄に質問内容を入力し 「Submit」 をクリックすると AI が回答をしてくれるという形で、通常のブラウザ上で操作するものと大きな違いはありません。
以下は 「ログイン画面のソースファイルとレイアウトファイルを作成して」 と質問した時の回答です。

AI のサービスを使用したことがある方は違和感なく使用できるかと思います。

ファイル操作

Android Studio の Gemini では通常のブラウザ上で使用するようなチャット形式でやり取りが出来る点に加え、生成したソースコードに対して特殊な操作が行えるようになります。

上記は実際に Android Studio 上の Gemini で生成したソースコードの直下に表示されるアイコンですが、左から順に

  1. Copy → ソースコードをコピーする
  2. Insert at Cursor → カーソルが当たっている箇所に該当のソースを挿入する
  3. Insert in New Kotlin File → ソースコードを新規ファイルとして生成する (レイアウトファイルの場合はレイアウトファイルとして新規で生成する)
  4. 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 のライブラリを設定しておらず、
こちらはソースコードの場合と違ってライブラリが不足していることを検知しませんでした。
レイアウトファイルの自動生成については、手動操作、エラーの解析、ライブラリの追加が必要となってしまいます。

本項でファイルの自動生成を試してみましたが、ソースファイルの生成は容易なものの、レイアウトファイルの生成にはまだまだ課題がありました。

コード補完

前項ではチャット形式でのやり取りについて説明しましたが、実際のソースコードのドキュメント作成や補完を行えるコード補完の機能についても触れたいと思います。
先ほど自動生成したファイルのメソッドで右クリックをすると以下の機能を選択できます。

こちらは上から順に

  1. Document Function "<メソッド名>" → メソッドの KDoc を作成する
  2. Comment Code → 処理にコメントを付ける
  3. Explain Code → 処理の解説をする
  4. Suggest Improvements → 処理の改善案を提示する
  5. 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 (ファイル自動生成)」を組み合わせて使用する形でした。
具体的な例を記載してみます。

  1. 「ログを出力する Utility クラスを作成して」と質問し、出力結果で「Insert in New Kotlin File」を実行しファイルを自動生成する

  2. 「verbose のログを出力するメソッドを作成して」と質問し、出力結果で「Insert at Cursor」を実行しクラスを拡張する

手動で実装することなくチャットによるやり取りのみでクラスの生成、クラスの拡張を行うことが出来ました。
ビジネスロジックを含むようなクラスの生成は難しいかもしれませんが、Utility クラスや data クラスなど単純なメソッドの組み合わせになるようなクラスであればこちらの方が速く実装できる可能性があります。

まとめ

今回 Android Studio の Gemini 機能を使用してみましたが、ブラウザと Android Studio を行き来する手間もなくなり、ファイルの自動生成など Android Studio の操作の手間をかなり軽減し、
また最低限の知識さえあればソースコードをほぼ書かずに実行ができる状態まで作れるため、開発のサポートとしては非常に強力だと感じました。

ただし、前述の通りまだプレビュー版のため、無料版の Gemini を使用していることで回答が返るまでに 10 秒程度かかってしまう点、レイアウトファイルの操作は完全に自動化されていないなど、
まだまだ課題も目立っています。
今後どう改修されていくかは分かりませんが、結局は Android 開発の正しい知識は必須であることには変わりないため、あくまで開発の補助に留めて使用するのがベストかと感じました。

今回紹介した内容が少しでも皆さまのお役に立てれば幸いです。

【2024最新】AWS Data Firehoseを使った際の4つの問題とその解決策

【2024最新】AWS Data Firehoseを使った際の4つの問題とその解決策
【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で logDriverawslogsに設定しその標準出力は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の機能だけで解決させることができました。
  • 具体的には、コンソールで言うところの特定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
    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に転送するアーキテクチャを採用することになると思います。

みなさまの快適なログライフを!

参考

Nuxt Bridge を使用して Nuxt 2 のアプリケーションへサーバーエンジン Nitro を導入した話

Nuxt Bridge を使用して Nuxt 2 のアプリケーションへサーバーエンジン Nitro を導入した話

はじめに

株式会社エブリーでソフトウェアエンジニアをしている桝村です。

本記事では、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 Structure
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 では getRequestHostgetRequestURL のような関数を使用してリクエストから情報を取得しています。

それにより、関数の抽象化を通じてコードの可読性と保守性を向上させることに重きを置いていたり、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 の導入を検討している方にとって、参考になれば幸いです。

Databricks GenieではじめるText-to-SQL

はじめに

こんにちは。
株式会社エブリーの開発本部データ&AIチーム(DAI)でデータエンジニアをしている吉田です。

今回は、Text-to-SQLを実現するDatabricks Genieを紹介します。

Databricks Genie

Databricks Genieは、自然言語を利用してデータ分析が行えるサービスです。
あらかじめデータ、サンプルクエリ、Genieへの指示を登録しておくことで、Genieに対して自然言語でクエリを投げることができます。
これにより、SQLに詳しくない人でもデータ分析を行うことができます。

AI/BI Genie Space とは何ですか?

Genieを利用する

Genieを利用するためには、以下の手順が必要です。

  1. 利用するデータをUnity Catalogに登録する
  2. Genie Spaceを作成する
  3. 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に詳しくない人でも質問を入力するだけでデータ分析を行うことができます。
これにより、データ分析の敷居が下がりデータ活用が進むことが期待されます。