很久很久以前...
ClickHouse 用户已经知道,它最大的优势在于其分析查询的高速处理能力。但是这样的说法需要通过可靠的性能测试来证实。这就是我们今天想要讨论的内容。
我们从 2013 年开始运行测试,那时 ClickHouse 尚未开源。当时,我们主要关注的是 Web 分析产品的数据处理速度。我们从 2009 年 1 月开始存储这些数据,这些数据后来将存储在 ClickHouse 中。部分数据从 2012 年开始写入数据库,部分数据从 OLAPServer 和 Metrage(解决方案先前使用的数据结构)转换而来。为了进行测试,我们从 10 亿次页面浏览量的数据中随机抽取了第一个子集。我们的 Web 分析平台当时没有任何查询,因此我们提出了我们感兴趣的查询,使用了所有可能的过滤、聚合和排序数据的方法。
ClickHouse 的性能与 Vertica 和 MonetDB 等类似系统进行了比较。为了避免偏见,测试由一位未参与 ClickHouse 开发的员工执行,并且在获得所有结果之前,代码中的特殊情况没有进行优化。我们使用了相同的方法来获取用于功能测试的数据集。
在 ClickHouse 于 2016 年开源发布后,人们开始质疑这些测试。
私有数据测试的缺点
我们的性能测试
- 无法独立重现,因为它们使用了无法发布的私有数据。出于同样的原因,一些功能测试对外部用户不可用。
- 需要进一步开发。为了隔离系统中各个部分的性能变化,需要大幅扩展测试集。
- 没有在每次提交或针对单个拉取请求运行时运行。外部开发人员无法检查他们的代码是否存在性能回归。
我们可以通过抛弃旧测试并基于开放数据编写新测试来解决这些问题,例如美国的航班数据和纽约的出租车行程。或者我们可以使用 TPC-H、TPC-DS 和星型模式基准测试等基准。缺点是这些数据与 Web 分析数据非常不同,我们宁愿保留测试查询。
为什么使用真实数据很重要
性能应该只在来自生产环境的真实数据上进行测试。让我们看一些例子。
示例 1
假设您用均匀分布的伪随机数填充数据库。在这种情况下,数据压缩将不起作用,尽管数据压缩对于分析型数据库至关重要。在选择正确的压缩算法以及将其集成到系统中的正确方法方面,没有万能的解决方案,因为数据压缩需要在压缩和解压缩的速度与潜在的压缩效率之间做出妥协。但是,无法压缩数据的系统肯定是失败者。如果您的测试使用均匀分布的伪随机数,则会忽略此因素,并且结果将被扭曲。
底线:测试数据必须具有真实的压缩率。
示例 2
假设我们对这个 SQL 查询的执行速度感兴趣
SELECT RegionID, uniq(UserID) AS visitors
FROM test.hits
GROUP BY RegionID
ORDER BY visitors DESC
LIMIT 10
这是一个典型的 Web 分析产品的查询。什么影响处理速度?
- How
GROUP BY
is executed. - Which data structure is used for calculating the
uniq
aggregate function. - How many different RegionIDs there are and how much RAM each state of the
uniq
function requires.
但另一个重要因素是,数据量在不同区域之间分布不均。(它可能遵循幂律。我将分布放在对数-对数图上,但我不确定。)如果是这种情况,则值较少的 uniq 聚合函数的状态必须使用非常少的内存。当有很多不同的聚合键时,每一字节都很重要。我们如何获得具有所有这些属性的生成数据?显而易见的解决方案是使用真实数据。
许多 DBMS 实现了 HyperLogLog 数据结构来近似 COUNT(DISTINCT),但它们都不能很好地工作,因为此数据结构使用固定量的内存。ClickHouse 有一个函数,它根据数据集的大小使用三种不同数据结构的组合。
底线:测试数据必须充分代表真实数据的分布属性,这意味着基数(每列不同值的数量)和跨列基数(跨多个不同列计数的不同值的数量)。
示例 3
与其测试 ClickHouse DBMS 的性能,不如采用更简单的东西,例如哈希表。对于哈希表,选择正确的哈希函数至关重要。对于 std::unordered_map 来说,这并不那么重要,因为它是一个基于链的哈希表,并且使用质数作为数组大小。GCC 和 Clang 中的标准库实现使用一个简单的哈希函数作为数字类型的默认哈希函数。但是,当我们寻求最大速度时,std::unordered_map 并不是最佳选择。对于开放寻址哈希表,我们不能仅仅使用标准哈希函数。选择正确的哈希函数成为决定性因素。
很容易找到使用随机数据的哈希表性能测试,这些测试没有考虑使用的哈希函数。许多哈希函数测试也侧重于计算速度和某些质量标准,即使它们忽略了使用的数据结构。但事实是,哈希表和 HyperLogLog 需要不同的哈希函数质量标准。
挑战
我们的目标是获得用于测试性能的数据,该数据具有与我们的 Web 分析数据相同的结构,并具有对基准测试重要的所有属性,但方式是数据中不保留真实网站用户的任何痕迹。换句话说,数据必须是匿名的,并且仍然保留其
- 压缩率。
- 基数(不同值的数量)。
- 几个不同列之间的互基数。
- 可用于数据建模的概率分布的属性(例如,如果我们认为区域根据幂律分布,那么指数——分布参数——对于人工数据和真实数据应该大致相同)。
我们如何获得数据相似的压缩率?如果使用 LZ4,则二进制数据中的子字符串必须以大致相同的距离重复,并且重复必须具有大致相同的长度。对于 ZSTD,每字节的熵也必须一致。
最终目标是创建一个公开可用的工具,任何人都可以使用该工具来匿名化他们的数据集以供发布。这将使我们能够在类似于我们生产数据的其他人数据上进行调试和测试性能。我们还希望生成的数据有趣。
但是,这些是非常宽松定义的要求,我们不打算为这项任务编写正式的问题陈述或规范。
可能的解决方案
我不想让它听起来好像这个问题特别重要。它实际上从未包含在计划中,也没有人打算处理它。我希望有一天会有一个想法出现,突然间我会心情很好,并且能够将其他一切都推迟到以后。
显式概率模型
- 我们希望保持时间序列数据的连续性。这意味着对于某些类型的数据,我们需要对相邻值之间的差异而不是值本身进行建模。
- 为了对列的“联合基数”进行建模,我们还必须明确反映列之间的依赖关系。例如,每个用户 ID 通常只有很少的 IP 地址,因此要生成 IP 地址,我们必须使用用户 ID 的哈希值作为种子,并添加少量其他伪随机数据。
- 我们不确定如何表达这样的依赖关系:同一用户经常在大约同一时间访问具有匹配域名的 URL。
所有这些都可以用 C++ “脚本”编写,其中分布和依赖关系被硬编码。但是,马尔可夫模型是从统计数据与平滑和添加噪声相结合获得的。我开始编写这样的脚本,但是在为十列编写显式模型后,它变得令人难以忍受地无聊——而且早在 2012 年,Web 分析产品中的“点击”表就超过了 100 列。
EventTime.day(std::discrete_distribution<>({
0, 0, 13, 30, 0, 14, 42, 5, 6, 31, 17, 0, 0, 0, 0, 23, 10, ...})(random));
EventTime.hour(std::discrete_distribution<>({
13, 7, 4, 3, 2, 3, 4, 6, 10, 16, 20, 23, 24, 23, 18, 19, 19, ...})(random));
EventTime.minute(std::uniform_int_distribution<UInt8>(0, 59)(random));
EventTime.second(std::uniform_int_distribution<UInt8>(0, 59)(random));
UInt64 UserID = hash(4, powerLaw(5000, 1.1));
UserID = UserID / 10000000000ULL * 10000000000ULL
+ static_cast<time_t>(EventTime) + UserID % 1000000;
random_with_seed.seed(powerLaw(5000, 1.1));
auto get_random_with_seed = [&]{ return random_with_seed(); };
优点
- 概念上的简洁性。
缺点
- 需要大量工作。
- 该解决方案仅适用于一种类型的数据。
我更喜欢更通用的解决方案,该解决方案可用于混淆任何数据集。
在任何情况下,这个解决方案都可以改进。我们可以实现模型目录并从中选择最佳模型(最佳拟合加上某种形式的正则化),而不是手动选择模型。或者,我们可以对所有类型的字段使用马尔可夫模型,而不仅仅是文本。数据之间的依赖关系也可以自动提取。这将需要计算列之间的相对熵(相对信息量)。一个更简单的替代方法是计算每对列的相对基数(类似于“对于固定的值 B,平均有多少个不同的 A 值”)。例如,这将清楚地表明 URLDomain 完全依赖于 URL,反之亦然。
但我也拒绝了这个想法,因为有太多因素需要考虑,而且编写起来会花费太长时间。
神经网络
正如我已经提到的,这项任务在优先级列表中并不高——甚至没有人考虑尝试解决它。但幸运的是,我们的同事 Ivan Puzirevsky 在高等经济学院任教。他问我是否有任何有趣的问题可以作为他学生的合适论文主题。当我向他提供这个问题时,他向我保证它有潜力。所以我把这个挑战交给了“街头”的一个好人 Sharif(尽管他确实必须签署 NDA 才能访问数据)。
我与他分享了我的所有想法,但强调解决问题的方式没有任何限制,一个不错的选择是尝试我一无所知的方法,例如使用 LSTM 生成数据的文本转储。在看到文章《循环神经网络的不合理有效性》后,这似乎很有希望。
第一个挑战是我们需要生成结构化数据,而不仅仅是文本。但尚不清楚循环神经网络是否可以生成具有所需结构的数据。有两种方法可以解决这个问题。第一个解决方案是使用单独的模型来生成结构和“填充物”,并且仅使用神经网络来生成值。但是这种方法被推迟了,然后从未完成。第二个解决方案是简单地生成 TSV 转储作为文本。经验表明,文本中的某些行与结构不匹配,但是可以在加载数据时删除这些行。
第二个挑战是循环神经网络生成数据序列,因此数据中的依赖关系必须遵循序列的顺序。但是在我们的数据中,列的顺序可能与它们之间的依赖关系相反。我们没有做任何事情来解决这个问题。
随着夏天的临近,我们有了第一个可以生成数据的 Python 工作脚本。乍一看,数据质量似乎不错
但是,我们确实遇到了一些困难
-
模型的大小约为千兆字节。我们尝试为大小为几个千兆字节的数据(作为开始)创建一个模型。结果模型如此之大这一事实引起了担忧。是否有可能提取它训练的真实数据?不太可能。但是我对机器学习和神经网络了解不多,也没有阅读过这位开发人员的 Python 代码,所以我怎么能确定呢?当时发表了几篇文章,内容是关于如何在不损失质量的情况下压缩神经网络,但它没有实现。一方面,这似乎不是一个严重的问题,因为我们可以选择不发布模型而只发布生成的数据。另一方面,如果发生过拟合,则生成的数据可能包含源数据的某些部分。
-
在具有单个 CPU 的机器上,数据生成速度约为每秒 100 行。我们的目标是生成至少十亿行。计算表明,这在论文答辩日期之前无法完成。使用额外的硬件没有意义,因为目标是制作任何人都可以使用的数据生成工具。
Sharif 尝试通过比较统计数据来分析数据质量。除其他外,他计算了源数据和生成数据中不同字符出现的频率。结果令人震惊:最常见的字符是 Ð 和 Ñ。
不过,别担心 Sharif。他成功地捍卫了自己的论文,我们也很高兴地忘记了整件事。
压缩数据的突变
让我们假设问题陈述已简化为一个要点:我们需要生成与源数据具有相同压缩率的数据,并且数据必须以相同的速度解压缩。我们如何实现这一目标?我们需要直接编辑压缩数据字节!这使我们能够在不更改压缩数据大小的情况下更改数据,而且一切都会快速工作。我想立即尝试这个想法,尽管它解决的问题与我们最初开始的问题不同。但事情总是这样。
那么我们如何编辑压缩文件呢?假设我们只对 LZ4 感兴趣。LZ4 压缩数据由序列组成,这些序列又是未压缩字节(文字)的字符串,后跟匹配副本
- 文字(按原样复制以下 N 个字节)。
- 匹配,最小重复长度为 4(在文件中以 M 的距离重复 N 个字节)。
源数据
Hello world Hello.
压缩数据(任意示例)
literals 12 "Hello world " match 5 12.
在压缩文件中,我们将“match”保持原样,并更改“literals”中的字节值。结果,在解压缩后,我们得到一个文件,其中所有至少 4 个字节长的重复序列也在相同的距离重复,但它们由一组不同的字节组成(基本上,修改后的文件不包含从源文件获取的任何字节)。
但是我们如何更改字节呢?答案并不明显,因为除了列类型之外,数据还具有我们希望保留的自身内部的、隐式的结构。例如,文本数据通常以 UTF-8 编码存储,我们希望生成的数据也是有效的 UTF-8。我开发了一种简单的启发式方法,涉及满足几个标准
- 空字节和 ASCII 控制字符保持原样。
- 一些标点符号保持原样。
- ASCII 转换为 ASCII,对于其他所有内容,保留最高有效位(或者为不同的 UTF-8 长度编写一组显式的“if”语句)。在一个字节类中,均匀随机地选择一个新值。
- 像 https:// 这样的片段被保留;否则,它看起来有点傻。
这种方法的唯一缺点是数据模型是源数据本身,这意味着它不能发布。该模型仅适用于生成不超过源数据量的数据量。相反,以前的方法提供了允许生成任意大小数据的模型。
http://ljc.she/kdoqdqwpgafe/klwlpm&qw=962788775I0E7bs7OXeAyAx
http://ljc.she/kdoqdqwdffhant.am/wcpoyodjit/cbytjgeoocvdtclac
http://ljc.she/kdoqdqwpgafe/klwlpm&qw=962788775I0E7bs7OXe
http://ljc.she/kdoqdqwdffhant.am/wcpoyodjit/cbytjgeoocvdtclac
http://ljc.she/kdoqdqwdbknvj.s/hmqhpsavon.yf#aortxqdvjja
http://ljc.she/kdoqdqw-bknvj.s/hmqhpsavon.yf#aortxqdvjja
http://ljc.she/kdoqdqwpdtu-Unu-Rjanjna-bbcohu_qxht
http://ljc.she/kdoqdqw-bknvj.s/hmqhpsavon.yf#aortxqdvjja
http://ljc.she/kdoqdqwpdtu-Unu-Rjanjna-bbcohu_qxht
http://ljc.she/kdoqdqw-bknvj.s/hmqhpsavon.yf#aortxqdvjja
http://ljc.she/kdoqdqwpdtu-Unu-Rjanjna-bbcohu-702130
结果是积极的,数据很有趣,但有些地方不太对劲。URL 保留了相同的结构,但在其中一些 URL 中,很容易识别出原始术语,例如“avito”(俄罗斯的一个流行市场),所以我创建了一种启发式方法,可以交换一些字节。
还有其他担忧。例如,敏感信息可能以二进制表示形式驻留在 FixedString 列中,并且可能由 ASCII 控制字符和标点符号组成,我决定保留这些字符和标点符号。但是,我没有考虑数据类型。
另一个问题是,如果列以“长度,值”格式存储数据(这是 String 列的存储方式),我如何确保长度在突变后保持正确?当我尝试解决这个问题时,我立刻失去了兴趣。
随机排列
不幸的是,问题没有解决。我们进行了一些实验,但情况变得更糟。剩下的唯一事情就是无所事事地坐着,随机浏览网页,因为魔力消失了。幸运的是,我偶然发现一个页面,该页面解释了游戏《Wolfenstein 3D》中渲染主角死亡的算法。

