重要
Azure Data Lake Analytics 于 2024 年 2 月 29 日停用。 了解更多信息,请查看此公告。
对于数据分析,你的组织可以使用 Azure Synapse Analytics 或 Microsoft Fabric。
什么是数据倾斜?
简要说明,数据倾斜是一个过度表示的值。 假设你已经分配了 50 名税务检查员来审核纳税申报表,每个州都有一名考官。 怀俄明州考官,因为人口很小,没有什么可做。 然而,在加州,由于该州人口众多,审查员一直忙着。
在我们的方案中,数据在所有税务检查员中分布不均匀,这意味着某些考官必须比其他检查员工作更多。 在你自己的工作中,你经常遇到类似于税务检查员示例的情况。 在更专业的术语中,一个顶点获得的数据远远多于其他同级顶点,这种情况导致该顶点比其他顶点工作更多,最终会减慢整个作业的进度。 更糟的是,作业可能会失败,因为顶点可能会受到例如 5 小时的运行时间限制和 6 GB 内存限制的影响。
解决数据倾斜问题
适用于 Visual Studio 和 Visual Studio Code 的 Azure Data Lake 工具可以帮助检测作业是否存在数据倾斜问题。
如果存在问题,可以通过尝试本部分中的解决方案来解决该问题。
解决方案 1:改进表分区
选项 1:提前筛选倾斜的键值
如果不会影响业务逻辑,可以提前筛选高频率值。 例如,如果在 GUID 列中存在许多 000-000-000,那么你可能不希望对该值进行聚合。 在聚合之前,可以编写“WHERE GUID != ”000-000-000“来筛选高频率值。
选项 2:选取其他分区或分发键
在前面的示例中,如果只想检查全国各地/地区的税务审核工作负荷,可以通过选择 ID 号作为密钥来改进数据分布。 选择不同的分区或分发键有时可以更均匀地分布数据,但需要确保此选项不会影响业务逻辑。 例如,若要计算每个州的税金,可能需要将 State 指定为分区键。 如果继续遇到此问题,请尝试使用选项 3。
选项 3:添加更多分区或分发键
可以使用多个键进行分区,而不是仅使用 State 作为分区键。 例如,请考虑将 邮政编码 添加为另一个分区键,以减少数据分区大小并更均匀地分布数据。
选项 4:使用轮询分配
如果找不到适合分区和分布的键,则可以尝试使用轮循进行分布。 轮循分布平均处理所有行,并随机将它们放入相应的存储桶中。 数据得到均匀分布,但会丢失位置信息,这是一个缺点,这可能还会降低某些操作的作业性能。 此外,如果要对倾斜的键执行聚合,则数据倾斜问题将仍然存在。 若要了解有关轮循分配的详细信息,请参阅 CREATE TABLE(U-SQL):创建带有模式的表的 U-SQL 表分配部分。
解决方案 2:改进查询计划
选项 1:使用 CREATE STATISTICS 语句
U-SQL 针对表提供 CREATE STATISTICS 语句。 此语句向查询优化器提供有关存储在表中的数据特征(例如值分布)的更多信息。 对于大多数查询,查询优化器已经为高质量的查询计划生成必要的统计信息。 有时,可能需要通过 CREATE STATISTICS 创建更多统计信息或修改查询设计来提高查询性能。 有关详细信息,请参阅 CREATE STATISTICS (U-SQL) 页。
代码示例:
CREATE STATISTICS IF NOT EXISTS stats_SampleTable_date ON SampleDB.dbo.SampleTable(date) WITH FULLSCAN;
注释
统计信息不会自动更新。 如果在不重新创建统计信息的情况下更新表中的数据,查询性能可能会下降。
选项 2:使用 SKEWFACTOR
如果要对每个州的税收求和,必须使用 GROUP BY 状态,此方法不会避免数据倾斜问题。 但是,可以在查询中提供数据提示来标识键中的数据偏斜,以便优化器可以为你准备执行计划。
通常,可以将参数设置为 0.5 和 1,0.5 表示不多倾斜,一个表示严重倾斜。 由于该提示会影响当前语句和所有下游语句的执行计划优化,因此请确保在潜在的偏斜键聚合之前添加提示。
SKEWFACTOR (columns) = x
提供一个提示,即给定列的倾斜因子 x 从 0(无倾斜)到 1(重倾斜)。
代码示例:
//Add a SKEWFACTOR hint.
@Impressions =
SELECT * FROM
searchDM.SML.PageView(@start, @end) AS PageView
OPTION(SKEWFACTOR(Query)=0.5)
;
//Query 1 for key: Query, ClientId
@Sessions =
SELECT
ClientId,
Query,
SUM(PageClicks) AS Clicks
FROM
@Impressions
GROUP BY
Query, ClientId
;
//Query 2 for Key: Query
@Display =
SELECT * FROM @Sessions
INNER JOIN @Campaigns
ON @Sessions.Query == @Campaigns.Query
;
选项 3:使用 ROWCOUNT
除了 SKEWFACTOR 之外,对于特定的倾斜键联接事例,如果知道其他联接行集很小,可以通过在 JOIN 之前在 U-SQL 语句中添加 ROWCOUNT 提示来告诉优化器。 这样,优化器就可以选择广播联接策略来帮助提高性能。 请注意,ROWCOUNT 无法解决数据倾斜问题,但它可以提供一些额外的帮助。
OPTION(ROWCOUNT = n)
在 JOIN 之前,通过提供估计的整数行数,标识一个小规模的行集。
代码示例:
//Unstructured (24-hour daily log impressions)
@Huge = EXTRACT ClientId int, ...
FROM @"wasb://ads@wcentralus/2015/10/30/{*}.nif"
;
//Small subset (that is, ForgetMe opt out)
@Small = SELECT * FROM @Huge
WHERE Bing.ForgetMe(x,y,z)
OPTION(ROWCOUNT=500)
;
//Result (not enough information to determine simple broadcast JOIN)
@Remove = SELECT * FROM Bing.Sessions
INNER JOIN @Small ON Sessions.Client == @Small.Client
;
解决方案 3:改进用户定义的化简器和合并器
有时可以编写用户定义的运算符来处理复杂的进程逻辑,并且编写良好的化简器和合并器在某些情况下可能会缓解数据倾斜问题。
选项 1:尽可能使用递归化简器
默认情况下,用户定义的化简器以非递归模式运行,这意味着密钥的化简工作将分布到单个顶点。 但是,如果数据有偏斜,庞大的数据集可能会在单个节点中被处理,并且会运行很长时间。
为了提高性能,可以在代码中添加一个属性,以定义要在递归模式下运行的化简器。 然后,可以将大型数据集分布到多个顶点并并行运行,从而加快作业速度。
若要将非递归化简器更改为递归,需要确保算法是关联的。 例如,总和是关联性的,并且中值不是。 还需要确保化简器的输入和输出保持相同的架构。
递归化简器的属性:
[SqlUserDefinedReducer(IsRecursive = true)]
代码示例:
[SqlUserDefinedReducer(IsRecursive = true)]
public class TopNReducer : IReducer
{
public override IEnumerable<IRow>
Reduce(IRowset input, IUpdatableRow output)
{
//Your reducer code goes here.
}
}
选项 2:如果可能,请使用行级组合模式
与特定倾斜键联接事例的 ROWCOUNT 提示类似,组合器模式尝试将巨大的偏斜键值集分布到多个顶点,以便可以并发执行工作。 合并器模式无法解决数据倾斜问题,但它可以为巨大的倾斜键值集提供一些额外的帮助。
默认情况下,合并器模式为 Full,这意味着无法分隔左行集和右行集。 将连接模式设置为“左/右/内部”可启用行级别连接。 系统将相应的行集分开,并将其分布到并行运行的多个顶点中。 但是,在配置合并器模式之前,请务必确保可以分隔相应的行集。
以下示例显示了一个单独的左行集。 每个输出行都依赖于左侧的单个输入行,并且可能依赖于右侧具有相同键值的所有行。 如果将合并器模式设置为左,系统会将巨大的左行集合分割为较小的集合,并将它们分配给多个顶点。
注释
如果设置错误的组合器模式,则组合效率较低,并且结果可能不正确。
组合器模式的属性:
SqlUserDefinedCombiner(Mode=CombinerMode.Full):每个输出行都可能依赖于左侧和右侧具有相同键值的所有输入行。
SqlUserDefinedCombiner(Mode=CombinerMode.Left):每个输出行都依赖于左侧的单个输入行(可能来自右侧的所有行具有相同键值)。
qlUserDefinedCombiner(Mode=CombinerMode.Right):每个输出行都依赖于右侧的单个输入行(可能来自左侧的所有行具有相同键值)。
SqlUserDefinedCombiner(Mode=CombinerMode.Inner):每个输出行都依赖于左侧和右侧具有相同值的单个输入行。
代码示例:
[SqlUserDefinedCombiner(Mode = CombinerMode.Right)]
public class WatsonDedupCombiner : ICombiner
{
public override IEnumerable<IRow>
Combine(IRowset left, IRowset right, IUpdatableRow output)
{
//Your combiner code goes here.
}
}