Amazon Athena におけるプリペアドステートメント活用ガイド

この記事は はてなエンジニア Advent Calendar 2025 の12月30日の記事です。

はじめに

アプリケーション開発者にとって、SQL インジェクションを防ぐ プリペアドステートメント は馴染みのある仕組みです。

Athena (Trino ベースのエンジン) もこの標準に準拠しており、?プレースホルダーとして使用するプリペアドステートメントをサポートしています。AWS のドキュメントでは「parameterized queries (パラメータ化されたクエリ)」と呼ばれていますが、一般的な RDBMS におけるプリペアドステートメントと同じ仕組みです。

本記事では、この手法を用いてクエリを実行する方法を AWS SDK の実例と共に解説します。

データ分析で直面する課題

アプリケーションから Athena を利用する際、開発者が直面するのが以下の課題です。

  • セキュリティリスク: ユーザー入力を文字列操作で直接クエリに埋め込むことは、SQL インジェクションのリスクを招きます。
  • パフォーマンス: 同じ構造のクエリであっても毎回ゼロから解析処理が行われるため、実行開始までのオーバーヘッドが発生してしまいます。

そこで使えるのがプリペアドステートメント (パラメータ化されたクエリ) です。

マネジメントコンソールでの実行イメージ

まずはイメージを掴むため、AWS マネジメントコンソールのクエリエディタでの挙動を見てみましょう。

このように、SQL クエリの構文 (ロジック) とパラメータ値 (データ) が明確に分かれていることがわかります。

SDK での実装方法

ここからは、AWS SDK を使った実装方法を紹介します。

Athena の StartQueryExecution API では、パラメータを ExecutionParameters という配列で渡します。

import boto3

athena = boto3.client('athena', region_name='ap-northeast-1')
database_name = 'your_database'
query_result_location = 's3://athena-result/'

# 文字列リテラルとして扱うため、シングルクォート(')を含める
date_from = "'2026/01/01'"
status_code = '200'

response = athena.start_query_execution(
    QueryString="""
        SELECT
            date,
            time,
            elb_status_code
        FROM
            your_table
        WHERE date >= ?
          AND elb_status_code = ?
        LIMIT 10;
    """,
    QueryExecutionContext={'Database': database_name},
    ResultConfiguration={'OutputLocation': query_result_location},
    ExecutionParameters=[date_from, status_code]
)

query_id = response['QueryExecutionId']
print(f"Query started: {query_id}")

文字列型や日付型の場合は、値自体にクォートを含めて渡す必要があります。一般的な SQL クエリとは異なり、Athena では値を自動でクォートしないため、そのまま渡すと TYPE_MISMATCH: line 5:12: Cannot apply operator: varchar = integer というようなエラーになってしまいます。

上記のコードではシングルクォートで囲い "'2026/01/01'" としましたが、扱いづらい仕様なので本番ではヘルパー関数を作ると可読性が上がるかと思います。

まとめ

特に、Lambda や Fargate 、Step Functions などで繰り返しクエリを実行する場面で効果を発揮すると思うので、ぜひ使ってみてください。

参考

docs.aws.amazon.com

Amazon ECS FireLens でタスク ID をログに含める方法 - Classic mode 編

概要

Amazon ECS (以下、 ECS) におけるログ管理では、FireLens と AWS For Fluent Bit を組み合わせた構成が広く採用されています1。この構成により、 AWSLogs ドライバーでは実現できない柔軟なログルーティングとカスタマイズが可能になります。

しかし、ログ分析やトラブルシューティングにおいて、特定のタスクを識別するためのタスク ID が直接的には取得できないという課題があります。FireLens はデフォルトでタスク ARN を提供しますが、多くの運用シナリオではタスク ID そのものが必要になります。

この記事では、Fluent Bit の設定をカスタマイズして、ログにタスク ID を含める方法を解説します。

前提

Fluent Bit のコンフィグには YAML と Classic mode の 2 種類の形式があります。本記事では、現在も広く使用されている Classic mode 形式での設定例を紹介します。

注意: Classic mode 形式は 2026 年末に非推奨となる予定です2。新規プロジェクトでは YAML 形式の採用を検討することをお勧めしますが、既存環境での実装や学習目的では、Classic mode 形式も有効です。

docs.fluentbit.io

課題の詳細

