Repository: drill
Updated Branches:
  refs/heads/master 2444271e4 -> 9313a2865


DRILL-5815: Option to set query memory as percent of total

closes #960


Project: http://git-wip-us.apache.org/repos/asf/drill/repo
Commit: http://git-wip-us.apache.org/repos/asf/drill/commit/f781ce1f
Tree: http://git-wip-us.apache.org/repos/asf/drill/tree/f781ce1f
Diff: http://git-wip-us.apache.org/repos/asf/drill/diff/f781ce1f

Branch: refs/heads/master
Commit: f781ce1f54d0d79f2d7014490d4dc422f33b309c
Parents: 2444271
Author: Paul Rogers <prog...@maprtech.com>
Authored: Mon Sep 25 17:04:41 2017 -0700
Committer: Paul Rogers <prog...@maprtech.com>
Committed: Mon Oct 16 12:01:00 2017 -0700

----------------------------------------------------------------------
 .../org/apache/drill/exec/ExecConstants.java    |  33 ++++-
 .../server/options/SystemOptionManager.java     |   1 +
 .../exec/util/MemoryAllocationUtilities.java    | 111 +++++++++++----
 .../src/main/resources/drill-module.conf        | 113 +++++++--------
 .../impl/xsort/TestSortSpillWithException.java  |   2 +
 .../drill/exec/util/TestQueryMemoryAlloc.java   | 137 +++++++++++++++++++
 6 files changed, 315 insertions(+), 82 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/drill/blob/f781ce1f/exec/java-exec/src/main/java/org/apache/drill/exec/ExecConstants.java
----------------------------------------------------------------------
diff --git 
a/exec/java-exec/src/main/java/org/apache/drill/exec/ExecConstants.java 
b/exec/java-exec/src/main/java/org/apache/drill/exec/ExecConstants.java
index 5c19e13..17926af 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/ExecConstants.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/ExecConstants.java
@@ -19,12 +19,12 @@ package org.apache.drill.exec;
 
 import org.apache.drill.exec.physical.impl.common.HashTable;
 import org.apache.drill.exec.rpc.user.InboundImpersonationManager;
-import org.apache.drill.exec.server.options.OptionMetaData;
 import org.apache.drill.exec.server.options.OptionValidator;
 import org.apache.drill.exec.server.options.TypeValidators.BooleanValidator;
 import org.apache.drill.exec.server.options.TypeValidators.DoubleValidator;
 import 
org.apache.drill.exec.server.options.TypeValidators.EnumeratedStringValidator;
 import org.apache.drill.exec.server.options.TypeValidators.LongValidator;
+import org.apache.drill.exec.server.options.TypeValidators.MaxWidthValidator;
 import 
org.apache.drill.exec.server.options.TypeValidators.PositiveLongValidator;
 import 
org.apache.drill.exec.server.options.TypeValidators.PowerOfTwoLongValidator;
 import 
