こんにちは、近藤(りょう)です!
Amazon Aurora PostgreSQL(Aurora PostgreSQL) に pg_columnmask 拡張が追加され、データベース側で「動的データマスキング(Dynamic Data Masking)」を実現できるようになりました。
aws.amazon.com
これにより、アプリケーションを変更せずに、ロールごとにマスクされたデータを返す柔軟なアクセス制御が可能になります。
本記事では、pg_columnmask を用いた動的データマスキングの仕組みと設定方法、ロール別の動作を確認し、1000 万行のデータによる性能検証(フルスキャン / SELECT / ORDER BY / GROUP BY / JOIN)も行います。また、運用上の注意点やベストプラクティスについても解説します。
動的データマスキング(Dynamic Data Masking)とは?
動的データマスキング(DDM)は、ユーザーがデータベースへクエリを実行した際に、返却される値のみをマスクする仕組みです。
データそのものは変更せず、ロールごとにどの程度の詳細な値を表示するかを柔軟に制御できるため、アプリケーションの改修なしで機密情報を安全に扱える点が特徴です。
Aurora PostgreSQL では、pg_columnmask 拡張を有効化することで DDM が利用でき、次のようなマスキングを実現できます。
- 氏名の一部伏せ字(例:山田太郎 → 山田**)
- メールアドレスの部分マスク(例:taro@example.com → t*@example.com)
- 数値の丸め や ゼロ化(例:残高、個数などを粗い粒度で返す)
マスキングは列単位で柔軟に設定でき、ロールごとに異なるマスキングポリシーを適用することも可能です。
DDM は、データの閲覧権限をきめ細かく制御したい場合や、アプリケーションの改修コストを抑えながら機密情報を保護したい場面に適しています。
特に、分析環境や複数部門が利用するシステムなど、利用者ごとに異なる可視性が求められるケースで有効に活用できます。
pg_columnmask 拡張とは?
Aurora PostgreSQL の pg_columnmask は、ユーザーやロールに応じて SELECT 結果を動的にマスキングするための独自拡張です。
クエリ実行時にマスク関数を適用するよう自動で書き換える仕組みを持ち、アプリケーション側の修正なしで柔軟なアクセス制御を実現します。
Aurora PostgreSQL 固有の機能であり、OSS PostgreSQL の拡張一覧(Additional Supplied Modules)には含まれていません。
検証環境の準備
pg_columnmask を利用した動的データマスキングの動作検証を行うため、Aurora PostgreSQL Serverless v2 と、接続用の Amazon EC2 インスタンスを用いたシンプルな構成で環境を準備します。
前提条件
■ Aurora PostgreSQL のバージョン
本検証で利用する動的データマスキング(DDM)は、Aurora PostgreSQL の特定バージョン以降でサポートされています。
- Aurora PostgreSQL のバージョン要件
- Aurora PostgreSQL 16.10 以上
- Aurora PostgreSQL 17.6 以上
■ クライアント用インスタンス(EC2)
Aurora PostgreSQL に対して接続し、DDL 実行や性能検証を行うため、以下の CLI が利用可能である必要があります。
- AWS CLI(SSM 経由で EC2 に接続)
- SSM Session Manager(SSH は不要)
- psql(PostgreSQL クライアントツール)
検証構成
Aurora PostgreSQL Serverless v2 とクライアント用の EC2 インスタンスを利用し、Aurora PostgreSQL の動的データマスキング(DDM)の動作を確認します。
CloudFormation(CFn)テンプレートをデプロイすると、Aurora PostgreSQL に接続するためのエンドポイントや接続情報が Outputs に出力されますので、接続時はこちらをご参照ください。