FireLens は、ECS タスクのログにクラスター名やタスク ARN といったメタデータをデフォルトで付与します3

しかし、タスク ID そのものは独立したフィールドとして提供されません

また、 ECS Filter という Fluent Bit のプラグインがありますが、EC2 起動タイプでのみ機能し、 Fargate 起動タイプでは利用できません。

これにより、以下のような運用上の課題が発生します:

例えば、CloudWatch Logs で特定のタスクのログを検索する際、タスク ARN 全体での検索が必要となり、クエリが複雑になってしまいます。

解決方法: Fluent Bit によるタスク ID 抽出

この課題を解決するため、Fluent Bit のフィルター機能を使用してタスク ARN からタスク ID を抽出し、独立したフィールドとして追加する方法を実装します。

アプローチの概要

既存の FireLens 機能を活用しながらタスク ID を取得します。

  1. FireLens が提供するecs_task_arn フィールドを取得
  2. Fluent Bit の正規表現フィルターでタスク ID を抽出
  3. 新しいフィールド ecs_task_id としてログレコードに追加

タスクARN 形式

ECS タスク ARN には、アカウント設定により 2 つの形式が存在します4:

  • 従来形式: arn:aws:ecs:region:aws_account_id:task/task-id
  • 新形式: arn:aws:ecs:region:aws_account_id:task/cluster-name/task-id

どちらの形式でも、最後のスラッシュ (/) 以降がタスク ID であることがポイントです。

コンフィグ例

パーサー

タスク ARN をパースしてタスク ID を取得するコンフィグは次のようになります。

[PARSER]
    Name    ecs_task_id
    Format  regex
    Regex   .*\/(?<ecs_task_id>[^\/]+)$

正規表現の解説:

  • .*\/: 文字列の先頭から一番最後の / までにマッチ (貪欲マッチ)
  • (?<ecs_task_id>[^\/]+): ecs_task_id という名前付きキャプチャグループで、最後の / 以降の、/ を含まない 1 文字以上の文字列 (つまりタスク ID) を取得。これにより新形式と従来形式の ARN 両方に対応
  • $: 文字列の終端を示す

フィルター

次のように Filter から呼び出します。これにより、 ecs_task_id というフィールドがログに追加されます。

[FILTER]
    Name          parser
    Match         *-firelens-*
    Key_Name      ecs_task_arn
    Parser        ecs_task_id
    Reserve_Data  true
    Preserve_Key  true

結果

ログをクエリして、ログにタスク ID (ecs_task_id 列) が正しく追加されることを確認しました。

SELECT 
    dt,
    ecs_task_id
FROM logs
WHERE dt > '2025/10/01'
LIMIT 10;

Amazon Athena でログをクエリした結果

おわりに

この記事では、 ECS の FireLens において、タスク ID をログに含める実践的な方法を詳しく解説しました。

今後 AWS For Fluent Bit v3.0.0 を検証して YAML 編も出すかもしれません。

参考

実装

OpenTelemetry Collector の Resource Detection Processor の実装を参考にしました。この実装でもタスク ARN をパースしてタスク ID を取得しています。

// Parses ECS Task ARN into subcomponents according to its spec
// See: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-account-settings.html#ecs-resource-ids
func parseTaskARN(taskARN string) (region, account, taskID string) {
    parts := strings.Split(taskARN, ":")
    if len(parts) >= 5 {
        region := parts[3]
        account := parts[4]

        // ECS Task ARNs come in two versions. In the old one, the last part of the ARN contains
        // only the "task/<task-id>". In the new one, it contains "task/cluster-name/task-id".
        // This handles both cases.
        taskInfo := parts[5]
        taskInfoParts := strings.Split(taskInfo, "/")
        taskID := taskInfoParts[len(taskInfoParts)-1]

        return region, account, taskID
    }

    return "", "", ""
}

github.com

ドキュメント

docs.aws.amazon.com

github.com

Amazon ECS & AWS Fargate 運用アーキテクチャ2025 / Amazon ECS and AWS Fargate Ops Architecture 2025 - Speaker Deck

Terraform cloudinit Provider を使って MIME multi-part 形式の cloud-init 設定を管理する

概要

この記事では、Terraform の cloudinit Provider を紹介します。 まず、 cloud-initMIME multi-part 形式を説明した後、この Provider を使った Amazon EC2 の IaC (Infrastructure as Code) を紹介します。