org.apache.drill.exec.server.options.TypeValidators.RangeDoubleValidator;
@@ -358,7 +358,8 @@ public final class ExecConstants {
   public static final OptionValidator ENABLE_MEMORY_ESTIMATION = new 
BooleanValidator(ENABLE_MEMORY_ESTIMATION_KEY);
 
   /**
-   * Maximum query memory per node (in MB). Re-plan with cheaper operators if 
memory estimation exceeds this limit.
+   * Maximum query memory per node (in MB). Re-plan with cheaper operators if
+   * memory estimation exceeds this limit.
    * <p/>
    * DEFAULT: 2048 MB
    */
@@ -366,6 +367,34 @@ public final class ExecConstants {
   public static final LongValidator MAX_QUERY_MEMORY_PER_NODE = new 
RangeLongValidator(MAX_QUERY_MEMORY_PER_NODE_KEY, 1024 * 1024, Long.MAX_VALUE);
 
   /**
+   * Alternative way to compute per-query-per-node memory as a percent
+   * of the total available system memory.
+   * <p>
+   * Suggestion for computation.
+   * <ul>
+   * <li>Assume an allowance for non-managed operators. Default assumption:
+   * 50%</li>
+   * <li>Assume a desired number of concurrent queries. Default assumption:
+   * 10.</li>
+   * <li>The value of this parameter is<br>
+   * (1 - non-managed allowance) / concurrency</li>
+   * </ul>
+   * Doing the math produces the default 5% number. The actual number
+   * given is no less than the <tt>max_query_memory_per_node</tt>
+   * amount.
+   * <p>
+   * This number is used only when throttling is disabled. Setting the
+   * number to 0 effectively disables this technique as it will always
+   * produce values lower than <tt>max_query_memory_per_node</tt>.
+   * <p>
+   * DEFAULT: 5%
+   */
+
+  public static String PERCENT_MEMORY_PER_QUERY_KEY = 
"planner.memory.percent_per_query";
+  public static DoubleValidator PERCENT_MEMORY_PER_QUERY = new 
RangeDoubleValidator(
+      PERCENT_MEMORY_PER_QUERY_KEY, 0, 1.0);
+
+  /**
    * Minimum memory allocated to each buffered operator instance.
    * <p/>
    * DEFAULT: 40 MB

http://git-wip-us.apache.org/repos/asf/drill/blob/f781ce1f/exec/java-exec/src/main/java/org/apache/drill/exec/server/options/SystemOptionManager.java
----------------------------------------------------------------------
diff --git 
a/exec/java-exec/src/main/java/org/apache/drill/exec/server/options/SystemOptionManager.java
 
b/exec/java-exec/src/main/java/org/apache/drill/exec/server/options/SystemOptionManager.java
index bda6033..e81ed0b 100644
--- 
a/exec/java-exec/src/main/java/org/apache/drill/exec/server/options/SystemOptionManager.java
+++ 
b/exec/java-exec/src/main/java/org/apache/drill/exec/server/options/SystemOptionManager.java
@@ -167,6 +167,7 @@ public class SystemOptionManager extends BaseOptionManager 
implements AutoClosea
       new OptionDefinition(ExecConstants.EARLY_LIMIT0_OPT),
       new OptionDefinition(ExecConstants.ENABLE_MEMORY_ESTIMATION),
       new OptionDefinition(ExecConstants.MAX_QUERY_MEMORY_PER_NODE),
+      new OptionDefinition(ExecConstants.PERCENT_MEMORY_PER_QUERY),
       new OptionDefinition(ExecConstants.MIN_MEMORY_PER_BUFFERED_OP),
       new OptionDefinition(ExecConstants.NON_BLOCKING_OPERATORS_MEMORY),
       new OptionDefinition(ExecConstants.HASH_JOIN_TABLE_FACTOR),

http://git-wip-us.apache.org/repos/asf/drill/blob/f781ce1f/exec/java-exec/src/main/java/org/apache/drill/exec/util/MemoryAllocationUtilities.java
----------------------------------------------------------------------
diff --git 
a/exec/java-exec/src/main/java/org/apache/drill/exec/util/MemoryAllocationUtilities.java
 
b/exec/java-exec/src/main/java/org/apache/drill/exec/util/MemoryAllocationUtilities.java
index 6a805c1..3189535 100644
--- 
a/exec/java-exec/src/main/java/org/apache/drill/exec/util/MemoryAllocationUtilities.java
+++ 
b/exec/java-exec/src/main/java/org/apache/drill/exec/util/MemoryAllocationUtilities.java
@@ -27,6 +27,9 @@ import org.apache.drill.exec.ops.QueryContext;
 import org.apache.drill.exec.physical.PhysicalPlan;
 import org.apache.drill.exec.physical.base.PhysicalOperator;
 import org.apache.drill.exec.server.options.OptionManager;
+import org.apache.drill.exec.server.options.OptionSet;
+
+import com.google.common.annotations.VisibleForTesting;
 
 public class MemoryAllocationUtilities {
 
@@ -71,30 +74,90 @@ public class MemoryAllocationUtilities {
     }
 
     // if there are any sorts, compute the maximum allocation, and set it on 
them
-    if (bufferedOpList.size() > 0) {
-      final OptionManager optionManager = queryContext.getOptions();
-      double cpu_load_average = 
optionManager.getOption(ExecConstants.CPU_LOAD_AVERAGE);
-      final long maxWidth = 
optionManager.getOption(ExecConstants.MAX_WIDTH_PER_NODE);
-      final long maxWidthPerNode = 
ExecConstants.MAX_WIDTH_PER_NODE.computeMaxWidth(cpu_load_average,maxWidth);
-      long maxAllocPerNode = Math.min(DrillConfig.getMaxDirectMemory(),
-          
queryContext.getConfig().getLong(RootAllocatorFactory.TOP_LEVEL_MAX_ALLOC));
-      maxAllocPerNode = Math.min(maxAllocPerNode,
-          
optionManager.getOption(ExecConstants.MAX_QUERY_MEMORY_PER_NODE_KEY).num_val);
-      final long maxOperatorAlloc = maxAllocPerNode / (bufferedOpList.size() * 
maxWidthPerNode);
-      logger.debug("Max buffered operator alloc: {}", maxOperatorAlloc);
-
-      // User configurable option to allow forcing minimum memory.
-      // Ensure that the buffered ops receive the minimum memory needed to 
make progress.
-      // Without this, the math might work out to allocate too little memory.
-      final long opMinMem = 
queryContext.getOptions().getOption(ExecConstants.MIN_MEMORY_PER_BUFFERED_OP_KEY).num_val;
-
-      for(final PhysicalOperator op : bufferedOpList) {
-
-        long alloc = Math.max(maxOperatorAlloc, op.getInitialAllocation());
-        alloc = Math.max(alloc, opMinMem);
-        op.setMaxAllocation(alloc);
-      }
-    }
     plan.getProperties().hasResourcePlan = true;
+    if (bufferedOpList.isEmpty()) {
+      return;
+    }
+
+    // Setup options, etc.
+
+    final OptionManager optionManager = queryContext.getOptions();
+    final long directMemory = DrillConfig.getMaxDirectMemory();
+
+    // Compute per-node, per-query memory.
+
+    final long maxAllocPerNode = computeQueryMemory(queryContext.getConfig(), 
optionManager, directMemory);
+    logger.debug("Memory per query per node: {}", maxAllocPerNode);
+
+    // Now divide up the memory by slices and operators.
+
+    final long opMinMem = computeOperatorMemory(optionManager, 
maxAllocPerNode, bufferedOpList.size());
+
+    for(final PhysicalOperator op : bufferedOpList) {
+      final long alloc = Math.max(opMinMem, op.getInitialAllocation());
+      op.setMaxAllocation(alloc);
+    }
+  }
+
+  /**
+   * Compute per-operator memory based on the computed per-node memory, the
+   * number of operators, and the computed number of fragments (which house
+   * the operators.) Enforces a floor on the amount of memory per operator.
+   *
+   * @param optionManager system option manager
+   * @param maxAllocPerNode computed query memory per node
+   * @param opCount number of buffering operators in this query
+   * @return the per-operator memory
+   */
+
+  public static long computeOperatorMemory(OptionSet optionManager, long 
maxAllocPerNode, int opCount) {
+    final long maxWidth = 
optionManager.getOption(ExecConstants.MAX_WIDTH_PER_NODE);
+    final double cpuLoadAverage = 
optionManager.getOption(ExecConstants.CPU_LOAD_AVERAGE);
+    final long maxWidthPerNode = 
ExecConstants.MAX_WIDTH_PER_NODE.computeMaxWidth(cpuLoadAverage, maxWidth);
+    final long maxOperatorAlloc = maxAllocPerNode / (opCount * 
maxWidthPerNode);
+    logger.debug("Max buffered operator alloc: {}", maxOperatorAlloc);
+
+    // User configurable option to allow forcing minimum memory.
+    // Ensure that the buffered ops receive the minimum memory needed to make 
progress.
+    // Without this, the math might work out to allocate too little memory.
+
+    return Math.max(maxOperatorAlloc,
+        optionManager.getOption(ExecConstants.MIN_MEMORY_PER_BUFFERED_OP));
+  }
+
+  /**
+   * Per-node memory calculations based on a number of constraints.
+   * <p>
+   * Factored out into a separate method to allow unit testing.
+   * @param config Drill config
+   * @param optionManager system options
+   * @param directMemory amount of direct memory
+   * @return memory per query per node
+   */
+
+  @VisibleForTesting
+  public static long computeQueryMemory(DrillConfig config, OptionSet 
optionManager, long directMemory) {
+
+    // Memory computed as a percent of total memory.
+
+    long perQueryMemory = Math.round(directMemory *
+        optionManager.getOption(ExecConstants.PERCENT_MEMORY_PER_QUERY));
+
+    // But, must allow at least the amount given explicitly for
+    // backward compatibility.
+
+    perQueryMemory = Math.max(perQueryMemory,
+        optionManager.getOption(ExecConstants.MAX_QUERY_MEMORY_PER_NODE));
+
+    // Compute again as either the total direct memory, or the
+    // configured maximum top-level allocation (10 GB).
+
+    long maxAllocPerNode = Math.min(directMemory,
+        config.getLong(RootAllocatorFactory.TOP_LEVEL_MAX_ALLOC));
+
+    // Final amount per node per query is the minimum of these two.
+
+    maxAllocPerNode = Math.min(maxAllocPerNode, perQueryMemory);
+    return maxAllocPerNode;
   }
 }

http://git-wip-us.apache.org/repos/asf/drill/blob/f781ce1f/exec/java-exec/src/main/resources/drill-module.conf
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/resources/drill-module.conf 
b/exec/java-exec/src/main/resources/drill-module.conf
index 26c8df0..d95f421 100644
--- a/exec/java-exec/src/main/resources/drill-module.conf
+++ b/exec/java-exec/src/main/resources/drill-module.conf
@@ -19,7 +19,7 @@
 
 drill {
   classpath.scanning {
-    base.classes : ${?drill.classpath.scanning.base.classes} [
+    base.classes: ${?drill.classpath.scanning.base.classes} [
       org.apache.drill.exec.expr.DrillFunc,
       org.apache.drill.exec.expr.fn.PluggableFunctionRegistry,
       org.apache.drill.exec.physical.base.PhysicalOperator,
@@ -33,7 +33,7 @@ drill {
 
     annotations += org.apache.drill.exec.expr.annotations.FunctionTemplate
 
-    packages : ${?drill.classpath.scanning.packages} [
+    packages: ${?drill.classpath.scanning.packages} [
           org.apache.drill.exec.expr,
           org.apache.drill.exec.physical,
           org.apache.drill.exec.store,
@@ -71,7 +71,7 @@ drill.exec: {
     bit: {
       timeout: 300,
       server: {
-        port : 31011,
+        port: 31011,
         retry:{
           count: 7200,
           delay: 500
@@ -89,7 +89,7 @@ drill.exec: {
         }
       }
     },
-    use.ip : false
+    use.ip: false
   },
   optimizer: {
     implementation: "org.apache.drill.exec.opt.IdentityOptimizer"
@@ -180,15 +180,15 @@ drill.exec: {
     use_login_principal: false
   }
   security.user.encryption.sasl: {
-    enabled : false,
-    max_wrapped_size : 65536
+    enabled: false,
+    max_wrapped_size: 65536
   }
   security.bit.encryption.sasl: {
-    enabled : false,
-    max_wrapped_size : 65536
+    enabled: false,
+    max_wrapped_size: 65536
   }
   security.user.encryption.ssl: {
-    enabled : false
+    enabled: false
   }
   trace: {
     directory: "/tmp/drill-trace",
@@ -388,10 +388,10 @@ drill.jdbc: {
 # Users are not supposed to set these options in the drill-override.conf file.
 # Users should use ALTER SYSTEM and ALTER SESSION to set the options.
 
-drill.exec.options:  {
+drill.exec.options: {
     bootstrap-storage-plugins.json: .sys.drill,
-    debug.validate_iterators : false,
-    debug.validate_vectors :false,
+    debug.validate_iterators: false,
+    debug.validate_vectors: false,
     drill.exec.functions.cast_empty_string_to_null: false,
     # Setting to control if HashAgg should fallback to older behavior of 
consuming
     # unbounded memory. In case of 2 phase Agg when available memory is not 
enough
@@ -416,9 +416,9 @@ drill.exec.options:  {
     exec.hashagg.use_memory_prediction: true,
     exec.impersonation.inbound_policies: "[]",
     exec.java.compiler.exp_in_method_size: 50,
-    exec.java_compiler : "DEFAULT",
-    exec.java_compiler_debug :true,
-    exec.java_compiler_janino_maxsize : 262144,
+    exec.java_compiler: "DEFAULT",
+    exec.java_compiler_debug: true,
+    exec.java_compiler_janino_maxsize: 262144,
     exec.max_hash_table_size: 1073741824,
     exec.min_hash_table_size: 65536,
     exec.persistent_table.umask: "002",
@@ -440,57 +440,58 @@ drill.exec.options:  {
     exec.udf.enable_dynamic_support: true,
     exec.udf.use_dynamic: true,
     new_view_default_permissions: 700,
-    org.apache.drill.exec.compile.ClassTransformer.scalar_replacement : "try",
-    planner.add_producer_consumer:false,
+    org.apache.drill.exec.compile.ClassTransformer.scalar_replacement: "try",
+    planner.add_producer_consumer: false,
     planner.affinity_factor: 1.2,
-    planner.broadcast_factor:1.0,
-    planner.broadcast_threshold:10000000,
-    planner.cpu_load_average : 0.70,
-    planner.disable_exchanges:false,
-    planner.enable_broadcast_join:true,
-    planner.enable_constant_folding:true,
-    planner.enable_decimal_data_type:false,
-    planner.enable_demux_exchange:false,
-    planner.enable_hash_single_key:true,
-    planner.enable_hashagg:true,
-    planner.enable_hashjoin:true,
-    planner.enable_hashjoin_swap:true,
-    planner.enable_hep_opt:true,
-    planner.enable_hep_partition_pruning:true,
+    planner.broadcast_factor: 1.0,
+    planner.broadcast_threshold: 10000000,
+    planner.cpu_load_average: 0.70,
+    planner.disable_exchanges: false,
+    planner.enable_broadcast_join: true,
+    planner.enable_constant_folding: true,
+    planner.enable_decimal_data_type: false,
+    planner.enable_demux_exchange: false,
+    planner.enable_hash_single_key: true,
+    planner.enable_hashagg: true,
+    planner.enable_hashjoin: true,
+    planner.enable_hashjoin_swap: true,
+    planner.enable_hep_opt: true,
+    planner.enable_hep_partition_pruning: true,
     planner.enable_join_optimization: true,
     planner.enable_limit0_optimization: false,
-    planner.enable_mergejoin:true,
-    planner.enable_multiphase_agg:true,
-    planner.enable_mux_exchange:true,
-    planner.enable_nestedloopjoin:true,
-    planner.enable_nljoin_for_scalar_only:true,
-    planner.enable_streamagg:true,
+    planner.enable_mergejoin: true,
+    planner.enable_multiphase_agg: true,
+    planner.enable_mux_exchange: true,
+    planner.enable_nestedloopjoin: true,
+    planner.enable_nljoin_for_scalar_only: true,
+    planner.enable_streamagg: true,
     planner.enable_type_inference: true,
-    planner.enable_unionall_distribute:false,
-    planner.filter.max_selectivity_estimate_factor:1.0,
-    planner.filter.min_selectivity_estimate_factor:0.0,
-    planner.force_2phase_aggr : false,
-    planner.identifier_max_length:1024,
+    planner.enable_unionall_distribute: false,
+    planner.filter.max_selectivity_estimate_factor: 1.0,
+    planner.filter.min_selectivity_estimate_factor: 0.0,
+    planner.force_2phase_aggr: false,
+    planner.identifier_max_length: 1024,
     planner.in_subquery_threshold: 20,
-    planner.join.hash_join_swap_margin_factor:10,
-    planner.join.row_count_estimate_factor:1.0,
+    planner.join.hash_join_swap_margin_factor: 10,
+    planner.join.row_count_estimate_factor: 1.0,
     planner.memory.average_field_width: 8,
     planner.memory.enable_memory_estimation: false,
     planner.memory.hash_agg_table_factor: 1.1d,
     planner.memory.hash_join_table_factor: 1.1d,
-    planner.memory.max_query_memory_per_node: 2147483648,
-    planner.memory.min_memory_per_buffered_op: 41943040,
+    planner.memory.max_query_memory_per_node: 2147483648, # 2 GB
+    planner.memory.percent_per_query: 0.05, # 5%
+    planner.memory.min_memory_per_buffered_op: 41943040, # 40 MB
     planner.memory.non_blocking_operators_memory: 64,
-    planner.memory_limit:268435456,
-    planner.nestedloopjoin_factor:100.0,
-    planner.parser.quoting_identifiers :"`",
-    planner.partitioner_sender_max_threads:8,
-    planner.partitioner_sender_set_threads:-1,
-    planner.partitioner_sender_threads_factor:2,
-    planner.producer_consumer_queue_size:10,
+    planner.memory_limit: 268435456,
+    planner.nestedloopjoin_factor: 100.0,
+    planner.parser.quoting_identifiers: "`",
+    planner.partitioner_sender_max_threads: 8,
+    planner.partitioner_sender_set_threads: -1,
+    planner.partitioner_sender_threads_factor: 2,
+    planner.producer_consumer_queue_size: 10,
     planner.slice_target: 100000,
-    planner.store.parquet.rowgroup.filter.pushdown.enabled : true,
-    planner.store.parquet.rowgroup.filter.pushdown.threshold : 10000,
+    planner.store.parquet.rowgroup.filter.pushdown.enabled: true,
+    planner.store.parquet.rowgroup.filter.pushdown.threshold: 10000,
     # Max per node should always be configured as zero and
     # it is dynamically computed based on cpu_load_average
     planner.width.max_per_node: 0,
@@ -526,7 +527,7 @@ drill.exec.options:  {
     store.parquet.use_new_reader: false,
     store.parquet.vector_fill_check_threshold: 10,
     store.parquet.vector_fill_threshold: 85,
-    store.parquet.writer.use_single_fs_block : false,
+    store.parquet.writer.use_single_fs_block: false,
     store.partition.hash_distribute: false,
     store.text.estimated_row_size_bytes: 100.0,
     web.logs.max_lines: 10000,

http://git-wip-us.apache.org/repos/asf/drill/blob/f781ce1f/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/xsort/TestSortSpillWithException.java
----------------------------------------------------------------------
diff --git 
a/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/xsort/TestSortSpillWithException.java
 
b/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/xsort/TestSortSpillWithException.java
index d816cb1..f6b3a8d 100644
--- 
a/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/xsort/TestSortSpillWithException.java
+++ 
b/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/xsort/TestSortSpillWithException.java
@@ -55,6 +55,8 @@ public class TestSortSpillWithException extends ClusterTest {
         .configProperty(ExecConstants.EXTERNAL_SORT_SPILL_THRESHOLD, 1) // 
Unmanaged
         .configProperty(ExecConstants.EXTERNAL_SORT_SPILL_GROUP_SIZE, 1) // 
Unmanaged
         .sessionOption(ExecConstants.MAX_QUERY_MEMORY_PER_NODE_KEY, 60 * 1024 
* 1024) // Spill early
+        // Prevent the percent-based memory rule from second-guessing the 
above.
+        .sessionOption(ExecConstants.PERCENT_MEMORY_PER_QUERY_KEY, 0.0)
         .configProperty(ExecConstants.EXTERNAL_SORT_DISABLE_MANAGED, false)
         .maxParallelization(1)
         ;

http://git-wip-us.apache.org/repos/asf/drill/blob/f781ce1f/exec/java-exec/src/test/java/org/apache/drill/exec/util/TestQueryMemoryAlloc.java
----------------------------------------------------------------------
diff --git 
a/exec/java-exec/src/test/java/org/apache/drill/exec/util/TestQueryMemoryAlloc.java
 
b/exec/java-exec/src/test/java/org/apache/drill/exec/util/TestQueryMemoryAlloc.java
new file mode 100644
index 0000000..2476da7
--- /dev/null
+++ 
b/exec/java-exec/src/test/java/org/apache/drill/exec/util/TestQueryMemoryAlloc.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+package org.apache.drill.exec.util;
+
+import static org.junit.Assert.*;
+
+import org.apache.drill.exec.ExecConstants;
+import org.apache.drill.test.DrillTest;
+import org.apache.drill.test.OperatorFixture;
+import org.apache.drill.test.OperatorFixture.OperatorFixtureBuilder;
+import org.junit.Test;
+
+public class TestQueryMemoryAlloc extends DrillTest {
+
+  public static final long ONE_MB = 1024 * 1024;
+  public static final long ONE_GB = 1024L * ONE_MB;
+
+  @Test
+  public void testDefaultOptions() throws Exception {
+    OperatorFixtureBuilder builder = OperatorFixture.builder();
+    builder.options().set(ExecConstants.PERCENT_MEMORY_PER_QUERY_KEY, 0.05);
+    builder.options().set(ExecConstants.MAX_QUERY_MEMORY_PER_NODE_KEY, 2 * 
ONE_GB);
+
+    try (OperatorFixture fixture = builder.build()) {
+
+      // Out-of-box memory, use query memory per node as floor.
+
+      long mem = 
MemoryAllocationUtilities.computeQueryMemory(fixture.config(), 
fixture.options(), 8 * ONE_GB);
+      assertEquals(2 * ONE_GB, mem);
+
+      // Up to 40 GB, query memory dominates.
+
+      mem = MemoryAllocationUtilities.computeQueryMemory(fixture.config(), 
fixture.options(), 40 * ONE_GB);
+      assertEquals(2 * ONE_GB, mem);
+
+      // After 40 GB, the percent dominates
+
+      mem = MemoryAllocationUtilities.computeQueryMemory(fixture.config(), 
fixture.options(), 100 * ONE_GB);
+      assertEquals(5 * ONE_GB, mem);
+    }
+  }
+
+  @Test
+  public void testCustomFloor() throws Exception {
+    OperatorFixtureBuilder builder = OperatorFixture.builder();
+    builder.options().set(ExecConstants.PERCENT_MEMORY_PER_QUERY_KEY, 0.05);
+    builder.options().set(ExecConstants.MAX_QUERY_MEMORY_PER_NODE_KEY, 3 * 
ONE_GB);
+
+    try (OperatorFixture fixture = builder.build()) {
+
+      // Out-of-box memory, use query memory per node as floor.
+
+      long mem = 
MemoryAllocationUtilities.computeQueryMemory(fixture.config(), 
fixture.options(), 8 * ONE_GB);
+      assertEquals(3 * ONE_GB, mem);
+
+      // Up to 60 GB, query memory dominates.
+
+      mem = MemoryAllocationUtilities.computeQueryMemory(fixture.config(), 
fixture.options(), 60 * ONE_GB);
+      assertEquals(3 * ONE_GB, mem);
+
+      // After 60 GB, the percent dominates
+
+      mem = MemoryAllocationUtilities.computeQueryMemory(fixture.config(), 
fixture.options(), 100 * ONE_GB);
+      assertEquals(5 * ONE_GB, mem);
+    }
+  }
+
+  @Test
+  public void testCustomPercent() throws Exception {
+    OperatorFixtureBuilder builder = OperatorFixture.builder();
+    builder.options().set(ExecConstants.PERCENT_MEMORY_PER_QUERY_KEY, 0.10);
+    builder.options().set(ExecConstants.MAX_QUERY_MEMORY_PER_NODE_KEY, 2 * 
ONE_GB);
+
+    try (OperatorFixture fixture = builder.build()) {
+
+      // Out-of-box memory, use query memory per node as floor.
+
+      long mem = 
MemoryAllocationUtilities.computeQueryMemory(fixture.config(), 
fixture.options(), 8 * ONE_GB);
+      assertEquals(2 * ONE_GB, mem);
+
+      // Up to 20 GB, query memory dominates.
+
+      mem = MemoryAllocationUtilities.computeQueryMemory(fixture.config(), 
fixture.options(), 20 * ONE_GB);
+      assertEquals(2 * ONE_GB, mem);
+
+      // After 20 GB, the percent dominates
+
+      mem = MemoryAllocationUtilities.computeQueryMemory(fixture.config(), 
fixture.options(), 30 * ONE_GB);
+      assertEquals(3 * ONE_GB, mem);
+    }
+  }
+
+  /**
+   * Test with default options, various memory configs.
+   * Since we can't change the actual CPUs on this node, use an
+   * option to specify the number (rather than the usual 70% of
+   * actual cores.)
+   *
+   * @throws Exception
+   */
+
+  @Test
+  public void testOpMemory() throws Exception {
+    OperatorFixtureBuilder builder = OperatorFixture.builder();
+    builder.options().set(ExecConstants.CPU_LOAD_AVERAGE_KEY, 0.7);
+    builder.options().set(ExecConstants.MAX_WIDTH_PER_NODE_KEY, 10);
+    builder.options().set(ExecConstants.MIN_MEMORY_PER_BUFFERED_OP_KEY, 40 * 
ONE_MB);
+
+    try (OperatorFixture fixture = builder.build()) {
+
+      // Enough memory to go above configured minimum.
+
+      long opMinMem = 
MemoryAllocationUtilities.computeOperatorMemory(fixture.options(), 4 * ONE_GB, 
2);
+      assertEquals(4 * ONE_GB / 10 / 2, opMinMem);
+
+      // Too little memory per operator. Use configured minimum.
+
+      opMinMem = 
MemoryAllocationUtilities.computeOperatorMemory(fixture.options(), ONE_GB, 100);
+      assertEquals(40 * ONE_MB, opMinMem);
+    }
+  }
+}

Reply via email to