From 83826709eeaa0a3704878e411d211909235a99dd Mon Sep 17 00:00:00 2001 From: Scott Sandre Date: Wed, 22 Feb 2023 16:15:57 -0800 Subject: [PATCH 1/3] Update delta-streaming and add image --- src/pages/latest/delta-streaming.mdx | 16 ++++++++++++---- .../delta/delta-initial-snapshot-data-drop.png | Bin 0 -> 56960 bytes 2 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 static/images/delta/delta-initial-snapshot-data-drop.png diff --git a/src/pages/latest/delta-streaming.mdx b/src/pages/latest/delta-streaming.mdx index db1cd6f..fd31ff5 100644 --- a/src/pages/latest/delta-streaming.mdx +++ b/src/pages/latest/delta-streaming.mdx @@ -3,6 +3,8 @@ title: Streaming Reads and Writes menu: docs --- +import initialSnapshotImg from '/static/images/delta/delta-initial-snapshot-data-drop.png'; + Delta Lake is deeply integrated with [Spark Structured Streaming](https://spark.apache.org/docs/latest/structured-streaming-programming-guide.html) through `readStream` and `writeStream`. Delta Lake overcomes many of the limitations typically associated with streaming systems and files, including: - Maintaining "exactly-once" processing with more than one stream (or concurrent batch jobs) @@ -83,7 +85,7 @@ If you update a `user_email` with the `UPDATE` statement, the file containing th You can use the following options to specify the starting point of the Delta Lake streaming source without processing the entire table. -- `startingVersion`: The Delta Lake version to start from. All table changes starting from this version (inclusive) will be read by the streaming source. You can obtain the commit versions from the `version` column of the [DESCRIBE HISTORY](https://delta.io/docs/spark/utilities#delta-history) command output. +- `startingVersion`: The Delta Lake version to start from. All table changes starting from this version (inclusive) will be read by the streaming source. You can obtain the commit versions from the `version` column of the [DESCRIBE HISTORY](/latest/delta-utility#delta-history) command output. - To return only the latest changes, specify `latest`. @@ -141,6 +143,12 @@ You can avoid the data drop issue by enabling the following option: With event time order enabled, the event time range of initial snapshot data is divided into time buckets. Each micro batch processes a bucket by filtering data within the time range. The `maxFilesPerTrigger` and `maxBytesPerTrigger` configuration options are still applicable to control the microbatch size but only in an approximate way due to the nature of the processing. +The graphic below shows this process: + +Initial Snapshot +
+
+ Notable information about this feature: - The data drop issue only happens when the initial Delta snapshot of a stateful streaming query is processed in the default order. @@ -273,7 +281,7 @@ The preceding example continuously updates a table that contains the aggregate n For applications with more lenient latency requirements, you can save computing resources with one-time triggers. Use these to update summary aggregation tables on a given schedule, processing only new data that has arrived since the last update. -## Idempotent table writes in `foreachBatch` +## Idempotent table writes in foreachBatch Available in Delta Lake 2.0.0 and above. @@ -290,14 +298,14 @@ Delta table uses the combination of `txnAppId` and `txnVersion` to identify dupl If a batch write is interrupted with a failure, rerunning the batch uses the same application and batch ID, which would help the runtime correctly identify duplicate writes and ignore them. Application ID (`txnAppId`) can be any user-generated unique string and does not have to be related to the stream ID. - + If you delete the streaming checkpoint and restart the query with a new checkpoint, you must provide a different `appId`; otherwise, writes from the restarted query will be ignored because it will contain the same `txnAppId` and the batch ID would start from 0. -The same `DataFrameWriter` options can be used to achieve the idempotent writes in non-Streaming job. For details [Idempotent writes](/latest/delta-batch/#idempotent-writes). +The same `DataFrameWriter` options can be used to achieve the idempotent writes in non-Streaming job. For details see [Idempotent writes](/latest/delta-batch/#idempotent-writes). diff --git a/static/images/delta/delta-initial-snapshot-data-drop.png b/static/images/delta/delta-initial-snapshot-data-drop.png new file mode 100644 index 0000000000000000000000000000000000000000..cdb0ce78075181976ad8e42cfcceeb42a167516f GIT binary patch literal 56960 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 Date: Wed, 22 Feb 2023 16:30:50 -0800 Subject: [PATCH 2/3] More minor changes --- src/pages/latest/concurrency-control.mdx | 11 +---------- src/pages/latest/porting.mdx | 2 +- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/pages/latest/concurrency-control.mdx b/src/pages/latest/concurrency-control.mdx index 3d51d35..96891ed 100644 --- a/src/pages/latest/concurrency-control.mdx +++ b/src/pages/latest/concurrency-control.mdx @@ -7,16 +7,7 @@ DeltaLake provides ACID transaction guarantees between reads and writes. This means that: - For supported [storage systems](/latest/delta-storage), multiple writers across multiple clusters can simultaneously modify a table partition and see a consistent snapshot view of the table and there will be a serial order for these writes. -- Readers continue to see a consistent snapshot view of the table that the - - Databricks job started with, even when a table is modified during a job. - -- For supported [storage systems](delta-storage.md), multiple writers across - multiple clusters can simultaneously modify a table partition and see a - consistent snapshot view of the table and there will be a serial order for - these writes. -- Readers continue to see a consistent snapshot view of the table that the - ApacheSpark job started with, even when a table is modified during a job. +- Readers continue to see a consistent snapshot view of the table that the Apache Spark job started with, even when a table is modified during a job. In this article: diff --git a/src/pages/latest/porting.mdx b/src/pages/latest/porting.mdx index c885101..539a9c3 100644 --- a/src/pages/latest/porting.mdx +++ b/src/pages/latest/porting.mdx @@ -111,7 +111,7 @@ CONVERT TO DELTA events -For details, see [Convert a Parquet table to a Delta table](delta-utility.md#convert-to-delta). +For details, see [Convert a Parquet table to a Delta table](/latest/delta-utility#convert-to-delta). ## Migrate Delta Lake workloads to newer versions From 2d9f178064d69f8c66fe563601a445a411e9cb05 Mon Sep 17 00:00:00 2001 From: Scott Sandre Date: Wed, 22 Feb 2023 17:35:31 -0800 Subject: [PATCH 3/3] add storage page --- src/pages/latest/delta-storage.mdx | 622 ++++++++++++++--------------- 1 file changed, 311 insertions(+), 311 deletions(-) diff --git a/src/pages/latest/delta-storage.mdx b/src/pages/latest/delta-storage.mdx index e067d93..83d53c9 100644 --- a/src/pages/latest/delta-storage.mdx +++ b/src/pages/latest/delta-storage.mdx @@ -116,67 +116,67 @@ to ensure that only one writer is able to create a file. This section explains how to quickly start reading and writing Delta tables on S3 using single-cluster mode. For a detailed explanation of the configuration, -see [\_](#setup-configuration-s3-multi-cluster). +see [Setup Configuration (S3 multi-cluster)](#setup-configuration-s3-multi-cluster). -#. Use the following command to launch a Spark shell with Delta Lake and S3 +1. Use the following command to launch a Spark shell with Delta Lake and S3 support (assuming you use Spark 3.2.1 which is pre-built for Hadoop 3.3.1): - + -```bash -bin/spark-shell \ - --packages io.delta:delta-core_2.12:$VERSION$,org.apache.hadoop:hadoop-aws:3.3.1 \ - --conf spark.hadoop.fs.s3a.access.key= \ - --conf spark.hadoop.fs.s3a.secret.key= -``` + ```bash + bin/spark-shell \ + --packages io.delta:delta-core_2.12:$VERSION$,org.apache.hadoop:hadoop-aws:3.3.1 \ + --conf spark.hadoop.fs.s3a.access.key= \ + --conf spark.hadoop.fs.s3a.secret.key= + ``` - + -#. Try out some basic Delta table operations on S3 (in Scala): +2. Try out some basic Delta table operations on S3 (in Scala): - + -```scala -// Create a Delta table on S3: -spark.range(5).write.format("delta").save("s3a:///") + ```scala + // Create a Delta table on S3: + spark.range(5).write.format("delta").save("s3a:///") -// Read a Delta table on S3: -spark.read.format("delta").load("s3a:///").show() -``` + // Read a Delta table on S3: + spark.read.format("delta").load("s3a:///").show() + ``` - + For other languages and more examples of Delta table operations, see the -[\_](quick-start.md) page. +[Quickstart](/latest/quick-start) page. #### Configuration (S3 single-cluster) Here are the steps to configure Delta Lake for S3. -#. Include `hadoop-aws` JAR in the classpath. +1. Include `hadoop-aws` JAR in the classpath. -Delta Lake needs the `org.apache.hadoop.fs.s3a.S3AFileSystem` class from the -`hadoop-aws` package, which implements Hadoop's `FileSystem` API for S3. Make -sure the version of this package matches the Hadoop version with which Spark was -built. + Delta Lake needs the `org.apache.hadoop.fs.s3a.S3AFileSystem` class from the + `hadoop-aws` package, which implements Hadoop's `FileSystem` API for S3. Make + sure the version of this package matches the Hadoop version with which Spark was + built. -#. Set up S3 credentials. +2. Set up S3 credentials. -We recommend using [IAM -roles](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html) for -authentication and authorization. But if you want to use keys, here is one way -is to set up the [Hadoop -configurations](https://spark.apache.org/docs/latest/configuration.html#custom-hadoophive-configuration) -(in Scala): + We recommend using [IAM + roles](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html) for + authentication and authorization. But if you want to use keys, here is one way + is to set up the [Hadoop + configurations](https://spark.apache.org/docs/latest/configuration.html#custom-hadoophive-configuration) + (in Scala): - + -```scala -sc.hadoopConfiguration.set("fs.s3a.access.key", "") -sc.hadoopConfiguration.set("fs.s3a.secret.key", "") -``` + ```scala + sc.hadoopConfiguration.set("fs.s3a.access.key", "") + sc.hadoopConfiguration.set("fs.s3a.secret.key", "") + ``` - + @@ -201,7 +201,7 @@ that S3 is lacking. #### Requirements (S3 multi-cluster) -- All of the requirements listed in [\_](#requirements-s3-single-cluster) +- All of the requirements listed in [Requirements (S3 single-cluster)](#requirements-s3-single-cluster) section - In additon to S3 credentials, you also need DynamoDB operating permissions @@ -210,118 +210,118 @@ that S3 is lacking. This section explains how to quickly start reading and writing Delta tables on S3 using multi-cluster mode. -#. Use the following command to launch a Spark shell with Delta Lake and S3 +1. Use the following command to launch a Spark shell with Delta Lake and S3 support (assuming you use Spark 3.2.1 which is pre-built for Hadoop 3.3.1): - + -```bash -bin/spark-shell \ - --packages io.delta:delta-core_2.12:$VERSION$,org.apache.hadoop:hadoop-aws:3.3.1,io.delta:delta-storage-s3-dynamodb:$VERSION$ \ - --conf spark.hadoop.fs.s3a.access.key= \ - --conf spark.hadoop.fs.s3a.secret.key= \ - --conf spark.delta.logStore.s3a.impl=io.delta.storage.S3DynamoDBLogStore \ - --conf spark.io.delta.storage.S3DynamoDBLogStore.ddb.region=us-west-2 -``` + ```bash + bin/spark-shell \ + --packages io.delta:delta-core_2.12:$VERSION$,org.apache.hadoop:hadoop-aws:3.3.1,io.delta:delta-storage-s3-dynamodb:$VERSION$ \ + --conf spark.hadoop.fs.s3a.access.key= \ + --conf spark.hadoop.fs.s3a.secret.key= \ + --conf spark.delta.logStore.s3a.impl=io.delta.storage.S3DynamoDBLogStore \ + --conf spark.io.delta.storage.S3DynamoDBLogStore.ddb.region=us-west-2 + ``` - + -#. Try out some basic Delta table operations on S3 (in Scala): +2. Try out some basic Delta table operations on S3 (in Scala): - + -```scala -// Create a Delta table on S3: -spark.range(5).write.format("delta").save("s3a:///") + ```scala + // Create a Delta table on S3: + spark.range(5).write.format("delta").save("s3a:///") -// Read a Delta table on S3: -spark.read.format("delta").load("s3a:///").show() -``` + // Read a Delta table on S3: + spark.read.format("delta").load("s3a:///").show() + ``` - + #### Setup Configuration (S3 multi-cluster) -#. Create the DynamoDB table. +1. Create the DynamoDB table. -You have the choice of creating the DynamoDB table yourself (recommended) or -having it created for you automatically. + You have the choice of creating the DynamoDB table yourself (recommended) or + having it created for you automatically. -- Creating the DynamoDB table yourself + - Creating the DynamoDB table yourself - This DynamoDB table will maintain commit metadata for multiple Delta tables, - and it is important that it is configured with the [Read/Write Capacity - Mode](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadWriteCapacityMode.html) - (for example, on-demand or provisioned) that is right for your use cases. As - such, we strongly recommend that you create your DynamoDB table yourself. The - following example uses the AWS CLI. To learn more, see the - [create-table](https://docs.aws.amazon.com/cli/latest/reference/dynamodb/create-table.html) - command reference. + This DynamoDB table will maintain commit metadata for multiple Delta tables, + and it is important that it is configured with the [Read/Write Capacity + Mode](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadWriteCapacityMode.html) + (for example, on-demand or provisioned) that is right for your use cases. As + such, we strongly recommend that you create your DynamoDB table yourself. The + following example uses the AWS CLI. To learn more, see the + [create-table](https://docs.aws.amazon.com/cli/latest/reference/dynamodb/create-table.html) + command reference. - + -```bash -aws dynamodb create-table \ - --region us-east-1 \ - --table-name delta_log \ - --attribute-definitions AttributeName=tablePath,AttributeType=S \ - AttributeName=fileName,AttributeType=S \ - --key-schema AttributeName=tablePath,KeyType=HASH \ - AttributeName=fileName,KeyType=RANGE \ - --billing-mode PAY_PER_REQUEST -``` + ```bash + aws dynamodb create-table \ + --region us-east-1 \ + --table-name delta_log \ + --attribute-definitions AttributeName=tablePath,AttributeType=S \ + AttributeName=fileName,AttributeType=S \ + --key-schema AttributeName=tablePath,KeyType=HASH \ + AttributeName=fileName,KeyType=RANGE \ + --billing-mode PAY_PER_REQUEST + ``` - + - - once you select a `table-name` and `region`, you will have to specify them in - each Spark session in order for this multi-cluster mode to work correctly. See - table below. - + + once you select a `table-name` and `region`, you will have to specify them in + each Spark session in order for this multi-cluster mode to work correctly. See + table below. + -- Automatic DynamoDB table creation + - Automatic DynamoDB table creation - Nonetheless, after specifying this `LogStore `implementation, if the default - DynamoDB table does not already exist, then it will be created for you - automatically. This default table supports 5 strongly consistent reads and 5 - writes per second. You may change these default values using the - table-creation-only configurations keys detailed in the table below. + Nonetheless, after specifying this `LogStore `implementation, if the default + DynamoDB table does not already exist, then it will be created for you + automatically. This default table supports 5 strongly consistent reads and 5 + writes per second. You may change these default values using the + table-creation-only configurations keys detailed in the table below. -#. Follow the configuration steps listed in -[\_](#configuration-s3-single-cluster) section. +2. Follow the configuration steps listed in +[Configuration (S3 single-cluster)](#configuration-s3-single-cluster) section. -#. Include the `delta-storage-s3-dynamodb` JAR in the classpath. +3. Include the `delta-storage-s3-dynamodb` JAR in the classpath. -#. Configure the `LogStore` implementation in your Spark session. +4. Configure the `LogStore` implementation in your Spark session. -First, configure this `LogStore` implementation for the scheme `s3`. You can -replicate this command for schemes `s3a` and `s3n` as well. + First, configure this `LogStore` implementation for the scheme `s3`. You can + replicate this command for schemes `s3a` and `s3n` as well. - + -```ini -spark.delta.logStore.s3.impl=io.delta.storage.S3DynamoDBLogStore -``` + ```ini + spark.delta.logStore.s3.impl=io.delta.storage.S3DynamoDBLogStore + ``` - + -Next, specify additional information necessary to instantiate the DynamoDB -client. You must instantiate the DynamoDB client with the same `tableName` and -`region` each Spark session for this multi-cluster mode to work correctly. A -list of per-session configurations and their defaults is given below: + Next, specify additional information necessary to instantiate the DynamoDB + client. You must instantiate the DynamoDB client with the same `tableName` and + `region` each Spark session for this multi-cluster mode to work correctly. A + list of per-session configurations and their defaults is given below: -| Configuration Key | Description | Default | -| ------------------------------------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | -| spark.io.delta.storage.S3DynamoDBLogStore.ddb.tableName | The name of the DynamoDB table to use | delta_log | -| spark.io.delta.storage.S3DynamoDBLogStore.ddb.region | The region to be used by the client | us-east-1 | -| spark.io.delta.storage.S3DynamoDBLogStore.credentials.provider | The AWSCredentialsProvider\* used by the client | [DefaultAWSCredentialsProviderChain](https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/auth/DefaultAWSCredentialsProviderChain.html) | -| spark.io.delta.storage.S3DynamoDBLogStore.provisionedThroughput.rcu | (Table-creation-only\*\*) Read Capacity Units | 5 | -| spark.io.delta.storage.S3DynamoDBLogStore.provisionedThroughput.wcu | (Table-creation-only\*\*) Write Capacity Units | 5 | + | Configuration Key | Description | Default | + | ------------------------------------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | + | spark.io.delta.storage.S3DynamoDBLogStore.ddb.tableName | The name of the DynamoDB table to use | delta_log | + | spark.io.delta.storage.S3DynamoDBLogStore.ddb.region | The region to be used by the client | us-east-1 | + | spark.io.delta.storage.S3DynamoDBLogStore.credentials.provider | The AWSCredentialsProvider\* used by the client | [DefaultAWSCredentialsProviderChain](https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/auth/DefaultAWSCredentialsProviderChain.html) | + | spark.io.delta.storage.S3DynamoDBLogStore.provisionedThroughput.rcu | (Table-creation-only\*\*) Read Capacity Units | 5 | + | spark.io.delta.storage.S3DynamoDBLogStore.provisionedThroughput.wcu | (Table-creation-only\*\*) Write Capacity Units | 5 | -- \*For more details on AWS credential providers, see the [AWS - documentation](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html). -- \*\*These configurations are only used when the given DynamoDB table doesn't - already exist and needs to be automatically created. + - \*For more details on AWS credential providers, see the [AWS + documentation](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html). + - \*\*These configurations are only used when the given DynamoDB table doesn't + already exist and needs to be automatically created. #### Production Configuration (S3 multi-cluster) @@ -329,104 +329,104 @@ By this point, this multi-cluster setup is fully operational. However, there is extra configuration you may do to improve performance and optimize storage when running in production. -#. Adjust your Read and Write Capacity Mode. - -If you are using the default DynamoDB table created for you by this `LogStore` -implementation, its default RCU and WCU might not be enough for your workloads. -You can [adjust the provisioned -throughput](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ProvisionedThroughput.html#ProvisionedThroughput.CapacityUnits.Modifying) -or [update to On-Demand -Mode](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithTables.Basics.html#WorkingWithTables.Basics.UpdateTable). - -#. Cleanup old DynamoDB entries using Time to Live (TTL). - -Once a DynamoDB metadata entry is marked as complete, and after sufficient time -such that we can now rely on S3 alone to prevent accidental overwrites on its -corresponding Delta file, it is safe to delete that entry from DynamoDB. The -cheapest way to do this is using [DynamoDB's -TTL](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html) -feature which is a free, automated means to delete items from your DynamoDB -table. - -Run the following command on your given DynamoDB table to enable TTL: - -```bash aws dynamodb update-time-to-live \ --region us-east-1 \ - --table-name delta_log \ --time-to-live-specification "Enabled=true, - AttributeName=commitTime" -``` - -#. Cleanup old AWS S3 temp files using S3 Lifecycle Expiration. - -In this `LogStore` implementation, a temp file is created containing a copy of -the metadata to be committed into the Delta log. Once that commit to the Delta -log is complete, and after the corresponding DynamoDB entry has been removed, it -is safe to delete this temp file. In practice, only the latest temp file will -ever be used during recovery of a failed commit. - -Here are two simple options for deleting these temp files. - -#. Delete manually using S3 CLI. - -This is the safest option. The following command will delete all but the latest temp file in your given `` and ``: - -```bash -aws s3 ls s3:////_delta_log/.tmp/ --recursive | awk 'NF>1{print $4}' | grep . | sort | head -n -1 | while read -r line ; do - echo "Removing ${line}" - aws s3 rm s3:////_delta_log/.tmp/${line} -done -``` - -#. Delete using an S3 Lifecycle Expiration Rule - -A more automated option is to use an [S3 Lifecycle Expiration rule](https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lifecycle-mgmt.html), with filter prefix pointing to the `/_delta_log/.tmp/` folder located in your table path, and an expiration value of 30 days. - - - It is important that you choose a sufficiently large expiration value. As - stated above, the latest temp file will be used during recovery of a failed - commit. If this temp file is deleted, then your DynamoDB table and S3 - `delta_table_path/_delta_log/.tmp/` folder will be out of sync. - - -There are a variety of ways to configuring a bucket lifecycle configuration, -described in AWS docs -[here](https://docs.aws.amazon.com/AmazonS3/latest/userguide/how-to-set-lifecycle-configuration-intro.html). - -One way to do this is using S3's `put-bucket-lifecycle-configuration` command. -See [S3 Lifecycle -Configuration](https://docs.aws.amazon.com/cli/latest/reference/s3api/put-bucket-lifecycle-configuration.html) -for details. An example rule and command invocation is given below: - - - -```json -// file://lifecycle.json -{ - "Rules": [ - { - "ID": "expire_tmp_files", - "Filter": { - "Prefix": "path/to/table/_delta_log/.tmp/" - }, - "Status": "Enabled", - "Expiration": { - "Days": 30 - } - } - ] -} -``` - - - -```bash aws s3api put-bucket-lifecycle-configuration \ --bucket my-bucket -\ --lifecycle-configuration file://lifecycle.json -``` - - - AWS S3 may have a limit on the number of rules per bucket. See - [PutBucketLifecycleConfiguration](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketLifecycleConfiguration.html) - for details. - +1. Adjust your Read and Write Capacity Mode. + + If you are using the default DynamoDB table created for you by this `LogStore` + implementation, its default RCU and WCU might not be enough for your workloads. + You can [adjust the provisioned + throughput](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ProvisionedThroughput.html#ProvisionedThroughput.CapacityUnits.Modifying) + or [update to On-Demand + Mode](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithTables.Basics.html#WorkingWithTables.Basics.UpdateTable). + +2. Cleanup old DynamoDB entries using Time to Live (TTL). + + Once a DynamoDB metadata entry is marked as complete, and after sufficient time + such that we can now rely on S3 alone to prevent accidental overwrites on its + corresponding Delta file, it is safe to delete that entry from DynamoDB. The + cheapest way to do this is using [DynamoDB's + TTL](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html) + feature which is a free, automated means to delete items from your DynamoDB + table. + + Run the following command on your given DynamoDB table to enable TTL: + + ```bash aws dynamodb update-time-to-live \ --region us-east-1 \ + --table-name delta_log \ --time-to-live-specification "Enabled=true, + AttributeName=commitTime" + ``` + +3. Cleanup old AWS S3 temp files using S3 Lifecycle Expiration. + + In this `LogStore` implementation, a temp file is created containing a copy of + the metadata to be committed into the Delta log. Once that commit to the Delta + log is complete, and after the corresponding DynamoDB entry has been removed, it + is safe to delete this temp file. In practice, only the latest temp file will + ever be used during recovery of a failed commit. + + Here are two simple options for deleting these temp files. + + 1. Delete manually using S3 CLI. + + This is the safest option. The following command will delete all but the latest temp file in your given `` and `
`: + + ```bash + aws s3 ls s3:////_delta_log/.tmp/ --recursive | awk 'NF>1{print $4}' | grep . | sort | head -n -1 | while read -r line ; do + echo "Removing ${line}" + aws s3 rm s3:////_delta_log/.tmp/${line} + done + ``` + + 2. Delete using an S3 Lifecycle Expiration Rule + + A more automated option is to use an [S3 Lifecycle Expiration rule](https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lifecycle-mgmt.html), with filter prefix pointing to the `/_delta_log/.tmp/` folder located in your table path, and an expiration value of 30 days. + + + It is important that you choose a sufficiently large expiration value. As + stated above, the latest temp file will be used during recovery of a failed + commit. If this temp file is deleted, then your DynamoDB table and S3 + `delta_table_path/_delta_log/.tmp/` folder will be out of sync. + + + There are a variety of ways to configuring a bucket lifecycle configuration, + described in AWS docs + [here](https://docs.aws.amazon.com/AmazonS3/latest/userguide/how-to-set-lifecycle-configuration-intro.html). + + One way to do this is using S3's `put-bucket-lifecycle-configuration` command. + See [S3 Lifecycle + Configuration](https://docs.aws.amazon.com/cli/latest/reference/s3api/put-bucket-lifecycle-configuration.html) + for details. An example rule and command invocation is given below: + + + + ```json + // file://lifecycle.json + { + "Rules": [ + { + "ID": "expire_tmp_files", + "Filter": { + "Prefix": "path/to/table/_delta_log/.tmp/" + }, + "Status": "Enabled", + "Expiration": { + "Days": 30 + } + } + ] + } + ``` + + + + ```bash aws s3api put-bucket-lifecycle-configuration \ --bucket my-bucket + \ --lifecycle-configuration file://lifecycle.json + ``` + + + AWS S3 may have a limit on the number of rules per bucket. See + [PutBucketLifecycleConfiguration](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketLifecycleConfiguration.html) + for details. + ## Microsoft Azure storage @@ -473,37 +473,37 @@ along with Apache Spark 3.0 compiled and deployed with Hadoop 3.2. Here are the steps to configure Delta Lake on Azure Blob storage. -#. Include `hadoop-azure` JAR in the classpath. See the requirements above for +1. Include `hadoop-azure` JAR in the classpath. See the requirements above for version details. -#. Set up credentials. +2. Set up credentials. -You can set up your credentials in the [Spark configuration -property](https://spark.apache.org/docs/latest/configuration.html). + You can set up your credentials in the [Spark configuration + property](https://spark.apache.org/docs/latest/configuration.html). -We recommend that you use a SAS token. In Scala, you can use the following: + We recommend that you use a SAS token. In Scala, you can use the following: - + -```scala -spark.conf.set( - "fs.azure.sas...blob.core.windows.net", - "") -``` + ```scala + spark.conf.set( + "fs.azure.sas...blob.core.windows.net", + "") + ``` - + -Or you can specify an account access key: + Or you can specify an account access key: - + -```scala -spark.conf.set( - "fs.azure.account.key..blob.core.windows.net", - "") -``` + ```scala + spark.conf.set( + "fs.azure.account.key..blob.core.windows.net", + "") + ``` - + #### Usage (Azure Blob storage) @@ -541,25 +541,25 @@ along with Apache Spark 3.0 compiled and deployed with Hadoop 3.2. Here are the steps to configure Delta Lake on Azure Data Lake Storage Gen1. -#. Include `hadoop-azure-datalake` JAR in the classpath. See the requirements +1. Include `hadoop-azure-datalake` JAR in the classpath. See the requirements above for version details. -#. Set up Azure Data Lake Storage Gen1 credentials. +2. Set up Azure Data Lake Storage Gen1 credentials. -You can set the following [Hadoop -configurations](https://spark.apache.org/docs/latest/configuration.html#custom-hadoophive-configuration) -with your credentials (in Scala): + You can set the following [Hadoop + configurations](https://spark.apache.org/docs/latest/configuration.html#custom-hadoophive-configuration) + with your credentials (in Scala): - + -```scala -spark.conf.set("dfs.adls.oauth2.access.token.provider.type", "ClientCredential") -spark.conf.set("dfs.adls.oauth2.client.id", "") -spark.conf.set("dfs.adls.oauth2.credential", "") -spark.conf.set("dfs.adls.oauth2.refresh.url", "https://login.microsoftonline.com//oauth2/token") -``` + ```scala + spark.conf.set("dfs.adls.oauth2.access.token.provider.type", "ClientCredential") + spark.conf.set("dfs.adls.oauth2.client.id", "") + spark.conf.set("dfs.adls.oauth2.credential", "") + spark.conf.set("dfs.adls.oauth2.refresh.url", "https://login.microsoftonline.com//oauth2/token") + ``` - + #### Usage (ADLS Gen1) @@ -600,40 +600,40 @@ along with Apache Spark 3.0 compiled and deployed with Hadoop 3.2. Here are the steps to configure Delta Lake on Azure Data Lake Storage Gen1. -#. Include the JAR of the Maven artifact `hadoop-azure-datalake` in the +1. Include the JAR of the Maven artifact `hadoop-azure-datalake` in the classpath. See the [requirements](#azure-blob-storage) for version details. In addition, you may also have to include JARs for Maven artifacts `hadoop-azure` and `wildfly-openssl`. -#. Set up Azure Data Lake Storage Gen2 credentials. +2. Set up Azure Data Lake Storage Gen2 credentials. - + -```scala -spark.conf.set("fs.azure.account.auth.type..dfs.core.windows.net", "OAuth") -spark.conf.set("fs.azure.account.oauth.provider.type..dfs.core.windows.net", "org.apache.hadoop.fs.azurebfs.oauth2.ClientCredsTokenProvider") -spark.conf.set("fs.azure.account.oauth2.client.id..dfs.core.windows.net", "") -spark.conf.set("fs.azure.account.oauth2.client.secret..dfs.core.windows.net","") -spark.conf.set("fs.azure.account.oauth2.client.endpoint..dfs.core.windows.net", "https://login.microsoftonline.com//oauth2/token") -``` + ```scala + spark.conf.set("fs.azure.account.auth.type..dfs.core.windows.net", "OAuth") + spark.conf.set("fs.azure.account.oauth.provider.type..dfs.core.windows.net", "org.apache.hadoop.fs.azurebfs.oauth2.ClientCredsTokenProvider") + spark.conf.set("fs.azure.account.oauth2.client.id..dfs.core.windows.net", "") + spark.conf.set("fs.azure.account.oauth2.client.secret..dfs.core.windows.net","") + spark.conf.set("fs.azure.account.oauth2.client.endpoint..dfs.core.windows.net", "https://login.microsoftonline.com//oauth2/token") + ``` - + -where ``, ``, `` and -`` are details of the service principal we set as requirements -earlier. + where ``, ``, `` and + `` are details of the service principal we set as requirements + earlier. -#. Initialize the file system if needed +3. Initialize the file system if needed - + -```scala -spark.conf.set("fs.azure.createRemoteFileSystemDuringInitialization", "true") -dbutils.fs.ls("abfss://@.dfs.core.windows.net/") -spark.conf.set("fs.azure.createRemoteFileSystemDuringInitialization", "false") -``` + ```scala + spark.conf.set("fs.azure.createRemoteFileSystemDuringInitialization", "true") + dbutils.fs.ls("abfss://@.dfs.core.windows.net/") + spark.conf.set("fs.azure.createRemoteFileSystemDuringInitialization", "false") + ``` - + #### Usage (ADLS Gen2) @@ -673,18 +673,18 @@ reading and writing from GCS. ### Configuration (GCS) -#. For Delta Lake 1.2.0 and below, you must explicitly configure the LogStore +1. For Delta Lake 1.2.0 and below, you must explicitly configure the LogStore implementation for the scheme `gs`. - + -```ini -spark.delta.logStore.gs.impl=io.delta.storage.GCSLogStore -``` + ```ini + spark.delta.logStore.gs.impl=io.delta.storage.GCSLogStore + ``` - + -#. Include the JAR for `gcs-connector` in the classpath. See the +2. Include the JAR for `gcs-connector` in the classpath. See the [documentation](https://cloud.google.com/dataproc/docs/tutorials/gcs-connector-spark-tutorial) for details on how to configure Spark with GCS. @@ -722,22 +722,22 @@ concurrently reading and writing. ### Configuration (OCI) -#. Configure LogStore implementation for the scheme `oci`. +1. Configure LogStore implementation for the scheme `oci`. - + -```ini -spark.delta.logStore.oci.impl=io.delta.storage.OracleCloudLogStore -``` + ```ini + spark.delta.logStore.oci.impl=io.delta.storage.OracleCloudLogStore + ``` - + -#. Include the JARs for `delta-contribs` and `hadoop-oci-connector` in the +2. Include the JARs for `delta-contribs` and `hadoop-oci-connector` in the classpath. See [Using the HDFS Connector with Spark](https://docs.oracle.com/en-us/iaas/Content/API/SDKDocs/hdfsconnectorspark.htm) for details on how to configure Spark with OCI. -#. Set the OCI Object Store credentials as explained in the +3. Set the OCI Object Store credentials as explained in the [documentation](https://docs.oracle.com/en-us/iaas/Content/API/SDKDocs/hdfsconnector.htm). ### Usage (OCI) @@ -775,43 +775,43 @@ concurrently reading and writing. ### Configuration (IBM) -#. Configure LogStore implementation for the scheme `cos`. +1. Configure LogStore implementation for the scheme `cos`. - + -```ini -spark.delta.logStore.cos.impl=io.delta.storage.IBMCOSLogStore -``` + ```ini + spark.delta.logStore.cos.impl=io.delta.storage.IBMCOSLogStore + ``` - + -#. Include the JARs for `delta-contribs` and `Stocator` in the classpath. +2. Include the JARs for `delta-contribs` and `Stocator` in the classpath. -#. Configure `Stocator` with atomic write support by setting the following +3. Configure `Stocator` with atomic write support by setting the following properties in the Hadoop configuration. - + -```ini fs.stocator.scheme.list=cos -fs.cos.impl=com.ibm.stocator.fs.ObjectStoreFileSystem -fs.stocator.cos.impl=com.ibm.stocator.fs.cos.COSAPIClient -fs.stocator.cos.scheme=cos fs.cos.atomic.write=true -``` + ```ini fs.stocator.scheme.list=cos + fs.cos.impl=com.ibm.stocator.fs.ObjectStoreFileSystem + fs.stocator.cos.impl=com.ibm.stocator.fs.cos.COSAPIClient + fs.stocator.cos.scheme=cos fs.cos.atomic.write=true + ``` - + -#. Set up IBM COS credentials. The example below uses access keys with a service +4. Set up IBM COS credentials. The example below uses access keys with a service named `service` (in Scala): - + -```scala -sc.hadoopConfiguration.set("fs.cos.service.endpoint", "") -sc.hadoopConfiguration.set("fs.cos.service.access.key", "") -sc.hadoopConfiguration.set("fs.cos.service.secret.key", "") -``` + ```scala + sc.hadoopConfiguration.set("fs.cos.service.endpoint", "") + sc.hadoopConfiguration.set("fs.cos.service.access.key", "") + sc.hadoopConfiguration.set("fs.cos.service.secret.key", "") + ``` - + ### Usage (IBM)