Databricksのベストプラクティス：Delta LakeによるGDPR、CCPAへの準拠

[Best practices: GDPR and CCPA compliance using Delta Lake \| Databricks on AWS](https://docs.databricks.com/security/privacy/gdpr-delta.html) [2021/6/11時点]の翻訳です。

本書では、どのようにDatabricks Delta Lakeを用いて、データレイクをGDPR(EU一般データ保護規則)やCCPA(カリフォルニア州消費者プライバシー法)に準拠させるのかを説明します。Delta Lakeは、お使いのデータレイクに構造化されたデータマネジメントを提供するトランザクションレイヤーを追加することで、顧客からのGDPR、CCPAリクエストに対応するために、迅速に個人情報(パーソナルデータ)を特定し、削除することが可能となります。

# 課題

あなたの会社では、クラウド上に数百テラバイトの個人情報を管理しているかもしれません。これらのデータセットをGDPR、CCPAに準拠させることは非常に重要ですが、特にデータレイクに格納された大規模データセットにおいては大きな課題となります。

これらの課題は多くの場合以下の要因によるものです：

- クラウドに大規模データ(ペタバイト規模)を格納する際、ユーザーデータは複数のデータセット、ロケーションにまたがるものになります。
- アドホックなクエリーは多くの場合フルテーブルスキャンが必要となり、(藁山から針を探すような)ユーザーの検索は高コストになります。GDPR/CCPAに対するブルートフォースアプローチは複数のテーブルに対する複数のジョブが実行され、結果的に数週間のエンジニアリング、オペレーション上の工数が発生し得ます。
- データレイクは基本的に追加のみの処理であり、ネイティブでは低レベルの「削除」、「更新」のオペレーションをサポートしていません。これは、データパーティションを再度書き込まなくてはならないことを意味します。典型的なデータレイクはACIDトランザクションや、効率的に適切なデータを検索する手段を提供しません。さらに、読み書きにおける一貫性も課題となります：データレイクからユーザーデータが検閲されたとしても、従来型のRDBMS同様にデータを読み込むプロセスは物理的インパクトから保護されるべきです。
- データレイクは設計上、可用性と分離許容性に関しては結果的整合性を採用しているため、データレイクにおけるデータの衛生状態は課題となります。清潔なデータを保証するためには強制可能かつ厳格なプラクティスが求められます。

このため、この規模でユーザーデータを取り扱う企業は、GDPR、CCPAに対応するために、計算リソース上、困難、効果かつ時間を浪費するデータパイプラインを構築するケースが多くあります。例えば、GDPR、CCPAコンプライアンスに対応するための削除処理を行うためにデータレイクの一部のデータを、プロプライエタリなデータウェアハウスにアップロードするかもしれません。このことは、複数のデータコピーを作成することによって、データの正確性を損ない、複雑性を増加させることになります。さらに、そのようなデータウェアハウスからデータレイクに再度データを取り込む際には、クエリー性能を改善するための再度の最適化が必要となります。これもまた、データの複製を生み出し、これを管理することになります。

# Delta Lakeはどのように課題に取り組むのか

上記の問題を解決するために、データレイクをGDPR、CCPAに準拠させる最適なアプローチには以下が必要となります。

- "[偽名化](https://en.wikipedia.org/wiki/Pseudonymization)"、あるいは外部では識別できないキー(`pseudonyms`:偽名、仮名)に対する、可逆な個人情報要素(`identifiers`:ID)のトークン化
- IDではなく偽名にリンクされたストレージ情報
- ID、偽名の組み合わせに対するポリシーの活用、厳密なアクセスの管理
- 法規制に準拠できるように、生データを即時削除できるパイプラインあるいはバケットポリシー
- 偽名とIDのリンクを破壊するためのIDの特定、削除を行う構造化パイプライン
- データレイクに対して更新削除が行われた際に、データレイクを読み取っているユーザーに対してネガティブなインパクトを引き起こさないように、データレイク上にACID機能のオーバーレイ
- 高性能パイプライン、例えば、5TBのデータを10分でクリーンアップ

Delta Lakeは、データレイクにトランザクション機能を追加する構造化されたデータ管理システムであるため、GDPR、CCPA準拠に対応するには非常に効果的なツールとなります。Delta Lakeの構造化、サイジング、インデックシング、統計情報が付加されたデータセットによって、迅速かつ容易な検索が可能になり、`DELETE`、`UPDATE`、`MERGE INTO`のような標準的なDQL DMLを用いた変更、クリーンアップが可能となります。

以下では、どのように既存のデータをDelta Lakeに変換するのか、個人情報を迅速かつ効率的に削除、クリーンアップするのかの二つのユースケースを説明します。また、本書では個人情報の偽名化とDelta Lakeにおけるクエリー性能の改善に関するオプションを説明します。

# パーソナルデータの削除

このユースケースでは、データレイクからパーソナルデータを削除する際に、Delta Lakeがどれだけ効率的かを説明します。

## サンプルデータセット

本書で説明するワークフローでは、個別の顧客IDを含む65,000,000行のサンプルデータ(3.228 GB)を含むデータベース`gdpr`を参照します。顧客の個人情報はデータベースの`customers`テーブルに格納されています。

`gdpr.customers`テーブルのスキーマは以下の通りとなっています。

```
|-- c_customer_sk: integer (nullable = true)
|-- c_customer_id: string (nullable = true)
|-- c_current_cdemo_sk: integer (nullable = true)
|-- c_current_hdemo_sk: integer (nullable = true)
|-- c_current_addr_sk: integer (nullable = true)
|-- c_first_shipto_date_sk: integer (nullable = true)
|-- c_first_sales_date_sk: integer (nullable = true)
|-- c_salutation: string (nullable = true)
|-- c_first_name: string (nullable = true)
|-- c_last_name: string (nullable = true)
|-- c_preferred_cust_flag: string (nullable = true)
|-- c_birth_day: integer (nullable = true)
|-- c_birth_month: integer (nullable = true)
|-- c_birth_year: integer (nullable = true)
|-- c_birth_country: string (nullable = true)
|-- c_email_address: string (nullable = true)
|-- c_last_review_date: string (nullable = true)
```

GDPR、CCPAに基づき削除を要求している顧客のリストは、データベースのテーブル`gdpr.customer_delete_keys`に格納されています。これらはオンラインポータルから入力されたものです。削除対称のキー(個別のユーザー)は、元の`gdpr.customers`の約10%(337.615 MB)となっています。

`gdpr.customer_delete_keys`のスキーマは以下の通りとなっています。

```
|-- c_customer_sk: integer (nullable = true)
|-- c_customer_id: string (nullable = true)
```

キー`c_customer_id`は削除対象の顧客を特定します。

## ステップ1: テーブルをDeltaフォーマットに変換

Delta Lakeを利用し始めるには、生データ(Parquet、CSV、JSONなど)を取り込み、マネージドDeltaテーブルとして書き出す必要があります。すでにデータがParquetフォーマットである場合には、再度書き込みを行わずに、Parquetファイルをその場でDeltaテーブルに変換する`CONVERT TO DELTA`を使用することができます。そうでない場合には、馴染み深いApache SparkのAPIを用いて[Deltaフォーマットとして書き出す](https://qiita.com/taka_yayoi/items/345f503d5f8177084f24#%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%81%AE%E4%BD%9C%E6%88%90)ことができます。Delta LakeはオープンフォーマットのParquetを用いているので、変換したデータはロックインされません。必要に応じて他のフォーマットに容易に戻すことができます。

以下の例では`gdpr`データベースのParquetテーブル`customers`を変換します。

```sql:SQL
CONVERT TO DELTA gdpr.customers
```

## ステップ2: deleteの実行

テーブルをDelta Lakeに変換した後は、削除を要求したユーザーのパーソナルデータを削除することができます。

> **注意**
以下の例では、`customer`テーブルからパーソナルデータを削除するシンプルな例が含まれています。より優れたプラクティスは、作業用テーブルの個人情報を(データ削除リクエストを受け付ける前に)偽名化し、顧客を偽名にマップする「ルックアップテーブル」から顧客のエントリーを削除するというものです。これにより、作業用テーブルから顧客IDを再構成できないようにすることができます。詳細は、[データの偽名化](#データの偽名化)を参照ください。
   
> **注意**
以下の例では、特定のパフォーマンスオプションのインパクトを示すためにパフォーマンスの数値を参照しています。これらの数値は、上記のデータセットで、それぞれが90GBのメモリー、12コア3台のワーカーノードと、30GBメモリー、4コアのドライバーノードで記録されたものです。

こちらがDelta Lakeのシンプルな`DELETE FROM`オペレーションであり、`customer_delete_keys`に含まれる顧客を`gdpr.customers`テーブルから削除します。

```sql:SQL
DELETE FROM `gdpr.customers` AS t1 WHERE EXISTS (SELECT c_customer_id FROM gdpr.customer_delete_keys WHERE t1.c_customer_id = c_customer_id)
```

テストの際、このオペレーションは完了までに長い時間がかかりました。ファイルの検索に32秒、ファイルの書き込みに2.6分かかりました。適切なファイルを検索する時間を短縮するために、ブロードキャストの閾値を増やすことができます。

```sql:SQL
set spark.sql.autoBroadcastJoinThreshold = 104857600;
```

このブロードキャストのヒントはSparkに対して、他のテーブルやビューとjoinする際に、指定されたそれぞれテーブルをブロードキャストすることを指示します。この設定によって、ファイルの検索は8秒、書き込みには1.6分に短縮されました。

Delta Lakeの[Z\-Ordering \(多次元クラスタリング\)](https://docs.databricks.com/delta/optimizations/file-mgmt.html#z-ordering-multi-dimensional-clustering)を用いることで、さらにパフォーマンスを改善することができます。Z-Orderingは、データのレンジパーティションベースの再配置を行い、この情報をDeltaテーブルにインデックスとして格納します。Delta Lakeは、`DELETE`オペレーションの対応となるフィルを検索する際に、このzインデックスを使用します。

Z-Orderingを活用するためには、削除したいデータがどのようにターゲットのテーブルに分布しているのかを理解する必要があります。例えば、少量のキーであっても、データがデータセットのファイルの90%に分散しているのであれば、90%以上にデータ対して再度書き込みを行う必要があります。適切なキーカラムによるZ-Orderingは、操作するファイルの数を削減し、再書き込みを効率的なものにします。

このケースでは、削除処理を行う前に`c_customer_id`でZ-Orderを実行すべきです。

```sql:SQL
OPTIMIZE gdpr.customers Z-ORDER BY c_customer_id
```

Z-Orderingを行うことで、ファイル検索は7秒、書き込みは50秒に削減されました。

## ステップ3: 古いデータのクリーンアップ

顧客のリクエストを受けてからどのくらいの期間でデータを削除するのか、使用しているデータレイクによって、テーブルの履歴と生データを削除する必要があるかもしれません。

デフォルトでは、Delta Lakeは30日分のテーブル履歴を保持しており、これは[タイムトラベル](https://docs.databricks.com/delta/delta-batch.html#query-an-older-snapshot-of-a-table-time-travel)とロールバックで利用することができます。これはすなわち、Deltaテーブルから個人情報を削除したとしても、企業内のユーザーは履歴データを参照し、個人情報が格納された状態にロールバックできるということを意味します。デフォルトの保持期間が切れる前にGDPR、CCPAコンプライアンスに対応してこれらの古い情報を検索できないようにするには、Deltaテーブルで参照できないように、指定された保持期間より古いファイルを削除する[VACUUM](https://docs.databricks.com/delta/delta-utility.html#delta-vacuum)関数を使用することができます。VACUUMコマンドを用いてテーブル履歴を削除した後は、全てのユーザーは履歴参照及びロールバックができなくなります。

個人情報の削除を依頼したすべての顧客を削除し、7日より前のテーブル履歴を削除するには、以下を実行します。

```sql:SQL
VACUUM gdpr.customers
```

7日より新しいデータを削除するには、`RETAIN num HOURS`オプションを使用します。

```sql:SQL
VACUUM gdpr.customers RETAIN 100 HOURS
```

また、Spark APIを用いて[非ParquetファイルをDeltaに書き込んで](https://qiita.com/taka_yayoi/items/345f503d5f8177084f24#%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%81%AE%E4%BD%9C%E6%88%90)Deltaテーブルを作成した場合には、生データには削除、偽名化した個人情報が含まれている場合があります。クラウドプロバイダーの保持ポリシーを設定して、30日あるいはそれ以下の期間が経過した生データのファイルを自動的に削除することをお勧めします。

# データの偽名化

上で述べた削除方法を用いることで、あなたの企業が厳密にGDPR、CCPAの要件に準拠できるように、個人情報を削除することができますが、多くの問題をもたらすことにもなります。まず初めに、GDPRは適切な削除リクエストを受け付けた後の追加の処理を許可しません。結果として、データが*偽名化*された状態(データ削除申請受付前に個人情報を人工的なIDや偽名で置換)で格納されない場合には、リンクされたすべての情報を削除する必要があります。しかし、事前にデータを偽名化しておけば、偽名の識別子にリンクするレコードをシンプルに削除するだけで済み(残りのデータはユーザーを識別できないものとします)、残りのデータを維持することができます。

典型的な偽名化のシナリオにおいては、顧客のユーザーID(氏名、メールアドレスなど)と偽名をマッピングするセキュアな「ルックアップテーブル」を保持します。これによって、削除が容易になるだけではなく、ユーザーデータを更新するためにユーザーIDを一時的に「復元」することができますが、顧客IDが復元できず、すべての顧客データが静的かつ過去のデータとなる、*匿名化*のシナリオでは活用することができません。

シンプルな偽名化の例では、削除の例で更新された顧客テーブルを考えてみます。偽名化のシナリオにおいては、顧客を識別するための全ての顧客データと、偽名化されたメールアドレスのカラムが追加された`gdpr.customers_lookup`を作成できます。顧客を参照する全てのデータテーブルのキーとして擬似的なメールアドレスを用い、情報削除の依頼があった際には、`gdpr.customers_lookup`テーブルからデータを削除するだけで済み、残りの情報は未来永劫、顧客が識別できない状態となります。

`gdpr.customers_lookup`テーブルのスキーマは以下の通りとなります。

```
|-- c_customer_id: string (nullable = true)
|-- c_email_address: string (nullable = true)
|-- c_email_address_pseudonym: string (nullable = true)
|-- c_first_name: string (nullable = true)
|-- c_last_name: string (nullable = true)
```

![](https://docs.databricks.com/_images/gdpr-lookup.png)

このシナリオでは、顧客を識別することができない`gdpr.customers_pseudo`と呼ばれる偽名化されたテーブルで、残りの情報を保持します。

```
|-- c_email_address_pseudonym: string (nullable = true)
|-- c_customer_sk: integer (nullable = true)
|-- c_current_cdemo_sk: integer (nullable = true)
|-- c_current_hdemo_sk: integer (nullable = true)
|-- c_current_addr_sk: integer (nullable = true)
|-- c_first_shipto_date_sk: integer (nullable = true)
|-- c_first_sales_date_sk: integer (nullable = true)
|-- c_salutation: string (nullable = true)
|-- c_preferred_cust_flag: string (nullable = true)
|-- c_birth_year: integer (nullable = true)
|-- c_birth_country: string (nullable = true)
|-- c_last_review_date: string (nullable = true)
```

![](https://docs.databricks.com/_images/gdpr-pseudo.png)

## 顧客データを偽名化するためにDelta Lakeを用いる

顧客情報を偽名化する強力な方法は、一方向の暗号化ハッシュと、単一のソルト、複数のソルトによるソルティングがあります。ハッシングではデータを非可逆の固定長のフィンガープリントに変換します。ソルティングでは、ハッシュ化されるデータに対して、検索あるいは既知のメールアドレス、パスワードを格納する「レインボー」テーブルを用いて攻撃を行う攻撃者を苛立たせる方法でランダム文字列を追加します。

ハッシングの前にランダムなシークレット文字列リテラルを追加することで、`c_email_address`カラムにソルトを追加します。ソルトに対するセキュリティを強化するために、シークレット文字列をDatabricksの[シークレット](https://qiita.com/taka_yayoi/items/338ef0c5394fe4eb87c0)に格納することができます。認証されないDatabricksユーザーがシークレットにアクセスしようとした場合には、検閲された値が表示されます。

```scala:Scala
dbutils.secrets.get(scope = "salt", key = "useremail")
```

```
res0: String = [REDACTED]
```

> **注意**
このシンプルな例はソルティングをデモするためのものです。全ての顧客キーに対して同じソルトを用いることは、攻撃を防ぐための良い方法とは言えません。これはたんに顧客キーを長いものにするだけです。よちセキュアなアプローチはユーザーごとにランダムなソルトを生成するというものです。詳細は[偽名化を強化する](#偽名化を強化する)を参照ください。

これで、この値を顧客キーが付与されたテーブルに使用することができます。

## 偽名化を強化する

データベースにおける単一のソルトが突破されるリスクを低減するために、(顧客ごと、ユーザーごとの)複数の異なるソルトを利用することをお勧めします。偽名化されたIDに結び付けられたデータ自身には個人を識別できる情報が含まれていないため、どのユーザーに関連づけられたソルトのレコードを削除した場合には、残りのデータは完全に匿名化されたものとなり、GDPR、CCPAのスコープ外となります。多くの企業はユーザーに対して複数のソルトを作成し、ビジネスニーズに応じてこれらのソルトをローテートさせることで、データ保護法のスコープ外となる完全に匿名化されたデータを作成することを選択しています。

そして、データが「パーソナル」か「識別可能」なのかは、要素レベルの分析ではなく基本的には配列レベルの分析であることを忘れないようにしてください。メールアドレスが個人情報であることは明確なことですが、それぞれが個人情報でなくても組み合わせが個人情報になる場合があります。例えば、[https://aboutmyinfo\.org/identity/about](https://aboutmyinfo.org/identity/about)を参照してください。1990年のUSの国勢調査の分析に基づくと、US人口の87%は郵便番号、生年月日、性別の組み合わせで特定できるとのことです。個人情報テーブルの一部として何を格納すべきかを決定する際、あるいは、偽名化されたテーブルで作業する際は、個人識別ができないように見える情報の組み合わせで個人が識別ができないかどうかを検討するようにしてください。そして、特定できないものと意図した情報を用いて、再度個人を識別しようとする試み(例えば、差別的なプライバシー、プライバシーを保持するヒストグラムなど)を防ぐ内部プロセスを含むご自身のプライバシーコンプライアンスがあることを確認してください。再識別を完全に防ぐことは不可能かもしれませんが、これらのステップを踏むことで一助になるかと思います。

# クエリー性能の改善

[ステップ2: deleteの実行](#ステップ2-deleteの実行)では、ブロードキャストの閾値の増加とZ-Orderingによって、どのようにDelta Lakeのクエリー性能を改善できるのかを示しましたが、これ以外にも注意すべきパフォーマンス改善のプラクティスが存在します。

- キーとなるカラムがテーブルの最初の32列に含まれることを確認してください。Delta Lakeは最初の32列から統計情報を収集し、これらの統計情報は削除、更新時にファイルを特定する際に役立ちます。
- DatabricksのDelta Lakeで使用できるAuto Optimize機能を使ってください。これによって、Deltaテーブルに対するそれぞれの書き込みの際に小さなファイルをコンパクトにし、特に大量の小さなファイルに遭遇するようなケースでは、頻繁に検索されるテーブルで大きなメリットが生じます。いつこの機能を使うかに関しては、[Auto Optimize](https://docs.databricks.com/delta/optimizations/auto-optimize.html)を参照ください。
- `BroadcastHashJoin`におけるソーステーブルのサイズを削減してください。これによって、削除すべき適切なデータを特定する際に、Delta Lakeは[dynamic file pruning](https://docs.databricks.com/release-notes/runtime/6.1.html#dynamic-file-pruning-dfp-enabled-by-default)を活用できます。これは特に、パーティションの境界を跨がないdeleteオペレーションを改善します。

    > **参考記事**
[動的ファイルプルーニングによるDelta LakeのSQLクエリーの高速化 \- Qiita](https://qiita.com/taka_yayoi/items/af8415bc7309c44b0855)

- `DELETE`のような、あらゆる更新オペレーションにおいては、検索句に全ての条件を記載し、可能な限り対象を絞り込むようにしてください。これによって、対象のファイル数を削減し、トランザクションの競合を防ぐことができます。
- Sparkのシャッフル、クラスターの利用率、ストレージシステムが最適な書き込み性能を達成できるように、継続的にチューニングしてください。

# より詳細を知るには

DatabricksのDelta Lakeに関しては、[Delta Lake and Delta Engine guide](https://docs.databricks.com/delta/index.html)を参照ください。

DatabricksエキスパートによるGDPR、CCPAコンプライアンスに関するDelta Lakeの記事は、以下を参照ください。

- [How to Avoid Drowning in GDPR Data Subject Requests in a Data Lake](https://databricks.com/blog/2018/05/01/how-to-avoid-drowning-in-dsrs-in-a-data-lake.html)
- [Make Your Data Lake CCPA Compliant with a Unified Approach to Data and Analytics](https://databricks.com/blog/2019/12/18/make-your-data-lake-ccpa-compliant.html)
- [Efficient Upserts into Data Lakes with Databricks Delta](https://databricks.com/blog/2019/03/19/efficient-upserts-into-data-lakes-databricks-delta.html)

Databricksワークスペースにおける個人情報の削除に関しては、[Manage workspace storage](https://docs.databricks.com/administration-guide/workspace/storage.html)を参照ください。

### Databricks 無料トライアル

[Databricks 無料トライアル](https://databricks.com/jp/try-databricks)