前提

以下のバージョンを参考にしています。

  • hashicorp/cloudinit Provider: v2.3.7
  • cloud-init (Amazon Linux 2023): 23.4.x

cloud-initMIME multi-part 形式

まずはじめに、 cloud-initMIME multi-part 形式について説明します。

MIME multi-part 形式とは、異なる複数のタイプを組み合わせることができる形式です。 例えば、 text/cloud-config (Cloud-config) と text/x-shellscript (ユーザーデータスクリプト) を組み合わせることができます。

MIME multi-part の設定例

Content-Type: multipart/mixed; boundary="===============2389165605550749110=="
MIME-Version: 1.0
Number-Attachments: 2

--===============2389165605550749110==
Content-Type: text/cloud-boothook; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="part-001"

#!/bin/sh
echo "this is from a boothook." > /var/tmp/boothook.txt

--===============2389165605550749110==
Content-Type: text/cloud-config; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="part-002"

bootcmd:
- echo "this is from a cloud-config." > /var/tmp/bootcmd.txt
--===============2389165605550749110==--

User-data formats - cloud-init 25.3 documentation

MIME multi-part 形式の課題

MIME multi-part は便利ですが、上記に示した例のように人間が読むには複雑です。

そこで、 Terraform の cloudinit Provider が役立ちます。

Terraform cloudinit Provider

cloudinit Provider は、 cloud-init の設定パーツを Terraform のコードとして記述し、自動で MIME multi-part 形式にレンダリングしてくれるデータソースを提供します。

この Provider は HashiCorp 公式で、すぐに利用できます。

registry.terraform.io

Amazon EC2user_data で使う例

ここからは Amazon EC2 (以下、 EC2) を使った Terraform コード例を紹介します。

EC2 は cloud-init をサポートしています。

ここでは、次のようなモジュール構成を想定します。

.
└── modules
    └── compute
        ├── files
        │   ├── cloud-config.yaml
        │   └── script.sh
        └── main.tf

compute モジュール:

1. Cloud-config 形式のファイルの作成

パッケージの更新と git のインストールを行います。

modules/compute/files/cloud-config.yaml

#cloud-config
package_update: true
package_upgrade: true

packages:
  - git

2. ユーザーデータスクリプトの作成

簡単なログを出力するスクリプトです。

modules/compute/files/script.sh

#!/bin/sh
set -e
echo "Hello from cloud-init shellscript part!" >> /tmp/cloud-init-log.txt

3. cloudinit_config データソースの設定と EC2 への適用

cloudinit_config データソースを使って files ディレクトリの 2 つのファイルを結合し、 EC2 インスタンスのユーザーデータに渡します。

modules/compute/main.tf (cloud-init に関連する部分を抜粋)

data "cloudinit_config" "this" {
  # EC2 ユーザーデータは 16KB の制限があるため、データサイズを削減するとよい
  gzip          = true
  base64_encode = true

  # ユーザーデータスクリプト
  part {
    filename     = "script.sh"
    content_type = "text/x-shellscript"

    content = file("${path.module}/files/script.sh")
  }

  # Cloud-config
  part {
    filename     = "cloud-config.yaml"
    content_type = "text/cloud-config"

    content = file("${path.module}/files/cloud-config.yaml")
  }
}

data "aws_ssm_parameter" "amazon_linux_2023" {
  name = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.12-arm64"
}

resource "aws_instance" "this" {
  ami           = data.aws_ssm_parameter.amazon_linux_2023.value
  instance_type = "t4g.small"

  # レンダリングされた user_data を渡す
  user_data_base64 = data.cloudinit_config.this.rendered

  # user_data の変更時にインスタンスを再作成
  user_data_replace_on_change = true

  # 必要に応じてセキュリティグループなどを設定
  # ...
}

file() 関数で静的なファイルを読み込んでいますが、templatefile() 関数を使えば Terraform の変数をファイルに埋め込むこともでき、より動的な設定が可能です。

おわりに

cloudinit Provider を紹介しました。紹介しているブログが他になさそうだったのでこの記事を書いたのですが、便利だと思うので使ってみてください。

参考

cloudinit.readthedocs.io

github.com

https://developer.hashicorp.com/terraform/tutorials/provision/cloud-initdeveloper.hashicorp.com

docs.aws.amazon.com

