RocksDB 跟踪、回放、分析和工作负载生成
无标题的
查询跟踪和重放
trace_replay API允许用户将查询信息跟踪到跟踪文件中。在当前实现中,Get、WriteBatch (Put、Delete、Merge、SingleDelete和DeleteRange)、Iterator (Seek和SeekForPrev)是trace_replay API跟踪的查询。 键、查询时间戳、值(如果应用)、cf_id形成一条跟踪记录。由于使用一个锁来保护跟踪实例,并且跟踪文件将有额外的IOs,因此DB的性能将受到影响。 根据目前在MyRocks和ZippyDB影子服务器上的测试,在这些影子中,性能并不是一个问题。来自相同DB实例的跟踪记录被写入二进制跟踪文件。 用户可以指定跟踪文件的路径(例如,存储在不同的存储设备中,以减少IO的影响)。
目前,可以使用db_bench重新播放跟踪文件。跟踪文件中的查询记录根据时间戳重新播放到目标DB实例。 它可以重放几乎与所收集的工作负载相同的工作负载,这将提供更类似于生产的测试用例。
一个使用跟踪API的简单例子:
Env* env = rocksdb::Env::Default();EnvOptions env_options;std::string trace_path = "/tmp/trace_test_example"std::unique_ptr<TraceWriter> trace_writer;DB* db = nullptr;std::string db_name = "/tmp/rocksdb"/*Create the trace file writer*/NewFileTraceWriter(env, env_options, trace_path, &trace_writer);DB::Open(options, dbname, &db);/*Start tracing*/db->StartTrace(trace_opt, std::move(trace_writer));/* your call of RocksDB APIs *//*End tracing*/db->EndTrace()
要重放跟踪:
./db_bench --benchmarks=replay --trace_file=/tmp/trace_test_example --num_column_families=5
跟踪分析、可视化和建模
在用户使用trace_replay API完成跟踪步骤之后,用户将获得一个二进制跟踪文件。在跟踪文件中,Get、Seek和SeekForPrev使用单独的跟踪记录进行跟踪, 而Put、Merge、Delete、SingleDelete和DeleteRange的查询被打包到writebatch中。 需要一种工具: 1)将跟踪解释为人类可读的格式以便进一步分析; 2)提供丰富而强大的内存处理选项来分析跟踪并输出相应的结果; 3)易于向工具中添加新的分析选项和查询类型。
RocksDB团队开发了该工具的最初版本:trace_analyzer。它提供了以下分析选项和输出结果。
注意,大多数生成的分析结果输出文件将被分隔在不同的列族和不同的查询类型中,这意味着一个列族中的一个查询类型将有自己的输出文件。 通常,一个指定的输出选项将生成一个输出文件。
分析跟踪
跟踪分析程序选项
-analyze_delete (Analyze the Delete query.) type: bool default: false-analyze_get (Analyze the Get query.) type: bool default: false-analyze_iterator ( Analyze the iterate query like seek() andseekForPrev().) type: bool default: false-analyze_merge (Analyze the Merge query.) type: bool default: false-analyze_put (Analyze the Put query.) type: bool default: false-analyze_range_delete (Analyze the DeleteRange query.) type: booldefault: false-analyze_single_delete (Analyze the SingleDelete query.) type: booldefault: false-convert_to_human_readable_trace (Convert the binary trace file to a humanreadable txt file for further processing. This file will be extremelylarge (similar size as the original binary trace file). You can specify'no_key' to reduce the size, if key is not needed in the next stepFile name: <prefix>_human_readable_trace.txtFormat:[type_id cf_id value_size time_in_micorsec <key>].) type: booldefault: false-key_space_dir (<the directory stores full key space files>The key space files should be: <column family id>.txt) type: stringdefault: ""-no_key ( Does not output the key to the result files to make smaller.)type: bool default: false-no_print (Do not print out any result) type: bool default: false-output_access_count_stats (Output the access count distribution statisticsto file.File name: <prefix>-<querytype>-<cf_id>-accessed_key_count_distribution.txtFormat:[access_count number_of_access_count]) type: bool default: false-output_dir (The directory to store the output files.) type: stringdefault: ""-output_ignore_count (<threshold>, ignores the access count <= this value,it will shorter the output.) type: int32 default: 0-output_key_distribution (Output the key size distribution.) type: booldefault: false-output_key_stats (Output the key access count statistics to filefor accessed keys:file name: <prefix>-<query type>-<cf_id>-accessed_key_stats.txtFormat:[cf_id value_size access_keyid access_count]for the whole key space keys:File name: <prefix>-<query type>-<cf_id>-whole_key_stats.txtFormat:[whole_key_space_keyid access_count]) type: bool default: false-output_prefix (The prefix used for all the output files.) type: stringdefault: "trace"-output_prefix_cut (The number of bytes as prefix to cut the keys.if it is enabled, it will generate the following:for accessed keys:File name: <prefix>-<query type>-<cf_id>-accessed_key_prefix_cut.txtFormat:[acessed_keyid access_count_of_prefix number_of_keys_in_prefixaverage_key_access prefix_succ_ratio prefix]for whole key space keys:File name: <prefix>-<query type>-<cf_id>-whole_key_prefix_cut.txtFormat:[start_keyid_in_whole_keyspace prefix]if 'output_qps_stats' and 'top_k' are enabled, it will output:File name: <prefix>-<querytype>-<cf_id>-accessed_top_k_qps_prefix_cut.txtFormat:[the_top_ith_qps_time QPS], [prefix qps_of_this_second].)type: int32 default: 0-output_qps_stats (Output the query per second(qps) statisticsFor the overall qps, it will contain all qps of each query type. The timeis started from the first trace recordFile name: <prefix>_qps_stats.txtFormat: [qps_type_1 qps_type_2 ...... overall_qps]For each cf and query, it will have its own qps outputFile name: <prefix>-<query type>-<cf_id>_qps_stats.txtFormat:[query_count_in_this_second].) type: bool default: false-output_time_series (Output the access time in second of each key, suchthat we can have the time series data of the queriesFile name: <prefix>-<query type>-<cf_id>-time_series.txtFormat:[type_id time_in_sec access_keyid].) type: bool default: false-output_value_distribution (Out put the value size distribution, onlyavailable for Put and Merge.File name: <prefix>-<querytype>-<cf_id>-accessed_value_size_distribution.txtFormat:[Number_of_value_size_between x and x+value_interval is: <thecount>]) type: bool default: false-print_correlation (intput format: [correlation pairs][.,.]Output the query correlations between the pairs of query types listed inthe parameter, input should select the operations from:get, put, delete, single_delete, rangle_delete, merge. No space betweenthe pairs separated by commar. Example: =[get,get]... It will print outthe number of pairs of 'A after B' and the average time interval betweenthe two query) type: string default: ""-print_overall_stats ( Print the stats of the whole trace, like totalrequests, keys, and etc.) type: bool default: true-print_top_k_access (<top K of the variables to be printed> Print the top kaccessed keys, top k accessed prefix and etc.) type: int32 default: 1-trace_path (The trace file path.) type: string default: ""-value_interval (To output the value distribution, we need to set the valueintervals and make the statistic of the value size distribution indifferent intervals. The default is 8.) type: int32 default: 8
一个例子
./trace_analyzer -analyze_get -output_access_count_stats -output_dir=/data/trace/result -output_key_stats-output_qps_stats -convert_to_human_readable_trace -output_value_distribution -output_key_distribution-print_overall_stats -print_top_k_access=3 -output_prefix=test -trace_path=/data/trace/trace
查询类型选项
用户可以指定应该分析的查询类型,并使用”-analyze_”。
输出人类可读的跟踪
原始二进制跟踪存储编码的数据结构和内容,要解释跟踪,工具应该使用RocksDB库。 因此,为了简化对跟踪的进一步分析,用户可以指定
-convert_to_human_readable_trace
原始跟踪将转换为txt文件,内容为”[type_id cf_id value_size time_in_micorsec]”。如果不需要密钥,用户可以指定”-no_key”来减小文件大小。 此选项独立于所有其他选项,一旦指定,将生成转换后的跟踪。如果包含原始键,则txt文件大小可能与原始跟踪文件大小相似,甚至更大。
输入和输出选项
要分析跟踪文件,用户需要指定跟踪文件的路径
-trace_path=<path to the trace>
要存储输出文件,用户可以指定一个目录(在运行分析器之前确保该目录存在)来存储这些文件
-output_dir=<the path to the output directory>
如果用户希望与现有密钥空间一起分析已访问的密钥。用户需要指定存储key空间文件的目录。 文件的名称应该是以”.txt”命名, 每一行都是一个键,通常,用户可以使用 “./ldb scan”的ldb工具,以转储所有现有的key。要指定目录
-key_space_dir=<the path to the key space directory>
为了更容易地收集输出文件,用户可以为所有输出文件指定”前缀”
-output_prefix=<the prefix, like "trace1">
如果用户不希望将一般统计信息打印到终端屏幕上,可以指定
-no_print
分析选项
目前,trace_analyzer工具提供了几种不同的分析选项来描述工作负载。一些结果直接打印出来(前缀为”-print”的选项),另一些将输出到文件(前缀为”-output”的选项)。 用户可以指定分析跟踪的选项的组合。注意,一些分析选项占用了大量内存(例如-output_time_series、-print_correlation和-key_space_dir)。 如果内存不够,尝试在不同的时间运行它们。
当指定此选项时,工作负载的一般信息(如键的总数、每个列族分析查询的数量、键和值大小统计信息(平均和中等)、被访问的键的数量等)将打印到屏幕上
-print_overall_stats
要获得每个键的总访问计数和值的大小,用户可以指定
-output_key_stats
它将输出文件中每个键的访问计数,并按字典顺序对键进行排序。每个键都将分配一个整数作为进一步处理的ID
在某些工作负载中,键的组成有一些共同的部分。例如,在MyRocks中,键的第一个X字节是表index_num。我们可以使用前X个字节将键切成不同的前缀范围。 通过指定要删除键空间的字节数,trace_analyzer将生成一个文件。文件中的一条记录表示前缀的剪切,相应的KeyID和前缀内容被存储。 如果指定-key_space_dir,将有两个单独的文件。一个文件用于访问的密钥,另一个文件用于整个密钥空间。 通常,前缀cut文件分别与accessed_key_stats.txt和whole_key_stats.txt一起使用。
-output_prefix_cut=<number of bytes as prefix>
如果用户想要可视化的访问跟踪时间轴中的键,用户可以指定:
-output_time_series
对一个key的每次访问都将作为一条记录存储在时间序列文件中。
如果用户有兴趣了解跟踪期间QPS变化的详细情况,可以指定:
-output_qps_stats
对于一个列族的每个查询类型,每秒将生成一个带有查询号的文件。 此外,在所有列族上都有一个包含每种查询类型的QPS的文件,以及所有QPS都输出到一个单独的文件中。将打印出平均QPS和峰值QPS。
有时,用户可能会对顶级统计数据感兴趣。用户可以指定
-print_top_k_access
顶部K个访问键,将打印访问号。此外,如果指定了prefix_cut选项,则输出包含总访问计数的前K个访问前缀。 同时,输出平均访问次数最高的前K个前缀。
如果用户有兴趣了解值大小分布(仅适用于Put和Merge),可以指定
-output_value_distribution-value_interval
由于值大小变化很大,用户可能只想知道每个值大小范围中有多少个值。用户可以指定value_interval=x来生成[0,x), [x,2x]……之间的值
如果用户指定,key大小分布将输出到文件
-output_key_distribution
可视化的工作负载
在使用trace_analyzer处理跟踪之后,用户将获得两个输出文件。其中一些文件可用于可视化工作负载,如热图(键的冷热度)、时间序列图(时间轴上键访问的概述)、分析查询的QPS。
在这里,我们使用开源绘图工具GNUPLOT作为例子来生成图形。 关于GNUPLOT的更多细节可以在这里找到(http://gnuplot.info/)。 用户可以直接编写GNUPLOT命令来绘制图形,或者简单地说,用户可以使用下面的shell脚本生成GNUPLOT源文件(在使用脚本之前,确保文件名和一些内容被有效的内容替换)。
绘制已访问key的热图
#!/bin/bash# The query typeops="iterator"# The column family IDcf="9"# The column family name if known, if not, replace it with some prefixcf_name="rev:cf-assoc-deleter-id1-type"form="accessed"# The column number that will be plotteduse="4"# The higher bound of Y-axisy=2# The higher bound of X-axisx=29233echo "set output '${cf_name}-${ops}-${form}-key_heatmap.png'" > plot-${cf_name}-${ops}-${form}-heatmap.gpecho "set term png size 2000,500" >>plot-${cf_name}-${ops}-${form}-heatmap.gpecho "set title 'CF: ${cf_name} ${form} Key Space Heat Map'">>plot-${cf_name}-${ops}-${form}-heatmap.gpecho "set xlabel 'Key Sequence'">>plot-${cf_name}-${ops}-${form}-heatmap.gpecho "set ylabel 'Key access count'">>plot-${cf_name}-${ops}-${form}-heatmap.gpecho "set yrange [0:$y]">>plot-${cf_name}-${ops}-${form}-heatmap.gpecho "set xrange [0:$x]">>plot-${cf_name}-${ops}-${form}-heatmap.gp# If the preifx cut is avialable, it will draw the prefix cutwhile read f1 f2doecho "set arrow from $f1,0 to $f1,$y nohead lc rgb 'red'" >> plot-${cf_name}-${ops}-${form}-heatmap.gpdone < "trace.1532381594728669-${ops}-${cf}-${form}_key_prefix_cut.txt"echo "plot 'trace.1532381594728669-${ops}-${cf}-${form}_key_stats.txt' using ${use} notitle w dots lt 2" >>plot-${cf_name}-${ops}-${form}-heatmap.gpgnuplot plot-${cf_name}-${ops}-${form}-heatmap.gp
绘制已访问key的时间序列映射
#!/bin/bash# The query typeops="iterator"# The higher bound of X-axisx=29233# The column family IDcf="8"# The column family name if known, if not, replace it with some prefixcf_name="rev:cf-assoc-deleter-id1-type"# The type of the output fileform="time_series"# The column number that will be plotteduse="3:2"# The total time of the tracing duration, in secondsy=88000echo "set output '${cf_name}-${ops}-${form}-key_heatmap.png'" > plot-${cf_name}-${ops}-${form}-heatmap.gpecho "set term png size 3000,3000" >>plot-${cf_name}-${ops}-${form}-heatmap.gpecho "set title 'CF: ${cf_name} time series'">>plot-${cf_name}-${ops}-${form}-heatmap.gpecho "set xlabel 'Key Sequence'">>plot-${cf_name}-${ops}-${form}-heatmap.gpecho "set ylabel 'Key access count'">>plot-${cf_name}-${ops}-${form}-heatmap.gpecho "set yrange [0:$y]">>plot-${cf_name}-${ops}-${form}-heatmap.gpecho "set xrange [0:$x]">>plot-${cf_name}-${ops}-${form}-heatmap.gp# If the preifx cut is avialable, it will draw the prefix cutwhile read f1 f2doecho "set arrow from $f1,0 to $f1,$y nohead lc rgb 'red'" >> plot-${cf_name}-${ops}-${form}-heatmap.gpdone < "trace.1532381594728669-${ops}-${cf}-accessed_key_prefix_cut.txt"echo "plot 'trace.1532381594728669-${ops}-${cf}-${form}.txt' using ${use} notitle w dots lt 2" >>plot-${cf_name}-${ops}-${form}-heatmap.gpgnuplot plot-${cf_name}-${ops}-${form}-heatmap.gp
来绘制QPS
#!/bin/bash# The query typeops="iterator"# The higher bound of the QPSy=5# The column family IDcf="9"# The column family name if known, if not, replace it with some prefixcf_name="rev:cf-assoc-deleter-id1-type"# The type of the output fileform="qps_stats"# The column number that will be plotteduse="1"# The total time of the tracing duration, in secondsx=88000echo "set output '${cf_name}-${ops}-${form}-IO_per_second.png'" > plot-${cf_name}-${ops}-${form}-heatmap.gpecho "set term png size 2000,1200" >>plot-${cf_name}-${ops}-${form}-heatmap.gpecho "set title 'CF: ${cf_name} QPS Over Time'">>plot-${cf_name}-${ops}-${form}-heatmap.gpecho "set xlabel 'Time in second'">>plot-${cf_name}-${ops}-${form}-heatmap.gpecho "set ylabel 'QPS'">>plot-${cf_name}-${ops}-${form}-heatmap.gpecho "set yrange [0:$y]">>plot-${cf_name}-${ops}-${form}-heatmap.gpecho "set xrange [0:$x]">>plot-${cf_name}-${ops}-${form}-heatmap.gpecho "plot 'trace.1532381594728669-${ops}-${cf}-${form}.txt' using ${use} notitle with linespoints" >>plot-${cf_name}-${ops}-${form}-heatmap.gpgnuplot plot-${cf_name}-${ops}-${form}-heatmap.gp
模型的工作负载
我们可以使用不同的工具、脚本和模型来适应工作负载统计数据。通常,用户可以使用密钥访问计数和前缀访问计数的分布来适应模型。 此外,还可以对QPS进行建模。在这里,我们以Matlab为例来拟合密钥访问计数、前缀访问计数和QPS。
用户可以尝试不同的发行版来适应模型。在本例中,我们使用两项指数模型来拟合访问分布,并使用两项sin()来拟合QPS
为了将密钥访问统计信息拟合到模型中并得到统计信息,用户可以运行以下脚本:
% This script is used to fit the key access count distribution% to the two-term exponential distirbution and get the parameters% The input file with surfix: accessed_key_stats.txtfileID = fopen('trace.1531329742187378-get-4-accessed_key_stats.txt');txt = textscan(fileID,'%f %f %f %f');fclose(fileID);% Get the number of keys that has access count xt2=sort(txt{4},'descend');% The number of access count that is used to fit the data% The value depends on the accuracy demond of your model fitting% and the value of count should be always not greater than% the size of t2count=30000;% Generate the access count xx=1:1:count;x=x';% Adjust the matrix and uniformedy=t2(1:count);y=y/(sum(y));figure;% fitting the data to the exp2 modelf=fit(x,y,'exp2')%plot out the original data and fitted line to compareplot(f,x,y);
为了使key访问计数分布符合模型,用户可以运行以下脚本:
% This script is used to fit the key access count distribution% to the two-term exponential distirbution and get the parameters% The input file with surfix: key_count_distribution.txtfileID = fopen('trace-get-9-accessed_key_count_distribution.txt');input = textscan(fileID,'%s %f %s %f');fclose(fileID);% Get the number of keys that has access count xt2=sort(input{4},'descend');% The number of access count that is used to fit the data% The value depends on the accuracy demond of your model fitting% and the value of count should be always not greater than% the size of t2count=100;% Generate the access count xx=1:1:count;x=x';y=t2(1:count);% Adjust the matrix and uniformedy=y/(sum(y));y=y(1:count);x=x(1:count);figure;% fitting the data to the exp2 modelf=fit(x,y,'exp2')%plot out the original data and fitted line to compareplot(f,x,y);
为使模型符合前缀平均访问计数,用户可以运行以下脚本:
% This script is used to fit the prefix average access count distribution% to the two-term exponential distirbution and get the parameters% The input file with surfix: accessed_key_prefix_cut.txtfileID = fopen('trace-get-4-accessed_key_prefix_cut.txt');txt = textscan(fileID,'%f %f %f %f %s');fclose(fileID);% The per key access (average) of each prefix, sortedt2=sort(txt{4},'descend');% The number of access count that is used to fit the data% The value depends on the accuracy demond of your model fitting% and the value of count should be always not greater than% the size of t2count=1000;% Generate the access count xx=1:1:count;x=x';% Adjust the matrix and uniformedy=t2(0:count);y=y/(sum(y));x=x(1:count);% fitting the data to the exp2 modelfigure;f=fit(x,y,'exp2')%plot out the original data and fitted line to compareplot(f,x,y);
为了使QPS适合模型,用户可以运行以下脚本:
% This script is used to fit the qps of the one query in one of the column% family to the sin'x' model. 'x' can be 1 to 10. With the higher value% of the 'x', you can get more accurate fitting of the qps. However,% the model will be more complex and some times will be overfitted.% The suggestion is to use sin1 or sin2% The input file shoud with surfix: qps_stats.txtfileID = fopen('trace-get-4-io_stats.txt');txt = textscan(fileID,'%f');fclose(fileID);t1=txt{1};% The input is the queries per second. If you directly use the qps% you may got a high value of noise. Here, 'n' is the number of qps% that you want to combined to one average value, such that you can% reduce it to queries per n*seconds.n=10;s1 = size(t1, 1);M = s1 - mod(s1, n);t2 = reshape(t1(1:M), n, []);y = transpose(sum(t2, 1) / n);% Up to this point, you need to move the data down to the x-axis,% the offset is the ave. So the model will be% s(x) = a1*sin(b1*x+c1) + a2*sin(b2*x+c2) + aveave = mean(y);y=y-ave;% Adjust the matrixcount = size(y,1);x=1:1:count;x=x';% Fit the model to 'sin2' in this example and draw the point and% fitted line to comparefigure;s = fit(x,y,'sin2')plot(s,x,y);
用户可以使用该模型进行进一步分析,或者使用它生成合成工作负载。
基于模型的综合工作负载生成
在前一节中,用户可以使用Matlab的拟合函数将跟踪的工作负载拟合到不同的模型中,这样我们就可以使用一组参数和函数来对工作负载进行概要分析。 我们主要关注四个变量来分析工作负载: 1)值的大小; 2)KV-Pair访问; 3)QPS; 4)迭代器扫描长度。
根据我们目前的研究,值大小和迭代器扫描长度服从Generalized Pareto Distribution(广义帕累托分布)。
概率密度函数是: f(x) = (1/sigma)(1+k(x-theta)\sigma)^(-1-1/k).
KV-pair访问遵循power-law(幂律),其中约80%的kv对访问计数小于4。我们根据访问计数按降序对键进行排序,并将它们匹配到模型中。两项功率模型最适合kv对接入分配。
概率密度函数是:f(x) = ax^b+c 正弦函数最适合QPS。F(x) = Asin(Bx + C) + D.
下面是我们从Facebook社交图中收集的工作负载中得到的参数的一个例子:
1.直大小: sigma = 226.409, k = 0.923$, theta = 0 2.KV-pair访问: a = 0.001636, b = -0.7094 , and c = 3.21710^-9 3.QPS: $A = 147.9, B = 8.310^-5, C = -1.734, D = 1064.2 4.迭代器扫描长度: sigma = 1.747, k = 0.0819, theta = 0
我们在db_bench中开发了一个名为”mixgraph”的基准测试,它可以使用四组参数生成合成工作负载。 工作负载在统计上与原来的工作负载相似。注意,只有适合用于这四个变量的模型的工作负载才能在mixgraph中使用。 例如,如果值大小遵循功率分布而不是广义的Pareto分布,那么我们就不能使用mixgraph来生成工作负载。
要启用”mixgraph”基准测试,用户需要指定:
./db_bench —benchmarks="mixgraph"
要设置值大小分布的参数(仅广义Pareto分布),用户需要指定:
-value_k=<> -value_sigma=<> -value_theta=<>
若要设置KV-Pair接入分配(仅限功率分配,C==0)的参数,需指定:
-key_dist_a=<> -key_dist_b=<>
要设置QPS(sine)的参数,用户需要指定:
-sine_a=<> -sine_b=<> -sine_c=<> -sine_d=<> -sine_mix_rate_interval_milliseconds=<>'
混合速率是用来设置我们应该如何根据分布来校正速率的时间间隔,它越小,就越适合。
要设置迭代器扫描长度分布的参数(仅广义Pareto分布),用户需要指定:
-iter_k=<> -iter_sigma=<> -iter_theta=<>
用户需要指定Get、Put和Seek之间的查询比率。这样我们就可以生成与社交图工作负载类似的混合工作负载(即所谓的混合图)
-mix_get_ratio=<> -mix_put_ratio=<> -mix_seek_ratio=<>
最后,用户需要指定他们想执行多少查询:
-reads=<>
当前测试数据库中总的kv对是多少
-num=<>
num和前面提到的发行版一起决定了查询。
下面是一个例子,可以直接用于生成数据库中的工作负载,其中包含5000000个kv对和1000000个查询:
./db_bench --benchmarks="mixgraph" -value_k=0.1033 -value_sigma=39 -key_dist_a=0.002312 -key_dist_b=0.3467 -sine_mix_rate_interval_milliseconds=500 -sine_a=350 -sine_b=0.0105 -sine_d=2300 -iter_k=2.517 -iter_sigma=14.236 -mix_get_ratio=0.806 -mix_put_ratio=0.159 -mix_seek_ratio=0.035 -reads=1000000 -num=5000000
