1#!/usr/bin/env bash
2# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3# REQUIRE: db_bench binary exists in the current directory
4
5if [ $# -ne 1 ]; then
6  echo -n "./benchmark.sh [bulkload/fillseq/overwrite/filluniquerandom/"
7  echo    "readrandom/readwhilewriting/readwhilemerging/updaterandom/"
8  echo    "mergerandom/randomtransaction/compact]"
9  exit 0
10fi
11
12# Make it easier to run only the compaction test. Getting valid data requires
13# a number of iterations and having an ability to run the test separately from
14# rest of the benchmarks helps.
15if [ "$COMPACTION_TEST" == "1" -a "$1" != "universal_compaction" ]; then
16  echo "Skipping $1 because it's not a compaction test."
17  exit 0
18fi
19
20# size constants
21K=1024
22M=$((1024 * K))
23G=$((1024 * M))
24T=$((1024 * G))
25
26if [ -z $DB_DIR ]; then
27  echo "DB_DIR is not defined"
28  exit 0
29fi
30
31if [ -z $WAL_DIR ]; then
32  echo "WAL_DIR is not defined"
33  exit 0
34fi
35
36output_dir=${OUTPUT_DIR:-/tmp/}
37if [ ! -d $output_dir ]; then
38  mkdir -p $output_dir
39fi
40
41# all multithreaded tests run with sync=1 unless
42# $DB_BENCH_NO_SYNC is defined
43syncval="1"
44if [ ! -z $DB_BENCH_NO_SYNC ]; then
45  echo "Turning sync off for all multithreaded tests"
46  syncval="0";
47fi
48
49num_threads=${NUM_THREADS:-64}
50mb_written_per_sec=${MB_WRITE_PER_SEC:-0}
51# Only for tests that do range scans
52num_nexts_per_seek=${NUM_NEXTS_PER_SEEK:-10}
53cache_size=${CACHE_SIZE:-$((17179869184))}
54compression_max_dict_bytes=${COMPRESSION_MAX_DICT_BYTES:-0}
55compression_type=${COMPRESSION_TYPE:-zstd}
56duration=${DURATION:-0}
57
58num_keys=${NUM_KEYS:-8000000000}
59key_size=${KEY_SIZE:-20}
60value_size=${VALUE_SIZE:-400}
61block_size=${BLOCK_SIZE:-8192}
62
63const_params="
64  --db=$DB_DIR \
65  --wal_dir=$WAL_DIR \
66  \
67  --num=$num_keys \
68  --num_levels=6 \
69  --key_size=$key_size \
70  --value_size=$value_size \
71  --block_size=$block_size \
72  --cache_size=$cache_size \
73  --cache_numshardbits=6 \
74  --compression_max_dict_bytes=$compression_max_dict_bytes \
75  --compression_ratio=0.5 \
76  --compression_type=$compression_type \
77  --level_compaction_dynamic_level_bytes=true \
78  --bytes_per_sync=$((8 * M)) \
79  --cache_index_and_filter_blocks=0 \
80  --pin_l0_filter_and_index_blocks_in_cache=1 \
81  --benchmark_write_rate_limit=$(( 1024 * 1024 * $mb_written_per_sec )) \
82  \
83  --hard_rate_limit=3 \
84  --rate_limit_delay_max_milliseconds=1000000 \
85  --write_buffer_size=$((128 * M)) \
86  --target_file_size_base=$((128 * M)) \
87  --max_bytes_for_level_base=$((1 * G)) \
88  \
89  --verify_checksum=1 \
90  --delete_obsolete_files_period_micros=$((60 * M)) \
91  --max_bytes_for_level_multiplier=8 \
92  \
93  --statistics=0 \
94  --stats_per_interval=1 \
95  --stats_interval_seconds=60 \
96  --histogram=1 \
97  \
98  --memtablerep=skip_list \
99  --bloom_bits=10 \
100  --open_files=-1"
101
102l0_config="
103  --level0_file_num_compaction_trigger=4 \
104  --level0_stop_writes_trigger=20"
105
106if [ $duration -gt 0 ]; then
107  const_params="$const_params --duration=$duration"
108fi
109
110params_w="$const_params \
111          $l0_config \
112          --max_background_compactions=16 \
113          --max_write_buffer_number=8 \
114          --max_background_flushes=7"
115
116params_bulkload="$const_params \
117                 --max_background_compactions=16 \
118                 --max_write_buffer_number=8 \
119                 --allow_concurrent_memtable_write=false \
120                 --max_background_flushes=7 \
121                 --level0_file_num_compaction_trigger=$((10 * M)) \
122                 --level0_slowdown_writes_trigger=$((10 * M)) \
123                 --level0_stop_writes_trigger=$((10 * M))"
124
125params_fillseq="$params_w \
126		--allow_concurrent_memtable_write=false"
127#
128# Tune values for level and universal compaction.
129# For universal compaction, these level0_* options mean total sorted of runs in
130# LSM. In level-based compaction, it means number of L0 files.
131#
132params_level_compact="$const_params \
133                --max_background_flushes=4 \
134                --max_write_buffer_number=4 \
135                --level0_file_num_compaction_trigger=4 \
136                --level0_slowdown_writes_trigger=16 \
137                --level0_stop_writes_trigger=20"
138
139params_univ_compact="$const_params \
140                --max_background_flushes=4 \
141                --max_write_buffer_number=4 \
142                --level0_file_num_compaction_trigger=8 \
143                --level0_slowdown_writes_trigger=16 \
144                --level0_stop_writes_trigger=20"
145
146function summarize_result {
147  test_out=$1
148  test_name=$2
149  bench_name=$3
150
151  # Note that this function assumes that the benchmark executes long enough so
152  # that "Compaction Stats" is written to stdout at least once. If it won't
153  # happen then empty output from grep when searching for "Sum" will cause
154  # syntax errors.
155  uptime=$( grep ^Uptime\(secs $test_out | tail -1 | awk '{ printf "%.0f", $2 }' )
156  stall_time=$( grep "^Cumulative stall" $test_out | tail -1  | awk '{  print $3 }' )
157  stall_pct=$( grep "^Cumulative stall" $test_out| tail -1  | awk '{  print $5 }' )
158  ops_sec=$( grep ^${bench_name} $test_out | awk '{ print $5 }' )
159  mb_sec=$( grep ^${bench_name} $test_out | awk '{ print $7 }' )
160  lo_wgb=$( grep "^  L0" $test_out | tail -1 | awk '{ print $9 }' )
161  sum_wgb=$( grep "^ Sum" $test_out | tail -1 | awk '{ print $9 }' )
162  sum_size=$( grep "^ Sum" $test_out | tail -1 | awk '{ printf "%.1f", $3 / 1024.0 }' )
163  wamp=$( echo "scale=1; $sum_wgb / $lo_wgb" | bc )
164  wmb_ps=$( echo "scale=1; ( $sum_wgb * 1024.0 ) / $uptime" | bc )
165  usecs_op=$( grep ^${bench_name} $test_out | awk '{ printf "%.1f", $3 }' )
166  p50=$( grep "^Percentiles:" $test_out | tail -1 | awk '{ printf "%.1f", $3 }' )
167  p75=$( grep "^Percentiles:" $test_out | tail -1 | awk '{ printf "%.1f", $5 }' )
168  p99=$( grep "^Percentiles:" $test_out | tail -1 | awk '{ printf "%.0f", $7 }' )
169  p999=$( grep "^Percentiles:" $test_out | tail -1 | awk '{ printf "%.0f", $9 }' )
170  p9999=$( grep "^Percentiles:" $test_out | tail -1 | awk '{ printf "%.0f", $11 }' )
171  echo -e "$ops_sec\t$mb_sec\t$sum_size\t$lo_wgb\t$sum_wgb\t$wamp\t$wmb_ps\t$usecs_op\t$p50\t$p75\t$p99\t$p999\t$p9999\t$uptime\t$stall_time\t$stall_pct\t$test_name" \
172    >> $output_dir/report.txt
173}
174
175function run_bulkload {
176  # This runs with a vector memtable and the WAL disabled to load faster. It is still crash safe and the
177  # client can discover where to restart a load after a crash. I think this is a good way to load.
178  echo "Bulk loading $num_keys random keys"
179  cmd="./db_bench --benchmarks=fillrandom \
180       --use_existing_db=0 \
181       --disable_auto_compactions=1 \
182       --sync=0 \
183       $params_bulkload \
184       --threads=1 \
185       --memtablerep=vector \
186       --allow_concurrent_memtable_write=false \
187       --disable_wal=1 \
188       --seed=$( date +%s ) \
189       2>&1 | tee -a $output_dir/benchmark_bulkload_fillrandom.log"
190  echo $cmd | tee $output_dir/benchmark_bulkload_fillrandom.log
191  eval $cmd
192  summarize_result $output_dir/benchmark_bulkload_fillrandom.log bulkload fillrandom
193  echo "Compacting..."
194  cmd="./db_bench --benchmarks=compact \
195       --use_existing_db=1 \
196       --disable_auto_compactions=1 \
197       --sync=0 \
198       $params_w \
199       --threads=1 \
200       2>&1 | tee -a $output_dir/benchmark_bulkload_compact.log"
201  echo $cmd | tee $output_dir/benchmark_bulkload_compact.log
202  eval $cmd
203}
204
205#
206# Parameter description:
207#
208# $1 - 1 if I/O statistics should be collected.
209# $2 - compaction type to use (level=0, universal=1).
210# $3 - number of subcompactions.
211# $4 - number of maximum background compactions.
212#
213function run_manual_compaction_worker {
214  # This runs with a vector memtable and the WAL disabled to load faster.
215  # It is still crash safe and the client can discover where to restart a
216  # load after a crash. I think this is a good way to load.
217  echo "Bulk loading $num_keys random keys for manual compaction."
218
219  fillrandom_output_file=$output_dir/benchmark_man_compact_fillrandom_$3.log
220  man_compact_output_log=$output_dir/benchmark_man_compact_$3.log
221
222  if [ "$2" == "1" ]; then
223    extra_params=$params_univ_compact
224  else
225    extra_params=$params_level_compact
226  fi
227
228  # Make sure that fillrandom uses the same compaction options as compact.
229  cmd="./db_bench --benchmarks=fillrandom \
230       --use_existing_db=0 \
231       --disable_auto_compactions=0 \
232       --sync=0 \
233       $extra_params \
234       --threads=$num_threads \
235       --compaction_measure_io_stats=$1 \
236       --compaction_style=$2 \
237       --subcompactions=$3 \
238       --memtablerep=vector \
239       --allow_concurrent_memtable_write=false \
240       --disable_wal=1 \
241       --max_background_compactions=$4 \
242       --seed=$( date +%s ) \
243       2>&1 | tee -a $fillrandom_output_file"
244
245  echo $cmd | tee $fillrandom_output_file
246  eval $cmd
247
248  summarize_result $fillrandom_output_file man_compact_fillrandom_$3 fillrandom
249
250  echo "Compacting with $3 subcompactions specified ..."
251
252  # This is the part we're really interested in. Given that compact benchmark
253  # doesn't output regular statistics then we'll just use the time command to
254  # measure how long this step takes.
255  cmd="{ \
256       time ./db_bench --benchmarks=compact \
257       --use_existing_db=1 \
258       --disable_auto_compactions=0 \
259       --sync=0 \
260       $extra_params \
261       --threads=$num_threads \
262       --compaction_measure_io_stats=$1 \
263       --compaction_style=$2 \
264       --subcompactions=$3 \
265       --max_background_compactions=$4 \
266       ;}
267       2>&1 | tee -a $man_compact_output_log"
268
269  echo $cmd | tee $man_compact_output_log
270  eval $cmd
271
272  # Can't use summarize_result here. One way to analyze the results is to run
273  # "grep real" on the resulting log files.
274}
275
276function run_univ_compaction {
277  # Always ask for I/O statistics to be measured.
278  io_stats=1
279
280  # Values: kCompactionStyleLevel = 0x0, kCompactionStyleUniversal = 0x1.
281  compaction_style=1
282
283  # Define a set of benchmarks.
284  subcompactions=(1 2 4 8 16)
285  max_background_compactions=(16 16 8 4 2)
286
287  i=0
288  total=${#subcompactions[@]}
289
290  # Execute a set of benchmarks to cover variety of scenarios.
291  while [ "$i" -lt "$total" ]
292  do
293    run_manual_compaction_worker $io_stats $compaction_style ${subcompactions[$i]} \
294      ${max_background_compactions[$i]}
295    ((i++))
296  done
297}
298
299function run_fillseq {
300  # This runs with a vector memtable. WAL can be either disabled or enabled
301  # depending on the input parameter (1 for disabled, 0 for enabled). The main
302  # benefit behind disabling WAL is to make loading faster. It is still crash
303  # safe and the client can discover where to restart a load after a crash. I
304  # think this is a good way to load.
305
306  # Make sure that we'll have unique names for all the files so that data won't
307  # be overwritten.
308  if [ $1 == 1 ]; then
309    log_file_name=$output_dir/benchmark_fillseq.wal_disabled.v${value_size}.log
310    test_name=fillseq.wal_disabled.v${value_size}
311  else
312    log_file_name=$output_dir/benchmark_fillseq.wal_enabled.v${value_size}.log
313    test_name=fillseq.wal_enabled.v${value_size}
314  fi
315
316  echo "Loading $num_keys keys sequentially"
317  cmd="./db_bench --benchmarks=fillseq \
318       --use_existing_db=0 \
319       --sync=0 \
320       $params_fillseq \
321       --min_level_to_compress=0 \
322       --threads=1 \
323       --memtablerep=vector \
324       --allow_concurrent_memtable_write=false \
325       --disable_wal=$1 \
326       --seed=$( date +%s ) \
327       2>&1 | tee -a $log_file_name"
328  echo $cmd | tee $log_file_name
329  eval $cmd
330
331  # The constant "fillseq" which we pass to db_bench is the benchmark name.
332  summarize_result $log_file_name $test_name fillseq
333}
334
335function run_change {
336  operation=$1
337  echo "Do $num_keys random $operation"
338  out_name="benchmark_${operation}.t${num_threads}.s${syncval}.log"
339  cmd="./db_bench --benchmarks=$operation \
340       --use_existing_db=1 \
341       --sync=$syncval \
342       $params_w \
343       --threads=$num_threads \
344       --merge_operator=\"put\" \
345       --seed=$( date +%s ) \
346       2>&1 | tee -a $output_dir/${out_name}"
347  echo $cmd | tee $output_dir/${out_name}
348  eval $cmd
349  summarize_result $output_dir/${out_name} ${operation}.t${num_threads}.s${syncval} $operation
350}
351
352function run_filluniquerandom {
353  echo "Loading $num_keys unique keys randomly"
354  cmd="./db_bench --benchmarks=filluniquerandom \
355       --use_existing_db=0 \
356       --sync=0 \
357       $params_w \
358       --threads=1 \
359       --seed=$( date +%s ) \
360       2>&1 | tee -a $output_dir/benchmark_filluniquerandom.log"
361  echo $cmd | tee $output_dir/benchmark_filluniquerandom.log
362  eval $cmd
363  summarize_result $output_dir/benchmark_filluniquerandom.log filluniquerandom filluniquerandom
364}
365
366function run_readrandom {
367  echo "Reading $num_keys random keys"
368  out_name="benchmark_readrandom.t${num_threads}.log"
369  cmd="./db_bench --benchmarks=readrandom \
370       --use_existing_db=1 \
371       $params_w \
372       --threads=$num_threads \
373       --seed=$( date +%s ) \
374       2>&1 | tee -a $output_dir/${out_name}"
375  echo $cmd | tee $output_dir/${out_name}
376  eval $cmd
377  summarize_result $output_dir/${out_name} readrandom.t${num_threads} readrandom
378}
379
380function run_readwhile {
381  operation=$1
382  echo "Reading $num_keys random keys while $operation"
383  out_name="benchmark_readwhile${operation}.t${num_threads}.log"
384  cmd="./db_bench --benchmarks=readwhile${operation} \
385       --use_existing_db=1 \
386       --sync=$syncval \
387       $params_w \
388       --threads=$num_threads \
389       --merge_operator=\"put\" \
390       --seed=$( date +%s ) \
391       2>&1 | tee -a $output_dir/${out_name}"
392  echo $cmd | tee $output_dir/${out_name}
393  eval $cmd
394  summarize_result $output_dir/${out_name} readwhile${operation}.t${num_threads} readwhile${operation}
395}
396
397function run_rangewhile {
398  operation=$1
399  full_name=$2
400  reverse_arg=$3
401  out_name="benchmark_${full_name}.t${num_threads}.log"
402  echo "Range scan $num_keys random keys while ${operation} for reverse_iter=${reverse_arg}"
403  cmd="./db_bench --benchmarks=seekrandomwhile${operation} \
404       --use_existing_db=1 \
405       --sync=$syncval \
406       $params_w \
407       --threads=$num_threads \
408       --merge_operator=\"put\" \
409       --seek_nexts=$num_nexts_per_seek \
410       --reverse_iterator=$reverse_arg \
411       --seed=$( date +%s ) \
412       2>&1 | tee -a $output_dir/${out_name}"
413  echo $cmd | tee $output_dir/${out_name}
414  eval $cmd
415  summarize_result $output_dir/${out_name} ${full_name}.t${num_threads} seekrandomwhile${operation}
416}
417
418function run_range {
419  full_name=$1
420  reverse_arg=$2
421  out_name="benchmark_${full_name}.t${num_threads}.log"
422  echo "Range scan $num_keys random keys for reverse_iter=${reverse_arg}"
423  cmd="./db_bench --benchmarks=seekrandom \
424       --use_existing_db=1 \
425       $params_w \
426       --threads=$num_threads \
427       --seek_nexts=$num_nexts_per_seek \
428       --reverse_iterator=$reverse_arg \
429       --seed=$( date +%s ) \
430       2>&1 | tee -a $output_dir/${out_name}"
431  echo $cmd | tee $output_dir/${out_name}
432  eval $cmd
433  summarize_result $output_dir/${out_name} ${full_name}.t${num_threads} seekrandom
434}
435
436function run_randomtransaction {
437  echo "..."
438  cmd="./db_bench $params_r --benchmarks=randomtransaction \
439       --num=$num_keys \
440       --transaction_db \
441       --threads=5 \
442       --transaction_sets=5 \
443       2>&1 | tee $output_dir/benchmark_randomtransaction.log"
444  echo $cmd | tee $output_dir/benchmark_rangescanwhilewriting.log
445  eval $cmd
446}
447
448function now() {
449  echo `date +"%s"`
450}
451
452report="$output_dir/report.txt"
453schedule="$output_dir/schedule.txt"
454
455echo "===== Benchmark ====="
456
457# Run!!!
458IFS=',' read -a jobs <<< $1
459# shellcheck disable=SC2068
460for job in ${jobs[@]}; do
461
462  if [ $job != debug ]; then
463    echo "Start $job at `date`" | tee -a $schedule
464  fi
465
466  start=$(now)
467  if [ $job = bulkload ]; then
468    run_bulkload
469  elif [ $job = fillseq_disable_wal ]; then
470    run_fillseq 1
471  elif [ $job = fillseq_enable_wal ]; then
472    run_fillseq 0
473  elif [ $job = overwrite ]; then
474    syncval="0"
475    params_w="$params_w \
476	--writes=125000000 \
477	--subcompactions=4 \
478	--soft_pending_compaction_bytes_limit=$((1 * T)) \
479	--hard_pending_compaction_bytes_limit=$((4 * T)) "
480    run_change overwrite
481  elif [ $job = updaterandom ]; then
482    run_change updaterandom
483  elif [ $job = mergerandom ]; then
484    run_change mergerandom
485  elif [ $job = filluniquerandom ]; then
486    run_filluniquerandom
487  elif [ $job = readrandom ]; then
488    run_readrandom
489  elif [ $job = fwdrange ]; then
490    run_range $job false
491  elif [ $job = revrange ]; then
492    run_range $job true
493  elif [ $job = readwhilewriting ]; then
494    run_readwhile writing
495  elif [ $job = readwhilemerging ]; then
496    run_readwhile merging
497  elif [ $job = fwdrangewhilewriting ]; then
498    run_rangewhile writing $job false
499  elif [ $job = revrangewhilewriting ]; then
500    run_rangewhile writing $job true
501  elif [ $job = fwdrangewhilemerging ]; then
502    run_rangewhile merging $job false
503  elif [ $job = revrangewhilemerging ]; then
504    run_rangewhile merging $job true
505  elif [ $job = randomtransaction ]; then
506    run_randomtransaction
507  elif [ $job = universal_compaction ]; then
508    run_univ_compaction
509  elif [ $job = debug ]; then
510    num_keys=1000; # debug
511    echo "Setting num_keys to $num_keys"
512  else
513    echo "unknown job $job"
514    exit
515  fi
516  end=$(now)
517
518  if [ $job != debug ]; then
519    echo "Complete $job in $((end-start)) seconds" | tee -a $schedule
520  fi
521
522  echo -e "ops/sec\tmb/sec\tSize-GB\tL0_GB\tSum_GB\tW-Amp\tW-MB/s\tusec/op\tp50\tp75\tp99\tp99.9\tp99.99\tUptime\tStall-time\tStall%\tTest"
523  tail -1 $output_dir/report.txt
524
525done
526