Nginx の公式 Docker イメージで /etc/resolv.conf のネームサーバーを resolver に使う方法

概要

Nginx の公式 Docker イメージを使用する際、コンテナの /etc/resolv.conf に記述されたネームサーバーを Nginx の設定ファイルで利用したい場合があります。本記事では、公式に提供されている機能でこの要件を満たす方法を解説します。

前提

Nginx の公式 Docker イメージの使用が前提です。

やり方

次の手順で設定します。

  1. 環境変数 NGINX_ENTRYPOINT_LOCAL_RESOLVERS を設定する
  2. 環境変数 NGINX_LOCAL_RESOLVERS を使う

1. 環境変数 NGINX_ENTRYPOINT_LOCAL_RESOLVERS を設定する

コンテナ起動時に、環境変数 NGINX_ENTRYPOINT_LOCAL_RESOLVERS を設定します。この変数は定義するだけで機能するため、空文字でなければどのような値でも構いません (下記の例では true を設定しています)。

docker コマンドの引数で設定する場合、次のようなコマンドになります。

docker run -e NGINX_ENTRYPOINT_LOCAL_RESOLVERS=true -d nginx

2. 環境変数 NGINX_LOCAL_RESOLVERS を使う

手順 1. で環境変数 NGINX_ENTRYPOINT_LOCAL_RESOLVERS を定義すると、/etc/resolv.conf のネームサーバーの設定が環境変数 NGINX_LOCAL_RESOLVERS に設定されます。この変数は Nginx の設定ファイルで使用できます。

環境変数 NGINX_LOCAL_RESOLVERS を使用して resolver ディレクティブを設定すると次のようになります。

http {
    resolver ${NGINX_LOCAL_RESOLVERS};
    # ...
}

Nginx の設定ファイルに環境変数を埋め込んで使う方法は、公式ドキュメント を参照してください。

おわり

この機能はドキュメントに記載が見つからず、紹介しているブログ記事もなさそうだったので書きました。エントリーポイントスクリプトを読むのが一番わかりやすいと思います。下記にリンクを貼ったのでそちらも参考にしてください。

参考

2024年の振り返り

2024 年を振り返ってよかったものをいくつか紹介します。

去年:

chaya2z.hatenablog.jp

仕事

新卒2年目。

ロールは変わらず SRE ですが、9月ごろにチーム異動しました。全社横断的な SRE から Embedded SRE と呼ばれるチーム組み込みの SRE になりました。

7月には祇園祭と合わせて京都のオフィスに遊びに行きました。

京都

『ベタープログラマ

www.oreilly.co.jp

発売は 2024 年ではないけど、2024 年に読んだので紹介します。

今まで読んだ技術書の中でもかなりいい本でした。どうやって学ぶか、どういう姿勢でプログラミングに向かうかという話や、32章「完了したときが完了」にはタスクの完了を定義する、タスクを分解するといった仕事でも役立つ内容もあります。目次を見ればひと目でいい本だとわかってもらえるはず。

『この平坦な道を僕はまっすぐ歩けない』

www.shinchosha.co.jp

ハライチ岩井のエッセイ。ラジオで『平坦道僕』と呼ばれてるやつ。収録されてる話はどれも好きだけど、1つ選ぶなら「高校生の僕と加藤と"もえたん"」かな。ラジオのトークにできるというのもあるかもしれないけど、30代になっても趣味を続けたり、新しいことに挑戦したりする生き方は憧れ。

ラジオ

オードリーのオールナイトニッポン

www.allnightnippon.com

去年と変わらず聞いてます。東京ドームライブが今年あって、それに向けた YouTube 企画もあり熱が高まりました。現地は行けなかったけど、U-NEXT でのライブ配信を家で見ました。

それと、オールナイトニッポン JAM というオールナイトニッポンのサブスクに今年から入りました。この記事を書きながらオードリーの過去の放送を聞いているところです。音楽が当時のままではなかったり、トークがカットされていたり (ひどいと回そのものがカット) と少し残念なところもあったけど、入ってよかったサブスク。

アニメ

機動戦士ガンダムSEED FREEDOM』

www.gundam-seed.net

公開初日に観に行きました。幼少期から何度も見て脳に焼き付いたバンク・BGM が流れて最高でした。

ゲーム

『ARMORED CORE VI FIRES OF RUBICON』

www.armoredcore.net