动画做得非常好——屏幕上充满了鲜血。文章解释说,这实际上是一个伪随机排列。一组元素的随机排列是对该集合随机选择的双射(一对一)变换。换句话说,一种映射,其中每个导出的元素都恰好对应于一个原始元素(反之亦然)。换句话说,这是一种随机迭代数据集所有元素的方法。这正是图片中显示的过程:每个像素都以随机顺序填充,没有任何重复。如果我们只是在每一步选择一个随机像素,那么到达最后一个像素将需要很长时间。
该游戏使用一种非常简单的伪随机排列算法,称为线性反馈移位寄存器(LFSR)。与伪随机数生成器类似,随机排列,或者更确切地说它们的族,当通过密钥参数化时,可以是密码学上安全的。这正是我们的数据转换所需要的。但是,细节比较棘手。例如,使用预定密钥和初始化向量将 N 个字节密码学上强加密为 N 个字节,似乎适用于 N 字节字符串集的伪随机排列。的确,这是一对一的转换,并且看起来是随机的。但是,如果我们对所有数据使用相同的转换,则结果可能会容易受到密码分析的影响,因为多次使用相同的初始化向量和密钥值。这类似于分组密码的电子密码本操作模式。
例如,murmurhash 终结器使用了三个乘法和两个 xorshift 操作。此操作是伪随机排列。但是,我应该指出,哈希函数不必是一对一的(即使是 N 位到 N 位的哈希)。
或者,这是 Jeff Preshing 网站上来自初等数论的另一个有趣的例子。
我们如何使用伪随机排列来解决我们的问题?我们可以使用它们来转换所有数字字段,以便我们可以保留所有字段组合的基数和互基数。换句话说,COUNT(DISTINCT) 将返回与转换前相同的值,并且对于任何 GROUP BY 也是如此。
值得注意的是,保留所有基数在某种程度上与我们数据匿名化的目标相矛盾。假设有人知道站点会话的源数据包含一个访问过来自 10 个不同国家的站点的用户,并且他们想在转换后的数据中找到该用户。转换后的数据也显示该用户访问过来自 10 个不同国家的站点,这使得缩小搜索范围变得容易。但是,即使他们发现用户被转换成了什么,也不会非常有用;所有其他数据也已转换,因此他们将无法弄清楚用户访问了哪些站点或任何其他内容。但是这些规则可以链接应用。例如,假设有人知道我们数据中最常出现的网站是 Google,其次是 Yahoo。在这种情况下,他们可以使用排名来确定哪些转换后的站点标识符实际上意味着 Yahoo 和 Google。这没什么奇怪的,因为我们正在处理非正式的问题陈述,并且我们试图在数据匿名化(隐藏信息)和保留数据属性(公开信息)之间找到平衡。有关如何更可靠地处理数据匿名化问题的信息,请阅读这篇文章。
除了保持值的原始基数之外,我还想保持值的数量级。我的意思是,如果源数据包含小于 10 的数字,那么我希望转换后的数字也很小。我们如何实现这一目标?
例如,我们可以将一组可能的值划分为大小类,并在每个类中单独执行排列(保持大小类)。最简单的方法是将最接近的 2 的幂或数字中最高有效位的位置作为大小类(这些是同一件事)。数字 0 和 1 将始终保持不变。数字 2 和 3 有时会保持不变(概率为 1/2),有时会被交换(概率为 1/2)。数字 1024..2047 的集合将映射到 1024!(阶乘)个变体之一,依此类推。对于有符号数字,我们将保留符号。
我们也怀疑是否需要一对一函数。我们可能只需使用密码学上强大的哈希函数。转换不会是一对一的,但基数将接近相同。
但是,我们需要一个密码学上强大的随机排列,以便当我们定义一个密钥并使用该密钥导出排列时,在不知道密钥的情况下从重新排列的数据中恢复原始数据将很困难。
有一个问题:除了对神经网络和机器学习一无所知之外,我对密码学也很无知。剩下的只有我的勇气。我仍然在阅读随机网页,并在 Hackers News 上找到了一个链接,指向 Fabien Sanglard 页面上的讨论。它有一个链接,指向 Redis 开发人员 Salvatore Sanfilippo 的一篇博客文章,该文章谈到了使用一种获取随机排列的绝妙通用方法,即 Feistel 网络。
Feistel 网络是迭代的,由多轮组成。每一轮都是一个非凡的转换,它允许您从任何函数获得一对一函数。让我们看看它是如何工作的。
- 参数的位被分成两半
arg: xxxxyyyy
arg_l: xxxx
arg_r: yyyy
- 右半部分替换左半部分。在它的位置,我们将左半部分初始值与应用于右半部分初始值的函数结果进行 XOR 运算的结果,就像这样
res: yyyyzzzz
res_l = yyyy = arg_r
res_r = zzzz = arg_l ^ F(arg_r)
还有一种说法,如果我们对 F 使用密码学上强大的伪随机函数并应用 Feistel 轮至少四次,我们将获得密码学上强大的伪随机排列。
这就像一个奇迹:我们采用一个基于数据生成随机垃圾的函数,将其插入 Feistel 网络,现在我们有了一个基于数据生成随机垃圾的函数,但它仍然是可逆的!
Feistel 网络是几种数据加密算法的核心。我们要做的事情有点像加密,只是它非常糟糕。有两个原因
- 我们正在独立且以相同的方式加密单个值,类似于电子密码本操作模式。
- 我们正在存储有关数量级(最接近的 2 的幂)和值符号的信息,这意味着某些值根本不会更改。
通过这种方式,我们可以混淆数字字段,同时保留我们需要的属性。例如,在使用 LZ4 之后,压缩率应保持大致相同,因为源数据中的重复值将在转换后的数据中重复,并且彼此之间的距离相同。
马尔可夫模型
文本模型用于数据压缩、预测输入、语音识别和随机字符串生成。文本模型是所有可能字符串的概率分布。假设我们有一个人类可能写出的所有书籍文本的假想概率分布。要生成一个字符串,我们只需使用此分布取一个随机值,然后返回结果字符串(人类可能写出的一本随机书)。但是我们如何找出所有可能字符串的概率分布呢?
首先,这将需要过多的信息。长度为 10 字节的字符串有 256^10 种可能性,并且显式编写一个包含每个字符串概率的表将占用大量内存。其次,我们没有足够的统计数据来准确评估分布。
这就是为什么我们使用从粗略统计数据获得的概率分布作为文本模型的原因。例如,我们可以计算每个字母在文本中出现的概率,然后通过以相同的概率选择每个下一个字母来生成字符串。这种原始模型有效,但生成的字符串仍然非常不自然。
为了稍微改进模型,我们还可以利用字母出现情况的条件概率,如果它前面有 N 个特定字母。N 是一个预设常数。假设 N = 5,我们正在计算字母“e”在字母“compr”之后出现的概率。这种文本模型称为 N 阶马尔可夫模型。
P(cata | cat) = 0.8
P(catb | cat) = 0.05
P(catc | cat) = 0.1
...
让我们看看马尔可夫模型在 Hay Kranen 的网站上的工作原理。与 LSTM 神经网络不同,这些模型只有足够的内存来处理固定长度 N 的小上下文,因此它们会生成有趣的无意义文本。马尔可夫模型也用于生成垃圾邮件的原始方法中,并且可以通过计数不符合模型的统计数据轻松地区分生成的文本与真实文本。但有一个优点:马尔可夫模型比神经网络快得多,这正是我们需要的。
标题示例(我们的示例是土耳其语,因为使用了土耳其语数据)
Hyunday Butter'dan anket shluha — Politika head manşetleri | STALKER BOXER Çiftede book — Yanudistkarışmanlı Mı Kanal | League el Digitalika Haberler Haberleri — Haberlerisi — Hotels with Centry'ler Neden babah.com
我们可以从源数据计算统计数据,创建马尔可夫模型,并生成新数据。请注意,该模型需要平滑处理,以避免泄露有关源数据中稀有组合的信息,但这不成问题。我们使用从 0 阶到 N 阶模型的组合。如果阶数 N 的统计数据不足,则改用 N-1 阶模型。
但我们仍然希望保留数据的基数。换句话说,如果源数据有 123456 个唯一 URL 值,则结果应具有大致相同数量的唯一值。我们可以使用确定性初始化的随机数生成器来实现这一点。最简单的方法是使用哈希函数并将其应用于原始值。换句话说,我们得到一个伪随机结果,该结果由原始值显式确定。
另一个要求是源数据可能有很多不同的 URL,它们以相同的前缀开头但不完全相同。例如: https://www.clickhouse.com/images/cats/?id=xxxxxx
。我们希望结果也具有都以相同前缀开头的 URL,但前缀不同。例如: http://ftp.google.kz/cgi-bin/index.phtml?item=xxxxxx。作为使用马尔可夫模型生成下一个字符的随机数生成器,我们将从指定位置的 8 字节移动窗口中获取哈希函数(而不是从整个字符串中获取)。
https://www.clickhouse.com/images/cats/?id=12345 ^^^^^^^^ distribution: [aaaa][b][cc][dddd][e][ff][ggggg][h]... hash("images/c") % total_count: ^
结果证明这正是我们需要的。以下是页面标题的示例
PhotoFunia - Haber7 - Hava mükemment.net Oynamak içinde şaşıracak haber, Oyunu Oynanılmaz • apród.hu kínálatában - RT Arabic PhotoFunia - Kinobar.Net - apród: Ingyenes | Posti PhotoFunia - Peg Perfeo - Castika, Sıradışı Deniz Lokoning Your Code, sire Eminema.tv/ PhotoFunia - TUT.BY - Your Ayakkanın ve Son Dakika Spor, PhotoFunia - big film izle, Del Meireles offilim, Samsung DealeXtreme Değerler NEWSru.com.tv, Smotri.com Mobile yapmak Okey PhotoFunia 5 | Galaxy, gt, după ce anal bilgi yarak Ceza RE050A V-Stranç PhotoFunia :: Miami olacaksını yerel Haberler Oyun Young video PhotoFunia Monstelli'nin En İyi kisa.com.tr –Star Thunder Ekranı PhotoFunia Seks - Politika,Ekonomi,Spor GTA SANAYİ VE PhotoFunia Taker-Rating Star TV Resmi Söylenen Yatağa każdy dzież wierzchnie PhotoFunia TourIndex.Marketime oyunu Oyna Geldolları Mynet Spor,Magazin,Haberler yerel Haberleri ve Solvia, korkusuz Ev SahneTv PhotoFunia todo in the Gratis Perky Parti'nin yapıyı bu fotogram PhotoFunian Dünyasın takımız halles en kulları - TEZ
结果
在尝试了四种方法后,我对这个问题感到厌倦,是时候选择一些方法,将其制成可用的工具并宣布解决方案了。我选择了使用随机排列和由密钥参数化的马尔可夫模型的解决方案。它以 clickhouse-obfuscator 程序的形式实现,该程序非常易于使用。输入是任何受支持格式(例如 CSV 或 JSONEachRow)的表转储,命令行参数指定表结构(列名和类型)和密钥(任何字符串,您可以在使用后立即忘记它)。输出是相同行数的混淆数据。
该程序与 clickhouse-client
一起安装,没有依赖项,并且几乎可以在任何 Linux 版本上运行。您可以将其应用于任何数据库转储,而不仅仅是 ClickHouse。例如,您可以从 MySQL 或 PostgreSQL 数据库生成测试数据,或创建类似于生产数据库的开发数据库。
clickhouse-obfuscator \
--seed "$(head -c16 /dev/urandom | base64)" \
--input-format TSV --output-format TSV \
--structure 'CounterID UInt32, URLDomain String, \
URL String, SearchPhrase String, Title String' \
< table.tsv > result.tsv
clickhouse-obfuscator --help
当然,一切并非如此简单,因为通过此程序转换的数据几乎是完全可逆的。问题是在不知道密钥的情况下是否可以执行反向转换。如果转换使用了加密算法,则此操作将与暴力搜索一样困难。虽然转换使用了一些加密原语,但它们的使用方式不正确,并且数据容易受到某些分析方法的攻击。为了避免问题,这些问题在程序的文档中进行了说明(使用 --help 访问它)。
最后,我们转换了 我们需要的用于功能和性能测试 的数据集,并获得了数据安全团队的发布批准。
我们的开发人员和社区成员在优化 ClickHouse 内部算法时使用此数据进行实际性能测试。第三方用户可以向我们提供他们混淆的数据,以便我们可以使 ClickHouse 对他们来说更快。我们还在此数据之上发布了一个独立的开放基准测试,供硬件和云提供商使用: https://benchmark.clickhouse.com/