anchor.diff:      ok.
   
   
  -perform_parallel_appends(Worker, LogId, AppendSize, ProcRepeats, ProcNum) ->
  -    Self = self(),
  -    Stopwatch = stopwatch:start(),
  -    Pids = [spawn_link(Worker, fun() ->
  -        perform_appends(Worker, LogId, AppendSize, ProcRepeats),
  -        Self ! {self(), ok}
  -    end) || _ <- lists:seq(1, ProcNum)],
  -    Times = [receive {Pid, ok} -> stopwatch:read_micros(Stopwatch) end || Pid <- Pids],
  -    lists:max(Times).
  -
  -
  -perform_appends(Worker, LogId, AppendSize, Repeats) ->
  -    AppendLog = str_utils:rand_hex(AppendSize),
  -    lists:foreach(fun(_) ->
  -        ?assertMatch(ok, rpc:call(Worker, infinite_log, append, [LogId, AppendLog]), ?ATTEMPTS)
  -    end, lists:seq(1, Repeats)).
  -
  -
  -perform_lists(Worker, LogId, ListOpts, AccessMode, Repeats) ->
  -    lists:foreach(fun(_) ->
  -        ?assertMatch({ok, _}, rpc:call(Worker, infinite_log, list, [LogId, ListOpts, AccessMode]), ?ATTEMPTS)
  -    end, lists:seq(1, Repeats)).
  -
  -
  -perform_parallel_lists(Worker, LogId, ListOpts, AccessMode, ProcRepeats, ProcNum) ->
  -    Self = self(),
  -    Stopwatch = stopwatch:start(),
  -    Pids = [spawn_link(Worker, fun() ->
  -        perform_lists(Worker, LogId, ListOpts, AccessMode, ProcRepeats),
  -        Self ! {self(), ok}
  -    end) || _ <- lists:seq(1, ProcNum)],
  -    Times = [receive {Pid, ok} -> stopwatch:read_micros(Stopwatch) end || Pid <- Pids],
  -    lists:max(Times).
  +%% @private
  +-spec perform_infinite_log_appends(node(), atom(), binary(), integer(), integer(), integer()) -> [integer()].
  +perform_infinite_log_appends(Worker, Model, LogId, AppendSize, ProcCount, 1) ->
  +    AppendFun = fun(_) ->
  +        AppendLog = str_utils:rand_hex(AppendSize div 2),
  +        Stopwatch = stopwatch:start(),
  +        Start = stopwatch:read_millis(Stopwatch),
  +        ?assertEqual(ok, rpc:call(Worker, Model, infinite_log_append, [LogId, AppendLog])),
  +        Stop = stopwatch:read_millis(Stopwatch),
  +        Stop - Start
  +    end,
  +    lists_utils:pmap(AppendFun, lists:seq(1, ProcCount));
  +perform_infinite_log_appends(Worker, Model, LogId, AppendSize, ProcCount, AppendsPerProcess) ->
  +    lists:foldl(fun(_, TimesListAcc) ->
  +        Times = perform_infinite_log_appends(Worker, Model, LogId, AppendSize, ProcCount, 1),
  +        clock_freezer_mock:simulate_seconds_passing(1),
  +        lists:zipwith(fun(Time, TimeAcc) -> Time + TimeAcc end, Times, TimesListAcc)
  +    end, [0 || _ <- lists:seq(1, ProcCount)], lists:seq(1, AppendsPerProcess)).
  +
  +
  +%% @private
  +-spec perform_infinite_log_listings(node(), atom(), binary(), map(), integer(), integer()) -> [integer()].
  +perform_infinite_log_listings(Worker, Model, LogId, ListOpts, ProcCount, ListingsPerProcess) ->
  +    ListingFun = fun(_) ->
  +        Stopwatch = stopwatch:start(),
  +        Start = stopwatch:read_millis(Stopwatch),
  +        lists:foreach(fun(_) ->
  +            ?assertMatch({ok, _}, rpc:call(Worker, Model, infinite_log_list, [LogId, ListOpts]))
  +        end, lists:seq(1, ListingsPerProcess)),
  +        Stop = stopwatch:read_millis(Stopwatch),
  +        Stop - Start
  +    end,
  +    lists_utils:pmap(ListingFun, lists:seq(1, ProcCount)).
  