発売は 2024 年ではないけど、2024 年にクリアしたので。

『Secret Level』というゲーム作品の映像化がアマプラで公開され、その中にアーマードコアもありました。そこで再燃し、年末には全実績解除までやりこみました。

AC6 実績

おわりに

世代作品の新作やリメイクが増えていてそういう年代になったのか?という感じがします。来年は引っ越しで環境を変える予定なので、それを機にいろいろ挑戦したいところ。まずは外に出るところから。

それではよいお年を。

Renovateが作るPRの説明に任意のメッセージを挿入する

この記事は はてなエンジニア Advent Calendar 2024 の22日目の記事です。昨日は id:deflis55 さんの 「【SwiftUI】UIViewRepresentable の使い方とライフサイクルを理解する」 でした。

はじめに

Renovate が作る PR の説明 (description) に任意のメッセージを挿入する方法を紹介します。

次のページを参考にしました。

docs.renovatebot.com

前提

Renovate をリポジトリにインストールしていることが前提です。

docs.renovatebot.com

使ってみる

renovate.json に次のように prHeaderprFooterprBodyNotes を設定します。

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "prHeader": "> [!NOTE]\n> ここが prHeader",
  "prFooter": "> [!NOTE]\n> ここが prFooter",
  "prBodyNotes": "> [!NOTE]\n> ここが prBodyNotes"
}

すると、Renovate が作る PR は次の画像のようになります。赤枠で囲った部分がそれぞれ今回挿入したメッセージです。

挿入したメッセージ

  • prHeader: 説明の冒頭にメッセージを挿入します
  • prFooter: 説明の末尾にメッセージを挿入します
  • prBodyNotes: テーブルの下にメッセージを挿入します

おわりに

私のチームでは prHeader にレビューのポリシーを書いています。例えば、「リリースノートを読んで破壊的変更があるか確認する」とメッセージを入れておけばレビューがスムーズになると思うのでおすすめです。

はてなエンジニア Advent Calendar 2024 の22日目の記事でした。明日の担当は id:fxwx23 さんです。

ISUCON14参加記 (最終スコア 10,285)

ISUCON14 にチーム「完璧な一日」で参加しました。技術サークルの友人2人とチームを組みました。

最終スコアは10,285でした。

関連:

isucon.net

github.com

オブザーバビリティ

OpenTelemetry を使ってオブザーバビリティのある状態を作りました。実際には十分なオブザーバビリティとは言えない様子だったのですが、最低限競技に使えるくらいにはなりました。

オブザーバビリティバックエンドは Honeycomb というサービスを使いました。

www.honeycomb.io

次のようなダッシュボードを作り、ボトルネックを探すのに役立てました。

例1. Nginx のリクエストを遅い順に並べる

Nginxのリクエス

例2. MySQL クエリのダイジェストを遅い順に並べる

MySQLのクエリ

もちろん、これらの設定を当日準備するのは時間がかかるので、事前にコンフィグのスニペットを作ったり、Ansible で OpenTelemetry Collector をインストールできるようにしています。また、オーバーヘッドもあるので、競技終了直前に OpenTelemetry Collector のサービスは止めました。

サーバー構成

競技環境の3台のサーバーは次の構成にしました。

構成図

  • ユーザーからのリクエストを受け付ける Nginx とアプリ本体を動かすサーバー
  • MySQL 専用のサーバー
  • isuride-matcher サービスを動かすサーバー

「サーバーは3台使えたほうがいいだろうから、すぐに分けられるところを分けよう」という方針でやったので、もっといいやり方がありそうです (MySQL を分けるとか) 。

やりたかったこと

大会本番にできなかったことは、MySQL でパフォーマンススキーマを使った分析です。OpenTelemetry の SQL Query Receiver と合わせてモニタリングできたら便利だったなと思っています。

例えば、実行したクエリについて rows_examinedrows_sent をモニタリングできると、問題あるクエリの発見が早くなるだろうと思ってます。

感想

競技環境はダッシュボードがかっこよかったですし、ベンチマークがすぐに実行されるのもよかったです。

個人的には、参加登録時点で OpenTelemetry を使いたいと思っていたので使えてよかったです。また、データベースに苦手意識があったのですが、今回の ISUCON を機に入門して苦手意識が減ったのもよかったです。

最後に、チーム組んでくれた2人に感謝。