■ スペック
動作検証で使用する環境のスペックは以下の通りです。
- データマスキング検証用DB(Aurora PostgreSQL Serverless v2)
- エンジン:Aurora PostgreSQL 16.10
- 構成:Serverless v2
- スケーリング:2 ACU 固定(MinCapacity = 2 / MaxCapacity = 2)
- クライアント用インスタンス(EC2)
- OS:Amazon Linux 2023
- インスタンスタイプ:t3.small
■ CFn テンプレート
以下の CloudFormation テンプレートを利用することで、本記事と同じ検証環境(Aurora PostgreSQL Serverless v2 + EC2)をそのまま構築できます。
動作を確認したい方は、こちらのテンプレートをご活用ください。
■ サンプルテーブル/ロール/データ
pg_columnmask の基本動作を確認するために、検証用の顧客情報テーブルとロール(ユーザー)ごとに異なるマスキングポリシーを設定します。
DDL の投入は postgres ユーザー(または同等権限のロール)で実行してください。
なお、各ロールのパスワードについてDDLをご確認下さい。
- テーブル
- customers(検証用の顧客情報テーブル)
- ロール 及び マスキングポリシー
- analyst(分析担当者)
個人情報は強めにマスク
(email / ssn 全てマスク、salary は粗粒度) - support(サポート担当者)
必要な範囲のみ部分マスク
(email のローカル部のみマスク、ssn 末尾4桁表示) - admin_user(管理者)
すべてのデータをフル表示
(マスキングなし)
- analyst(分析担当者)
以下、データが登録されます。
id | name | email | ssn | salary ----+-------------+--------------------+-------------+---------- 1 | Taro Yamada | taro@example.com | 123-45-6789 | 75000.00 2 | Hanako Sato | hanako@example.com | 987-65-4321 | 85000.00 3 | Jiro Tanaka | jiro@example.com | 555-12-3456 | 65000.00
pg_columnmask が提供するマスキング関数は、以下の 3 種類のマスキング関数が用意されています。
- mask_email(メールアドレス用マスク)
- mask_text(任意の文字列マスク)
- mask_timestamp(時刻データの粒度を落としてマスク)
pg_columnmask 機能を有効にするには、以下のSQLの実行が必要となります。
CREATE EXTENSION pg_columnmask;
実際に Aurora PostgreSQL 上で確認すると、拡張に含まれる関数は以下の通りです。
> SELECT proname FROM pg_proc WHERE pronamespace = 'pgcolumnmask'::regnamespace AND proname LIKE 'mask_%'; proname ---------------- mask_email mask_text mask_timestamp
マスキングポリシーの詳細はこちらをご確認下さい。
ロール別 動作検証
analyst(分析担当者) 動作検証
■ マスキングポリシー
email / ssn 全てマスク、salary は粗粒度
CALL pgcolumnmask.create_masking_policy(
'analyst_policy',
'public.customers',
JSON_BUILD_OBJECT(
'email', 'pgcolumnmask.mask_email(email)',
'ssn', 'pgcolumnmask.mask_text(ssn)',
'salary', 'ROUND(salary, -4)'
)::JSONB,
ARRAY['analyst'],
100
);
■ 結果
testdb=> SELECT * FROM customers; id | name | email | ssn | salary ----+-------------+--------------------+-------------+---------- 1 | Taro Yamada | XXXX@XXXXXXX.com | XXXXXXXXXXX | 80000.00 2 | Hanako Sato | XXXXXX@XXXXXXX.com | XXXXXXXXXXX | 90000.00 3 | Jiro Tanaka | XXXX@XXXXXXX.com | XXXXXXXXXXX | 70000.00 (3 rows)
support(サポート担当者)動作検証
■ マスキングポリシー
email のローカル部のみマスク、ssn 末尾4桁表示
CALL pgcolumnmask.create_masking_policy(
'support_policy',
'public.customers',
JSON_BUILD_OBJECT(
'email', 'pgcolumnmask.mask_email(email, ''*'', true, false)',
'ssn', 'pgcolumnmask.mask_text(ssn, ''*'', 0, 4)'
)::JSONB,
ARRAY['support'],
200
);
■ 結果
testdb=> SELECT * FROM customers; id | name | email | ssn | salary ----+-------------+--------------------+-------------+---------- 1 | Taro Yamada | ****@example.com | *******6789 | 75000.00 2 | Hanako Sato | ******@example.com | *******4321 | 85000.00 3 | Jiro Tanaka | ****@example.com | *******3456 | 65000.00
admin_user(管理者)動作検証
■ マスキングポリシー
ポリシーなし
■ 結果
testdb=> SELECT * FROM customers; id | name | email | ssn | salary ----+-------------+--------------------+-------------+---------- 1 | Taro Yamada | taro@example.com | 123-45-6789 | 75000.00 2 | Hanako Sato | hanako@example.com | 987-65-4321 | 85000.00 3 | Jiro Tanaka | jiro@example.com | 555-12-3456 | 65000.00 (3 rows)
パフォーマンス検証
pg_columnmask を有効にした Aurora PostgreSQL に対して、
1000 万件のデータを用いた性能検証の結果をまとめます。
テスト条件
- DB エンジン:Amazon Aurora PostgreSQL 16.10
- インスタンス構成:Aurora Serverless v2(2 ACU 固定)
- テーブル:
customers(1000 万件) - テストロール:
admin_user:マスキングなし(フルアクセス)analyst:強マスク(email/ssn全マスク +salary粗粒度化)support:部分マスク(emailローカル部のみマスク、ssn末尾 4 桁表示)
- その他
- 各ロールのテスト前に
DISCARD ALLを実行し、セッションキャッシュをクリア - 計測は
EXPLAIN ANALYZEのExecution Timeを使用
- 各ロールのテスト前に
ケース 1:フルスキャン(COUNT)
■ ユースケース
分析バッチ / ETL などで全件カウント・集計を行うケースを想定
SELECT COUNT(*) FROM customers;
| ロール | 実測値 3回(ms) | 平均(ms) | 備考 |
|---|---|---|---|
| analyst | 2044.8 / 1976.3 / 2404.7 | 2142 | マスク影響ほぼなし |
| support | 2094.3 / 2405.6 / 1991.9 | 2164 | ほぼ analyst と同等 |
| admin_user | 2410.7 / 1823.2 / 2403.5 | 2212 |
ケース 2:SELECT * LIMIT 10000
■ ユースケース
画面一覧表示や管理画面での「先頭の一定件数だけ表示する」ようなクエリ
SELECT * FROM customers LIMIT 10000;
| ロール | 実測値 3回(ms) | 平均(ms) | 備考 |
|---|---|---|---|
| analyst | 9.480 / 9.559 / 9.765 | 9.60 | |
| support | 7.903 / 8.577 / 8.791 | 8.42 | |
| admin_user | 1.824 / 1.814 / 1.873 | 1.84 | 最速 |
ケース 3:WHERE + ORDER BY
■ ユースケース
BI 分析で「給与帯」や「金額レンジ」グルーピングを模倣するクエリ
SELECT * FROM customers WHERE salary > 50000 ORDER BY salary DESC LIMIT 1000;
| ロール | 実測値 3回(ms) | 平均(ms) | 備考 |
|---|---|---|---|
| analyst | × / × / × | ― | マスク列のため、対象外 |
| support | 10384 / 8945 / 9306 | 9545 | |
| admin_user | 5502 / 4373 / 5185 | 5020 | 最速 |
※ analyst はマスク制約により実行不可
ケース4:GROUP BY
■ ユースケース
BI 分析で「給与帯」や「金額レンジ」グルーピングを模倣するクエリ
SELECT ROUND(salary, -4) AS salary_range, COUNT(*) \ FROM customers \ GROUP BY ROUND(salary, -4) \ ORDER BY salary_range \ LIMIT 20;
| ロール | 実測値 3回(ms) | 平均(ms) | 備考 |
|---|---|---|---|
| analyst | × / × / × | ― | 対象列がマスク対象のため、実行不可 |
| support | 10623 / 12298 / 13215 | 12045 | |
| admin_user | 10007 / 11715 / 10197 | 10639 | 最速 |
※ analyst はマスク制約により実行不可
ケース5:JOIN(自己結合)
■ ユースケース
「ユーザー情報 JOIN 注文情報」などの多テーブル JOIN を模倣したクエリ
SELECT c1.name, c1.email, c2.salary \ FROM customers c1 \ JOIN customers c2 ON c1.id = c2.id + 1 \ WHERE c1.id <= 100000 \ LIMIT 1000;
| ロール | 実測値 3回(ms) | 平均(ms) | 備考 |
|---|---|---|---|
| analyst | 5114 / 7026 / 6516 | 6219 | |
| support | 5276 / 4795 / 5884 | 5318 | |
| admin_user | 5256 / 5133 / 4587 | 4992 | 最速 |
パフォーマンス検証 まとめ
| クエリ | analyst | support | admin_user | 備考 |
|---|---|---|---|---|
| フルスキャン(COUNT) | 2142ms | 2164ms | 2212ms | ほぼ差なし |
| SELECT * LIMIT 10000 | 9.6ms | 8.4ms | 1.84ms | |
| WHERE + ORDER BY | × | 9545ms | 5020ms | |
| GROUP BY | × | 12045ms | 10639ms | |
| JOIN | 6219ms | 5318ms | 4992ms |
COUNT(*) のような rewrite を必要としないクエリ では、マスク有無による性能差はほとんど見られませんでした。
一方で、マスキング処理の有無で実行時間に影響 していることが確認できました。
今回のテスト規模(1000 万件)でも一定の差分は見られたため、より大規模なテーブル(数億行)では、この差がさらに顕著に現れる可能性が高いと考えられます。
補足:analyst ロール のエラーについて
analyst ロールでは、マスクされたカラム(email, ssn, salary)に対してテスト3(WHERE句)やテスト4(GROUP BY)を使用するクエリが実行できませんでした。これは pg_columnmask の制約によるもので、マスクされたカラムに対する述語(Predicates)の使用が制限されています。
以下が発生したメッセージとなります。
ERROR: Predicates on masked columns are not allowed HINT: Modify your query to exclude masked columns from predicates or joins.
参考:Performance insights の結果
以下、1回目~3回目実施中のPerformance insights の状況です。

