1
0
Fork 0
arangodb/tests/js/server/aql/aql-profiler-cluster.js

256 lines
11 KiB
JavaScript

/*jshint globalstrict:true, strict:true, esnext: true */
"use strict";
////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2019 ArangoDB GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
///
/// @author Tobias Gödderz
////////////////////////////////////////////////////////////////////////////////
// contains common code for aql-profiler* tests
const profHelper = require("@arangodb/aql-profiler-test-helper");
const _ = require('lodash');
const {db, aql} = require('@arangodb');
const console = require('console');
const { getResponsibleServers } = global.ArangoClusterInfo;
const internal = require('internal');
const jsunity = require('jsunity');
const assert = jsunity.jsUnity.assertions;
////////////////////////////////////////////////////////////////////////////////
/// @file test suite for AQL tracing/profiling: cluster tests
/// Contains tests for RemoteBlock, SortingGatherBlock, UnsortingGatherBlock
/// The main test suite (including comments) is in aql-profiler.js.
////////////////////////////////////////////////////////////////////////////////
// TODO Add tests for ScatterBlock and DistributeBlock.
function ahuacatlProfilerTestSuite () {
// import some names from profHelper directly into our namespace:
const defaultBatchSize = profHelper.defaultBatchSize;
const defaultTestRowCounts = profHelper.defaultTestRowCounts;
const addIntervals = profHelper.addIntervals;
const { CalculationNode, CollectNode, DistributeNode, EnumerateCollectionNode,
EnumerateListNode, EnumerateViewNode, FilterNode, GatherNode, IndexNode,
InsertNode, LimitNode, NoResultsNode, RemoteNode, RemoveNode, ReplaceNode,
ReturnNode, ScatterNode, ShortestPathNode, SingletonNode, SortNode,
SubqueryNode, TraversalNode, UpdateNode, UpsertNode } = profHelper;
const { CalculationBlock, CountCollectBlock, DistinctCollectBlock,
EnumerateCollectionBlock, EnumerateListBlock, FilterBlock,
HashedCollectBlock, IndexBlock, LimitBlock, NoResultsBlock, RemoteBlock,
ReturnBlock, ShortestPathBlock, SingletonBlock, SortBlock,
SortedCollectBlock, SortingGatherBlock, SubqueryBlock, TraversalBlock,
UnsortingGatherBlock, RemoveBlock, InsertBlock, UpdateBlock, ReplaceBlock,
UpsertBlock, ScatterBlock, DistributeBlock, IResearchViewUnorderedBlock,
IResearchViewBlock, IResearchViewOrderedBlock } = profHelper;
// TODO Test with different shard counts, if only to test both minelement and
// heap SortingGather.
const numberOfShards = 5;
const cn = 'AqlProfilerClusterTestCol';
let col;
// Example documents, grouped by the shard that would be responsible.
let exampleDocumentsByShard;
// helper functions
const optimalNonEmptyBatches = rows => Math.ceil(rows / defaultBatchSize);
const optimalBatches = rows => Math.max(1, optimalNonEmptyBatches(rows));
const mmfilesBatches = (rows) => Math.max(1, optimalNonEmptyBatches(rows) + (rows % defaultBatchSize === 0 ? 1 : 0));
const totalItems = (rowsPerShard) =>
_.sum(_.values(rowsPerShard));
const dbServerBatches = (rowsPerClient, fuzzy = false) => {
fuzzy = fuzzy || db._engine().name === 'mmfiles';
return _.sum(
_.values(rowsPerClient)
.map(fuzzy ? mmfilesBatches : optimalBatches)
);
};
const dbServerBatch = (rows, fuzzy = false) => {
fuzzy = fuzzy || db._engine().name === 'mmfiles';
return (fuzzy ? mmfilesBatches : optimalBatches)(rows);
};
const dbServerOptimalBatches = (rowsPerClient) =>
_.sum(
_.values(rowsPerClient)
.map(optimalBatches)
);
const groupedBatches = (rowsPerClient, fuzzy) => {
const callInfo = {calls: 0, overhead: 0};
for (const [shard, rows] of Object.entries(rowsPerClient)) {
const testHere = rows + callInfo.overhead;
callInfo.calls += dbServerBatch(testHere, fuzzy);
callInfo.overhead = testHere % defaultBatchSize;
}
return callInfo.calls;
};
return {
////////////////////////////////////////////////////////////////////////////////
/// @brief set up
////////////////////////////////////////////////////////////////////////////////
setUp : function () {
},
////////////////////////////////////////////////////////////////////////////////
/// @brief tear down
////////////////////////////////////////////////////////////////////////////////
tearDown : function () {
},
////////////////////////////////////////////////////////////////////////////////
/// @brief set up all
////////////////////////////////////////////////////////////////////////////////
setUpAll : function () {
col = db._create(cn, {numberOfShards});
const maxRowsPerShard = _.max(defaultTestRowCounts);
const shards = col.shards();
assert.assertEqual(numberOfShards, shards.length);
// Create an array of size numberOfShards, each entry an empty array.
exampleDocumentsByShard = _.fromPairs(shards.map(id => [id, []]));
const allShardsFull = () =>
_.values(exampleDocumentsByShard)
.map(elt => elt.length)
.every(n => n >= maxRowsPerShard);
let i = 0;
while (!allShardsFull()) {
const doc = {_key: i.toString(), i};
const shard = col.getResponsibleShard(doc);
if (exampleDocumentsByShard[shard].length < maxRowsPerShard) {
// shard needs more docs
exampleDocumentsByShard[shard].push(doc);
}
++i;
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief tear down all
////////////////////////////////////////////////////////////////////////////////
tearDownAll : function () {
db._drop(cn);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test RemoteBlock and UnsortingGatherBlock
////////////////////////////////////////////////////////////////////////////////
testRemoteAndUnsortingGatherBlock : function () {
const query = `FOR doc IN ${cn} RETURN doc`;
// Number of local getSome calls that do not return WAITING.
// This is at least 1.
// DONE can only be returned when the last shard is asked, so iff the last
// asked shard is empty, there is one more call (the last call returns
// DONE without any results).
// As there is no guaranteed order in which the shards are processed, we
// have to allow a range.
const localCalls = (rowsPerShard) => {
const batches = optimalNonEmptyBatches(_.sum(_.values(rowsPerShard)));
return [
Math.max(1, batches),
Math.max(1, batches+1)
];
};
// If we figure out that we are done depends on randomness.
// In some cases we get the full batch on last shard, in this case the DBServer knows it is done.
// In other cases we get the full batch on an early shard, but 0 documents later, in this case the DBServer does
// not know it is done in advance.
const fuzzyDBServerBatches = rowsPerServer => [
groupedBatches(rowsPerServer, false),
groupedBatches(rowsPerServer, true)
];
const coordinatorBatches = (rowsPerShard) => addIntervals(fuzzyDBServerBatches(rowsPerShard), localCalls(rowsPerShard));
const genNodeList = (rowsPerShard, rowsPerServer) => [
{ type : SingletonBlock, calls : numberOfShards, items : numberOfShards },
{ type : EnumerateCollectionBlock, calls : groupedBatches(rowsPerShard), items : totalItems(rowsPerShard) },
// Twice the number due to WAITING, fuzzy, because the Gather does not know
{ type : RemoteBlock, calls : fuzzyDBServerBatches(rowsPerServer).map(i => i * 2), items : totalItems(rowsPerShard) },
// We get dbServerBatches(rowsPerShard) times WAITING, plus the non-waiting getSome calls.
{ type : UnsortingGatherBlock, calls : coordinatorBatches(rowsPerServer), items : totalItems(rowsPerShard) },
{ type : ReturnBlock, calls : coordinatorBatches(rowsPerServer), items : totalItems(rowsPerShard) }
];
const options = {optimizer: { rules: ["-parallelize-gather"] } };
profHelper.runClusterChecks({col, exampleDocumentsByShard, query, genNodeList, options});
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test RemoteBlock and SortingGatherBlock
////////////////////////////////////////////////////////////////////////////////
testRemoteAndSortingGatherBlock : function () {
const query = `FOR doc IN ${cn} SORT doc.i RETURN doc`;
// Number of local getSome calls that do not return WAITING.
// This is at least 1.
// If the DBServers could lie about HASMORE, this would increase the calls
// here. However, due to the SortBlock, this cannot happen with this plan.
const localCalls = (rowsPerShard) => {
const batches = optimalBatches(_.sum(_.values(rowsPerShard)));
return Math.max(1, batches);
};
const coordinatorBatches = (rowsPerShard) => addIntervals(dbServerOptimalBatches(rowsPerShard), localCalls(rowsPerShard));
const genNodeList = (rowsPerShard, rowsPerServer) => [
{ type : SingletonBlock, calls : numberOfShards, items : numberOfShards },
{ type : EnumerateCollectionBlock, calls : dbServerBatches(rowsPerShard), items : totalItems(rowsPerShard) },
{ type : CalculationBlock, calls : dbServerBatches(rowsPerShard), items : totalItems(rowsPerShard) },
{ type : SortBlock, calls : dbServerOptimalBatches(rowsPerShard), items : totalItems(rowsPerShard) },
// Twice the number due to WAITING
{ type : RemoteBlock, calls : 2 * dbServerOptimalBatches(rowsPerServer), items : totalItems(rowsPerShard) },
// We get dbServerBatches(rowsPerShard) times WAITING, plus the non-waiting getSome calls.
{ type : SortingGatherBlock, calls : coordinatorBatches(rowsPerServer), items : totalItems(rowsPerShard) },
{ type : ReturnBlock, calls : coordinatorBatches(rowsPerServer), items : totalItems(rowsPerShard) }
];
profHelper.runClusterChecks({col, exampleDocumentsByShard, query, genNodeList});
},
};
}
////////////////////////////////////////////////////////////////////////////////
/// @brief executes the test suite
////////////////////////////////////////////////////////////////////////////////
jsunity.run(ahuacatlProfilerTestSuite);
return jsunity.done();