You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从Azure Databricks迁移8亿条数据至S3的性能优化求助

8亿条Azure Databricks数据迁移至AWS S3的高效方案

问题背景

需将8亿条记录从Azure Databricks迁移至AWS S3,测试阶段发现现有方案效率不足:

  • 单文件写入100万条记录耗时约60秒,但单文件模式无法适配8亿级数据的存储与操作需求;
  • 循环分块写入(拆分为5个20万条文件)耗时长达20-25分钟,且数据量增大后文件间间隔持续变长,完全无法支撑8亿条数据的迁移。

现有代码的核心问题

1. 单文件写入方案

from boto3.session import Session
from pyspark.sql import SparkSession
import requests
import boto3

file_name = 'full_data_in_one_file.csv'

s3_client = boto3.client('s3', aws_access_key_id=ACCESS_KEY, aws_secret_access_key=SECRET_KEY)
s3_client.put_object(Body=df.toPandas().to_csv(index=False), Bucket=bucket_name, Key=file_name)
  • 调用df.toPandas()会将所有Spark分布式数据拉取到Driver节点,极易触发内存溢出,且单线程写入S3完全浪费Spark集群的分布式计算能力。

2. 循环分块写入方案

from boto3.session import Session
from pyspark.sql import SparkSession
import requests
import boto3
import json
from pyspark.sql.functions import monotonically_increasing_id
from pyspark.sql import functions as F
from pyspark.sql.window import Window
import random
import time

s3_client = boto3.client('s3', aws_access_key_id=ACCESS_KEY, aws_secret_access_key=SECRET_KEY)

df = spark.read.format("json").options(header='true', inferSchema='true').option("mode","DROPMALFORMED").load("abfss://path/product.db/table_name")
total_records = df.count()
print("Total records : ",total_records)
df_with_index = df.withColumn("index", monotonically_increasing_id())

num_loop=3
data_size_per_loop=200000
start_time = time.time()
for i in range(num_loop):
    start = i * data_size_per_loop
    end = (i + 1) * data_size_per_loop
    paginated_df = df_with_index.filter((df_with_index["index"] >= start) & (df_with_index["index"] < end))
    paginated_records = paginated_df.count()
    print("paginated_records : ",paginated_records)
    paginated_data = paginated_df.filter(paginated_df.index >= start).filter(paginated_df.index < end)
    list_data = paginated_data.toJSON().collect()
    size = len(list_data)
    json_data = json.dumps(list_data)
    
    if size == 0:
            print("No more records for push into AWS.")
            break
    else:
        data_payload = '\n'.join(json_data)
        end_data_range_time = time.time() - start_time
        print("Time taken to prepare aws format json data : ", end_data_range_time, " seconds")
        savefile_name= file_name+str(i)+"_"+str(size)+"_"+str(end)+".json"
        s3_client.put_object(Body=data_payload, Bucket=bucket_name, Key=savefile_name)
        end_AWS = time.time() - end_data_range_time
        print(f"data push into AWS S3. Starting records: {start} and end: {end}, size : {size}, loop nimber : {i}, time to push into AWS : {end_AWS} seconds")
  • 每次循环过滤都会重新扫描全量DataFrame,重复计算导致耗时剧增;
  • collect()将分区数据拉取到Driver节点,随着循环次数增加,Driver内存压力持续增大;
  • 单线程调用boto3写入S3,完全未利用Spark的分布式执行能力,效率极低。

高效迁移方案

方案一:Spark原生分布式写入S3(推荐)

Spark支持直接写入S3,利用集群所有节点并行处理,是效率最高的方案。

写入CSV格式

# 配置AWS凭证(建议通过Databricks Secrets管理,避免硬编码)
spark._jsc.hadoopConfiguration().set("fs.s3a.access.key", ACCESS_KEY)
spark._jsc.hadoopConfiguration().set("fs.s3a.secret.key", SECRET_KEY)
spark._jsc.hadoopConfiguration().set("fs.s3a.endpoint", "s3.amazonaws.com") # 根据AWS区域调整

# 写入S3,自动分块
df.write \
  .mode("overwrite") \
  .option("header", "true") \
  .option("maxRecordsPerFile", 1000000) # 每个文件100万条,可按需调整
  .csv("s3a://bucket_name/path/to/output")

写入JSON格式

df.write \
  .mode("overwrite") \
  .option("maxRecordsPerFile", 1000000)
  .json("s3a://bucket_name/path/to/output")

优势

  • 完全分布式执行,集群所有节点同时写入,充分利用计算资源;
  • 自动分块,无需手动加索引和循环过滤,避免重复扫描数据;
  • 数据无需拉取到Driver节点,彻底规避内存瓶颈;
  • 支持多种格式,兼容性强。

方案二:使用Parquet格式优化存储与性能

若无需明文格式,推荐使用Parquet列式存储,压缩比高、读写速度快,更适合大数据场景:

df.write \
  .mode("overwrite") \
  .option("maxRecordsPerFile", 1000000) \
  .parquet("s3a://bucket_name/path/to/output")

方案三:基于foreachPartition的分布式boto3写入(特殊场景适配)

若因业务限制必须使用boto3,需将写入逻辑下放至Executor节点,避免Driver单点瓶颈:

from pyspark.sql import SparkSession
import boto3
import json
import uuid

def write_partition_to_s3(partition):
    # 每个分区初始化一次S3客户端,减少连接开销
    s3_client = boto3.client('s3', aws_access_key_id=ACCESS_KEY, aws_secret_access_key=SECRET_KEY)
    records = list(partition)
    if not records:
        return
    # 转换为JSON Lines格式
    json_lines = '\n'.join([json.dumps(record.asDict()) for record in records])
    # 生成唯一文件名避免冲突
    file_name = f"output_{uuid.uuid4()}.json"
    s3_client.put_object(Body=json_lines, Bucket=bucket_name, Key=file_name)

# 调整分区数,控制每个分区的记录量(示例:每100万条一个分区)
partition_count = df.count() // 1000000 + 1
df.repartition(partition_count).foreachPartition(write_partition_to_s3)

注意事项

  • 通过repartition合理设置分区数,避免分区过多或过少;
  • 每个分区内初始化一次S3客户端,不要为每条记录创建连接;
  • 生成唯一文件名,防止文件覆盖冲突。

内容的提问来源于stack exchange,提问作者user1398291

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 15:47:50