参考:パフォーマンス検証用 - サンプルスクリプト
パフォーマンス検証をする時に利用したサンプルスクリプトとなります。
エンドポイント名を変更してご利用ください。(ユーザー名等を変更している場合はも修正お願いします。)
ベストプラクティス
Amazon Aurora PostgreSQL の動的データマスキング(pg_columnmask)を安全に運用するためには、「ロール設計」「マスキング関数の作成」「テーブル/ビューのトリガー扱い」の3つが重要です。
ロール設計:ロールベースのセキュリティアーキテクチャ
権限はユーザー個別ではなく ロール(=グループ) で管理し、必要なテーブルにのみ SELECT を許可するようにします。
スキーマ全体へ広い権限を付与せず、analyst、support のように職務別ロールを作り、ユーザーに割り当てることで最小権限の原則を維持しつつ、運用・監査をシンプルにすることができます。
マスキング関数の作成:セキュアマスキング機能の開発
pg_columnmask の挙動は、マスキング関数の品質に大きく依存します。以下の3点を満たす作り方が推奨されます。
- BEGIN ATOMIC で早期バインディング
- すべての関数呼び出しをスキーマ修飾する
- NULL・空文字・不正フォーマットを必ず検証する
テーブル/ビューのトリガー扱い:DML トリガーの動作
pg_columnmask は SELECT 時のみ マスクを適用します。そのため、DML(INSERT / UPDATE / DELETE)とトリガーを組み合わせた場合、以下のように挙動が変わります。
- テーブルトリガー
- マスク前の生データを扱うので RAISE NOTICE 等で生データが漏洩する危険性があり注意が必要
- ビュートリガー(INSTEAD OF)
- マスク後のデータを扱うので表示権限に応じたデータが渡り比較的安全
マスクされたユーザーがトリガーを実行できないようにテーブルトリガー禁止またはビュートリガー禁止の制御を行うことが必要となることもありますのでご検討下さい。
まとめ
Aurora PostgreSQL の pg_columnmask 機能は、アプリ改修なしでロールごとに動的データマスキングを実現できることが分かりました。
COUNT(*) のような単純クエリは、ほぼ性能差はありませんが、WHERE / GROUP BY / JOIN など rewrite が必要なクエリの処理では差があることが確認できました。
また、マスク対象列に対する WHERE / GROUP BY / JOIN などの使用が制約されるため(Predicates 制限)、一部クエリを実行できないケースがありますが、個人情報を含む分析基盤、外部委託オペレーション、機密データの閲覧制御が必要な SaaS / マルチテナントの環境で有効そうです。
ロール設計・安全なマスキング関数・トリガー挙動の理解が若干必要ですが、pg_columnmask 機能を利用することで柔軟にマスキングを活用できるので是非導入を検討してはいかがでしょうか?
Amazon Comprehend を使った匿名化もありますが、日本語はまだ未対応(2025/12/09 時点)なのと開発が必要なので、実運用レベルでは pg_columnmask のほうが選択しやすいですね。
近藤 諒都
(記事一覧)カスタマーサクセス部CS5課
夜行性ではありません。朝活派です。
趣味:お酒、旅行、バスケ、掃除、家庭用パン作り(ピザも)など
2025 Japan AWS All Certifications Engineers