positive.code:     ok = test_utils:mock_new(Workers, datastore_model),
      ok = test_utils:mock_new(Workers, datastore),
      init_per_testcase(?DEFAULT_CASE(Case), Config);
  init_per_testcase(_, Config) ->
      [Worker | _] = ?config(cluster_worker_nodes, Config),
      application:load(cluster_worker),
  
negative.code:     {ok, {Diff1, Diff2, Diff3, Diff4, Diff5}}.
  
  
  infinite_log_append_performance_test(Config) ->
      ?PERFORMANCE(Config, [
          {repeats, ?REPEATS},
          {success_rate, ?SUCCESS_RATE},
          {description, "Append to infinite-log performance test case."},
          {parameters, [
              [{name, proc_count_list}, {value, [1, 100, 10000]}, {description, "Processes to be used."}],
              [{name, log_size}, {value, 10}, {description, "Number of bytes for single log entry."}],
              [{name, appends_count}, {value, 10000}, {description, "Total logs append count."}],
              [{name, max_entries_per_node_list}, {value, [50, 100, 200, 300, 400, 500, 1000, 2000, 5000]},
                  {description, "Max entries in node sizes to be tested."}]
          ]},
          {config, [
              {name, pruning_off},
              {parameters, [
                  [{name, size_pruning}, {value, undefined}],
                  [{name, age_pruning}, {value, undefined}]
              ]},
              {description, "Append with no pruning."}
          ]},
          {config, [
              {name, age_pruning_on},
              {parameters, [
                  [{name, size_pruning}, {value, undefined}],
                  [{name, age_pruning}, {value, 500}]
              ]},
              {description, "Append with age pruning."}
          ]},
          {config, [
              {name, size_pruning_on},
              {parameters, [
                  [{name, size_pruning}, {value, 5000}],
                  [{name, age_pruning}, {value, undefined}]
              ]},
              {description, "Append with size pruning."}
          ]},
          {config, [
              {name, size_pruning_on},
              {parameters, [
                  [{name, size_pruning}, {value, 2000}],
                  [{name, age_pruning}, {value, 500}]
              ]},
              {description, "Append with size and age pruning."}
          ]}
      ]).
  
  
  infinite_log_append_performance_test_base(Config) ->
      [Worker | _] = ?config(cluster_worker_nodes, Config),
      Model = ets_only_model,
  
      ProcCountList = ?config(proc_count_list, Config),
      AppendsCount = ?config(appends_count, Config),
      LogSize = ?config(log_size, Config),
  
      MaxEntriesPerNodeList = ?config(max_entries_per_node_list, Config),
      SizePruningThreshold = ?config(size_pruning, Config),
      AgePruningThreshold = ?config(age_pruning, Config),
  
  
      lists:foreach(fun(ProcCount) ->
          lists:foreach(fun(MaxEntriesPerNode) ->
              AppendsPerProcess = AppendsCount div ProcCount,
              LogId = str_utils:rand_hex(10),
              LogOpts = #{
                  max_entries_per_node => MaxEntriesPerNode,
                  size_pruning_threshold => SizePruningThreshold,
                  age_pruning_threshold => AgePruningThreshold
              },
              ?assertMatch(ok, rpc:call(Worker, Model, infinite_log_create, [LogId, LogOpts])),
              AppendTimes = perform_infinite_log_appends(Worker, Model, LogId, LogSize, ProcCount, AppendsPerProcess),
              AvgTime = lists:sum(AppendTimes) / ProcCount,
              ct:pal("Results for infinite log append tests:\n"
              "process num:            ~p~n"
              "process repeats:        ~p~n"
              "append size:            ~p~n"
              "max entries per node:   ~p~n"
              "size pruning threshold: ~p~n"
              "age pruning threshold:  ~p~n"
              "time:                   ~p [ms]~n"
              "efficiency:             ~p [B/s]"
                  , [ProcCount, AppendsPerProcess, LogSize, MaxEntriesPerNode, SizePruningThreshold, AgePruningThreshold,
                      AvgTime, 1000 * AppendsCount * LogSize / AvgTime])
          end, MaxEntriesPerNodeList)
      end, ProcCountList).
  
  
  infinite_log_list_performance_test(Config) ->
      ?PERFORMANCE(Config, [
          {repeats, ?REPEATS},
          {success_rate, ?SUCCESS_RATE},
          {description, "List from infinite-log testcase"},
          {parameters, [
              [{name, listing_direction_list}, {value, [backward_from_newest, forward_from_oldest]}, {description, "Listing directions to be tested."}],
              [{name, listing_start_from_list}, {value, [undefined, #{index => 3000}, #{timestamp => 100}]}, {description, "Starting from options to be tested."}],
              [{name, listing_offset_list}, {value, [0, 5000]}, {description, "Listing offsets to be tested."}],
              [{name, listing_limit_list}, {value, [1, 100, 1000]}, {description, "Listing limits to be tested."}],
              [{name, proc_count_list}, {value, [1, 100, 10000]}, {description, "Processes to be used."}],
              [{name, listings_count}, {value, 10000}, {description, "Total listings count to be performed."}],
              [{name, appends_count}, {value, 10000}, {description, "Total logs append count"}],
              [{name, log_size}, {value, 50}, {description, "Size of each log"}]
          ]},
          {config, [
              {name, small_entries_per_node},
              {parameters, [
                  [{name, max_entries_per_node}, {value, 10}],
                  [{name, size_pruning_threshold}, {value, undefined}],
                  [{name, age_pruning_threshold}, {value, undefined}]
              ]},
              {description, "Small entries per node"}
          ]},
          {config, [
              {name, medium_entries_per_node},
              {parameters, [
                  [{name, max_entries_per_node}, {value, 400}],
                  [{name, size_pruning_threshold}, {value, undefined}],
                  [{name, age_pruning_threshold}, {value, undefined}]
              ]},
              {description, "Medium entries per node"}
          ]},
          {config, [
              {name, many_entries_per_node},
              {parameters, [
                  [{name, max_entries_per_node}, {value, 1000}],
                  [{name, size_pruning_threshold}, {value, undefined}],
                  [{name, age_pruning_threshold}, {value, undefined}]
              ]},
              {description, "Many entries per node"}
          ]},
          {config, [
              {name, small_entries_per_node_size_pruning_on},
              {parameters, [
                  [{name, max_entries_per_node}, {value, 10}],
                  [{name, size_pruning_threshold}, {value, 1000}],
                  [{name, age_pruning_threshold}, {value, undefined}]
              ]},
              {description, "Small entries per node, size pruning on"}
          ]},
          {config, [
              {name, medium_entries_per_node_size_pruning_on},
              {parameters, [
                  [{name, max_entries_per_node}, {value, 400}],
                  [{name, size_pruning_threshold}, {value, 1000}],
                  [{name, age_pruning_threshold}, {value, undefined}]
              ]},
              {description, "Medium entries per node, size pruning on"}
          ]},
          {config, [
              {name, many_entries_per_node_size_pruning_on},
              {parameters, [
                  [{name, max_entries_per_node}, {value, 1000}],
                  [{name, size_pruning_threshold}, {value, 1000}],
                  [{name, age_pruning_threshold}, {value, undefined}]
              ]},
              {description, "Many entries per node, size pruning on"}
          ]},
          {config, [
              {name, small_entries_per_node_age_pruning_on},
              {parameters, [
                  [{name, max_entries_per_node}, {value, 10}],
                  [{name, size_pruning_threshold}, {value, undefined}],
                  [{name, age_pruning_threshold}, {value, 100}]
              ]},
              {description, "Small entries per node, age pruning on"}
          ]},
          {config, [
              {name, medium_entries_per_node_size_age_on},
              {parameters, [
                  [{name, max_entries_per_node}, {value, 400}],
                  [{name, size_pruning_threshold}, {value, undefined}],
                  [{name, age_pruning_threshold}, {value, 100}]
              ]},
              {description, "Medium entries per node, age pruning on"}
          ]},
          {config, [
              {name, many_entries_per_node_age_pruning_on},
              {parameters, [
                  [{name, max_entries_per_node}, {value, 1000}],
                  [{name, size_pruning_threshold}, {value, undefined}],
                  [{name, age_pruning_threshold}, {value, 100}]
              ]},
              {description, "Many entries per node, age pruning on"}
          ]},
          {config, [
              {name, small_entries_per_node_both_pruning_on},
              {parameters, [
                  [{name, max_entries_per_node}, {value, 10}],
                  [{name, size_pruning_threshold}, {value, 1000}],
                  [{name, age_pruning_threshold}, {value, 100}]
              ]},
              {description, "Small entries per node, both pruning on"}
          ]},
          {config, [
              {name, medium_entries_per_node_both_age_on},
              {parameters, [
                  [{name, max_entries_per_node}, {value, 400}],
                  [{name, size_pruning_threshold}, {value, 1000}],
                  [{name, age_pruning_threshold}, {value, 100}]
              ]},
              {description, "Medium entries per node, both pruning on"}
          ]},
          {config, [
              {name, many_entries_per_node_both_pruning_on},
              {parameters, [
                  [{name, max_entries_per_node}, {value, 1000}],
                  [{name, size_pruning_threshold}, {value, 1000}],
                  [{name, age_pruning_threshold}, {value, 100}]
              ]},
              {description, "Many entries per node, both pruning on"}
          ]}
      ]).
  
  
  infinite_log_list_performance_test_base(Config) ->
      [Worker | _] = ?config(cluster_worker_nodes, Config),
      Model = ets_only_model,
  
      ProcCountList = ?config(proc_count_list, Config),
      ListingsCount = ?config(listings_count, Config),
  
      % Log options
      LogId = str_utils:rand_hex(10),
      LogSize = ?config(log_size, Config),
      AppendsCount = ?config(appends_count, Config),
      MaxEntriesPerNode = ?config(max_entries_per_node, Config),
      SizePruningThreshold = ?config(size_pruning_threshold, Config),
      AgePruningThreshold = ?config(age_pruning_threshold, Config),
      LogOpts = #{
          max_entries_per_node => MaxEntriesPerNode,
          size_pruning_threshold => SizePruningThreshold,
          age_pruning_threshold => AgePruningThreshold
      },
  
      % Listing options
      ListingDirectionList = ?config(listing_direction_list, Config),
      ListingStartFromList = ?config(listing_start_from_list, Config),
      ListingOffsetList = ?config(listing_offset_list, Config),
      ListingLimitList = ?config(listing_limit_list, Config),
  
      AppendProcessesCount = 100,
      ?assertMatch(ok, rpc:call(Worker, Model, infinite_log_create, [LogId, LogOpts])),
      perform_infinite_log_appends(Worker, Model, LogId, LogSize, AppendProcessesCount, AppendsCount div AppendProcessesCount),
  
      lists:foreach(fun(ProcCount) ->
          lists:foreach(fun(ListingDirection) ->
              lists:foreach(fun(ListingStartFrom) ->
                  lists:foreach(fun(ListingOffset) ->
                      lists:foreach(fun(ListingLimit) ->
                          ListingsPerProcess = ListingsCount div ProcCount,
  
                          ListingStartFromParsed = case is_map(ListingStartFrom) of
                              true -> {hd(maps:keys(ListingStartFrom)), hd(maps:values(ListingStartFrom))};
                              false -> undefined
                          end,
  
                          ListOpts = #{
                              direction => ListingDirection,
                              start_from => ListingStartFromParsed,
                              offset => ListingOffset,
                              limit => ListingLimit
                          },
                          ListingTimes = perform_infinite_log_listings(Worker, Model, LogId, ListOpts, ProcCount, ListingsPerProcess),
                          AvgTime = lists:sum(ListingTimes) / ProcCount,
  
                          ct:pal("Results for infinite log list tests:\n"
                          "process num:            ~p~n"
                          "process repeats:        ~p~n"
                          "append size:            ~p~n"
                          "max entries per node:   ~p~n"
                          "size pruning threshold: ~p~n"
                          "age pruning threshold:  ~p~n"
                          "list direction:         ~p~n"
                          "list starting from:     ~p~n"
                          "offset:                 ~p~n"
                          "limit:                  ~p~n"
                          "time:                   ~p [ms]~n"
                          "efficiency:             ~p [listings/s]"
                              , [ProcCount, ListingsPerProcess, LogSize, MaxEntriesPerNode, SizePruningThreshold, AgePruningThreshold,
                                  ListingDirection, ListingStartFromParsed, ListingOffset, ListingLimit,
                                  AvgTime, 1000 * ProcCount * ListingsPerProcess / AvgTime])
                      end, ListingLimitList)
                  end, ListingOffsetList)
              end, ListingStartFromList)
          end, ListingDirectionList)
      end, ProcCountList).
  
  
  %%%===================================================================
  %%% Init/teardown functions
  %%%===================================================================
  
  
  init_per_suite(Config) ->
      datastore_test_utils:init_suite(?TEST_MODELS, Config,
  