You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by am...@apache.org on 2019/07/19 12:20:08 UTC
[ignite] 06/19: GG-20750: SQL: Fix performance drop with enabling
global SQL memory quota.
This is an automated email from the ASF dual-hosted git repository.
amashenkov pushed a commit to branch gg-21368
in repository https://gitbox.apache.org/repos/asf/ignite.git
commit 5417a6c5e109e1e3bb3eaa402bf1275026dc000f
Author: Andrey V. Mashenkov <an...@gmail.com>
AuthorDate: Wed Jul 17 11:09:10 2019 +0300
GG-20750: SQL: Fix performance drop with enabling global SQL memory quota.
---
.../processors/query/h2/H2MemoryTracker.java | 3 +-
.../processors/query/h2/QueryMemoryManager.java | 72 +++++++++----
.../processors/query/h2/QueryMemoryTracker.java | 113 +++++++++++----------
.../oom/AbstractQueryMemoryTrackerSelfTest.java | 27 ++++-
...yMemoryTrackerWithQueryParallelismSelfTest.java | 36 +++++++
.../query/oom/QueryMemoryTrackerSelfTest.java | 11 ++
6 files changed, 183 insertions(+), 79 deletions(-)
diff --git a/modules/h2/src/main/java/org/apache/ignite/internal/processors/query/h2/H2MemoryTracker.java b/modules/h2/src/main/java/org/apache/ignite/internal/processors/query/h2/H2MemoryTracker.java
index e1d3aac..bbcc5f4 100644
--- a/modules/h2/src/main/java/org/apache/ignite/internal/processors/query/h2/H2MemoryTracker.java
+++ b/modules/h2/src/main/java/org/apache/ignite/internal/processors/query/h2/H2MemoryTracker.java
@@ -21,6 +21,8 @@ package org.apache.ignite.internal.processors.query.h2;
*/
public abstract class H2MemoryTracker implements AutoCloseable {
/**
+ * Reserve memory.
+ *
* @param size Memory to reserve in bytes.
*/
public abstract void reserve(long size);
@@ -30,6 +32,5 @@ public abstract class H2MemoryTracker implements AutoCloseable {
*
* @param size Memory to release in bytes.
*/
-
public abstract void release(long size);
}
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/QueryMemoryManager.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/QueryMemoryManager.java
index 58ae9e2..727ed35 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/QueryMemoryManager.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/QueryMemoryManager.java
@@ -17,6 +17,7 @@
package org.apache.ignite.internal.processors.query.h2;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.LongBinaryOperator;
import org.apache.ignite.IgniteLogger;
import org.apache.ignite.IgniteSystemProperties;
import org.apache.ignite.configuration.IgniteConfiguration;
@@ -37,6 +38,19 @@ public class QueryMemoryManager extends H2MemoryTracker {
*/
private static final long DFLT_MEMORY_RESERVATION_BLOCK_SIZE = 512 * KB;
+ /** */
+ static final LongBinaryOperator RELEASE_OP = new LongBinaryOperator() {
+ @Override public long applyAsLong(long prev, long x) {
+ long res = prev - x;
+
+ if (res < 0)
+ throw new IllegalStateException("Try to free more memory that ever be reserved: [" +
+ "reserved=" + prev + ", toFree=" + x + ']');
+
+ return res;
+ }
+ };
+
/**
* Default query memory limit.
*
@@ -48,6 +62,9 @@ public class QueryMemoryManager extends H2MemoryTracker {
/** Logger. */
private final IgniteLogger log;
+ /** */
+ private final LongBinaryOperator reserveOp;
+
/** Global query memory quota. */
//TODO GG-18629: it looks safe to make this configurable at runtime.
private final long globalQuota;
@@ -74,48 +91,38 @@ public class QueryMemoryManager extends H2MemoryTracker {
(long)(Runtime.getRuntime().maxMemory() * 0.6d));
}
- long dfltSqlQryMemoryLimit = Long.getLong(IgniteSystemProperties.IGNITE_DEFAULT_SQL_QUERY_MEMORY_LIMIT, 0);
+ long dfltMemLimit = Long.getLong(IgniteSystemProperties.IGNITE_DEFAULT_SQL_QUERY_MEMORY_LIMIT, 0);
- if (dfltSqlQryMemoryLimit == 0)
- dfltSqlQryMemoryLimit = globalQuota > 0 ? globalQuota / IgniteConfiguration.DFLT_QUERY_THREAD_POOL_SIZE : -1;
+ if (dfltMemLimit == 0)
+ dfltMemLimit = globalQuota > 0 ? globalQuota / IgniteConfiguration.DFLT_QUERY_THREAD_POOL_SIZE : -1;
this.blockSize = Long.getLong(IgniteSystemProperties.IGNITE_SQL_MEMORY_RESERVATION_BLOCK_SIZE, DFLT_MEMORY_RESERVATION_BLOCK_SIZE);
this.globalQuota = globalQuota;
- this.dfltSqlQryMemoryLimit = dfltSqlQryMemoryLimit;
+ this.dfltSqlQryMemoryLimit = dfltMemLimit;
+
+ this.reserveOp = new ReservationOp(globalQuota);
this.log = ctx.log(QueryMemoryManager.class);
}
/** {@inheritDoc} */
@Override public void reserve(long size) {
- assert size >= 0;
-
if (size == 0)
return; // Nothing to do.
- reserved.accumulateAndGet(size, (prev, x) -> {
- if (prev + x > globalQuota)
- throw new IgniteSQLException("SQL query run out of memory: Global quota exceeded.",
- IgniteQueryErrorCode.QUERY_OUT_OF_MEMORY);
+ assert size > 0;
- return prev + x;
- });
+ reserved.accumulateAndGet(size, reserveOp);
}
/** {@inheritDoc} */
@Override public void release(long size) {
- assert size >= 0;
-
if (size == 0)
return; // Nothing to do.
- reserved.accumulateAndGet(-size, (prev, x) -> {
- if (prev + x < 0)
- throw new IllegalStateException("Try to free more memory that ever be reserved: [" +
- "reserved=" + prev + ", toFree=" + x + ']');
+ assert size > 0;
- return prev + x;
- });
+ reserved.accumulateAndGet(size, RELEASE_OP);
}
/**
@@ -172,4 +179,29 @@ public class QueryMemoryManager extends H2MemoryTracker {
if (log.isDebugEnabled() && reserved.get() != 0)
log.debug("Potential memory leak in SQL processor. Some query cursors were not closed or forget to free memory.");
}
+
+ /** */
+ private static class ReservationOp implements LongBinaryOperator {
+ /** Operation result high bound.*/
+ private final long limit;
+
+ /**
+ * Constructor.
+ * @param limit Operation result high bound.
+ */
+ ReservationOp(long limit) {
+ this.limit = limit;
+ }
+
+ /** {@inheritDoc} */
+ @Override public long applyAsLong(long prev, long x) {
+ long res = prev + x;
+
+ if (res > limit)
+ throw new IgniteSQLException("SQL query run out of memory: Global quota exceeded.",
+ IgniteQueryErrorCode.QUERY_OUT_OF_MEMORY);
+
+ return res;
+ }
+ }
}
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/QueryMemoryTracker.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/QueryMemoryTracker.java
index d0ff5c2..ea31930 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/QueryMemoryTracker.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/QueryMemoryTracker.java
@@ -16,8 +16,6 @@
package org.apache.ignite.internal.processors.query.h2;
-import java.util.concurrent.atomic.AtomicLongFieldUpdater;
-import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
import org.apache.ignite.internal.processors.query.IgniteSQLException;
import org.apache.ignite.internal.util.typedef.internal.S;
@@ -28,14 +26,6 @@ import org.apache.ignite.internal.util.typedef.internal.S;
* Track query memory usage and throws an exception if query tries to allocate memory over limit.
*/
public class QueryMemoryTracker extends H2MemoryTracker implements AutoCloseable {
- /** Resered field updater. */
- private static final AtomicLongFieldUpdater<QueryMemoryTracker> RESERVED_UPD =
- AtomicLongFieldUpdater.newUpdater(QueryMemoryTracker.class, "reserved");
-
- /** Closed flag updater. */
- private static final AtomicReferenceFieldUpdater<QueryMemoryTracker, Boolean> CLOSED_UPD =
- AtomicReferenceFieldUpdater.newUpdater(QueryMemoryTracker.class, Boolean.class, "closed");
-
/** Parent tracker. */
private final H2MemoryTracker parent;
@@ -46,13 +36,13 @@ public class QueryMemoryTracker extends H2MemoryTracker implements AutoCloseable
private final long blockSize;
/** Memory reserved on parent. */
- private volatile long reservedFromParent;
+ private long reservedFromParent;
/** Memory reserved by query. */
- private volatile long reserved;
+ private long reserved;
/** Close flag to prevent tracker reuse. */
- private volatile Boolean closed = Boolean.FALSE;
+ private Boolean closed = Boolean.FALSE;
/**
* Constructor.
@@ -71,40 +61,31 @@ public class QueryMemoryTracker extends H2MemoryTracker implements AutoCloseable
/** {@inheritDoc} */
@Override public void reserve(long size) {
- assert !closed && size >= 0;
-
if (size == 0)
return;
- long reserved0 = RESERVED_UPD.accumulateAndGet(this, size, (prev, x) -> {
- if (prev + x > maxMem) {
- throw new IgniteSQLException("SQL query run out of memory: Query quota exceeded.",
- IgniteQueryErrorCode.QUERY_OUT_OF_MEMORY);
- }
+ assert size > 0;
- return prev + x;
- });
+ synchronized (this) {
+ if (closed)
+ throw new IllegalStateException("Memory tracker has been closed concurrently.");
- if (parent != null && reserved0 > reservedFromParent) {
- synchronized (this) {
- assert !closed;
-
- if (reserved0 <= reservedFromParent)
- return;
-
- // If single block size is too small.
- long blockSize = Math.max(reserved0 - reservedFromParent, this.blockSize);
- // If we are too close to limit.
- blockSize = Math.min(blockSize, maxMem - reservedFromParent);
+ long reserved0 = reserve0(size);
+ if (parent != null && reserved0 > reservedFromParent) {
try {
+ // If single block size is too small.
+ long blockSize = Math.max(reserved0 - reservedFromParent, this.blockSize);
+ // If we are too close to limit.
+ blockSize = Math.min(blockSize, maxMem - reservedFromParent);
+
parent.reserve(blockSize);
reservedFromParent += blockSize;
}
catch (Throwable e) {
// Fallback if failed to reserve.
- RESERVED_UPD.addAndGet(this, -size);
+ release0(size);
throw e;
}
@@ -114,31 +95,50 @@ public class QueryMemoryTracker extends H2MemoryTracker implements AutoCloseable
/** {@inheritDoc} */
@Override public void release(long size) {
- assert size >= 0;
-
if (size == 0)
return;
- long reserved = RESERVED_UPD.accumulateAndGet(this, -size, (prev, x) -> {
- if (prev + x < 0)
- throw new IllegalStateException("Try to release more memory that were reserved: [" +
- "reserved=" + prev + ", toRelease=" + x + ']');
+ assert size > 0;
- return prev + x;
- });
+ synchronized (this) {
+ if (closed)
+ throw new IllegalStateException("Memory tracker has been closed concurrently.");
- assert !closed && reserved >= 0 || reserved == 0 : "Invalid reserved memory size:" + reserved;
+ long reserved = release0(size);
- // For now, won'tQ release memory to parent until tracker closed.
- /* if (parent != null && preAllocated - reserved >= 2 * blockSize) {
- synchronized (this) {
- if (preAllocated - reserved >= 2 * blockSize) {
- parent.release(blockSize);
+ assert reserved >= 0;
+ }
+ }
- preAllocated -= blockSize;
- }
- }
- }*/
+ /**
+ * @param size Memory to reserve in bytes.
+ * @return Reserved memory after release.
+ */
+ private long reserve0(long size) {
+ long res = reserved + size;
+
+ if (res > maxMem)
+ throw new IgniteSQLException("SQL query run out of memory: Query quota exceeded.",
+ IgniteQueryErrorCode.QUERY_OUT_OF_MEMORY);
+
+ return reserved = res;
+ }
+
+ /**
+ * Release reserved memory.
+ *
+ * @param size Memory to release in bytes.
+ * @return Reserved memory after release.
+ */
+ private long release0(long size) {
+
+ long res = reserved - size;
+
+ if (res < 0)
+ throw new IllegalStateException("Try to free more memory that ever be reserved: [" +
+ "reserved=" + reserved + ", toFree=" + size + ']');
+
+ return reserved = res;
}
/**
@@ -152,8 +152,13 @@ public class QueryMemoryTracker extends H2MemoryTracker implements AutoCloseable
@Override public void close() {
// It is not expected to be called concurrently with reserve\release.
// But query can be cancelled concurrently on query finish.
- if (CLOSED_UPD.compareAndSet(this, Boolean.FALSE, Boolean.TRUE)) {
- release(RESERVED_UPD.get(this));
+ synchronized (this) {
+ if (closed)
+ return;
+
+ closed = true;
+
+ release0(reserved);
if (parent != null)
parent.release(reservedFromParent);
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/oom/AbstractQueryMemoryTrackerSelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/oom/AbstractQueryMemoryTrackerSelfTest.java
index c77ed15..2fc6231 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/oom/AbstractQueryMemoryTrackerSelfTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/oom/AbstractQueryMemoryTrackerSelfTest.java
@@ -44,6 +44,7 @@ import org.h2.engine.Session;
import org.h2.expression.Expression;
import org.h2.result.H2BaseLocalResult;
import org.h2.result.LocalResult;
+import org.junit.Ignore;
import org.junit.Test;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;
@@ -371,9 +372,10 @@ public abstract class AbstractQueryMemoryTrackerSelfTest extends GridCommonAbstr
/** Check GROUP BY operation on indexed col. */
@Test
+ @Ignore("https://ggsystems.atlassian.net/browse/GG-19071")
public void testQueryWithGroupByPrimaryKey() throws Exception {
- //TODO: GG-19071: make next test pass without hint.
- execQuery("select K.indexed, sum(K.id) from K USE INDEX (K_IDX) GROUP BY K.indexed", true);
+ //TODO: GG-19071: make next query use correct index (K_IDX instead of primary).
+ execQuery("select K.indexed, sum(K.id) from K GROUP BY K.indexed", true);
assertEquals(0, localResults.size());
}
@@ -396,7 +398,7 @@ public abstract class AbstractQueryMemoryTrackerSelfTest extends GridCommonAbstr
// Local result is quite small.
assertEquals(1, localResults.size());
- assertTrue(maxMem > localResults.get(0).memoryReserved());
+ assertTrue(maxMem > localResults.get(0).memoryReserved() + 1000);
assertTrue(BIG_TABLE_SIZE > localResults.get(0).getRowCount());
}
@@ -407,7 +409,7 @@ public abstract class AbstractQueryMemoryTrackerSelfTest extends GridCommonAbstr
// Local result is quite small.
assertEquals(1, localResults.size());
- assertTrue(maxMem > localResults.get(0).memoryReserved());
+ assertTrue(maxMem > localResults.get(0).memoryReserved() + 1000);
assertTrue(100 > localResults.get(0).getRowCount());
}
@@ -428,6 +430,7 @@ public abstract class AbstractQueryMemoryTrackerSelfTest extends GridCommonAbstr
"GROUP BY K.indexed ORDER BY a DESC", true);
assertEquals(1, localResults.size());
+ assertTrue(maxMem < localResults.get(0).memoryReserved() + 1000);
assertTrue(BIG_TABLE_SIZE > localResults.get(0).getRowCount());
}
@@ -447,6 +450,9 @@ public abstract class AbstractQueryMemoryTrackerSelfTest extends GridCommonAbstr
public void testQueryWithDistinctAndLowCardinality() throws Exception {
// Distinct on indexed column with small cardinality.
execQuery("select DISTINCT K.grp_indexed from K", false);
+
+ assertEquals(1, localResults.size());
+ assertEquals(100, localResults.get(0).getRowCount());
}
/** Check query failure with DISTINCT constraint. */
@@ -454,6 +460,9 @@ public abstract class AbstractQueryMemoryTrackerSelfTest extends GridCommonAbstr
public void testQueryWithDistinctAndHighCardinality() throws Exception {
// Distinct on indexed column with unique values.
checkQueryExpectOOM("select DISTINCT K.id from K", true);
+
+ assertEquals(1, localResults.size());
+ assertTrue(BIG_TABLE_SIZE > localResults.get(0).getRowCount());
}
/** Check HashJoin with large table. */
@@ -473,6 +482,16 @@ public abstract class AbstractQueryMemoryTrackerSelfTest extends GridCommonAbstr
}
}
+ /** Check Join with large table. */
+ @Test
+ public void testJoinWithLargeTable() throws Exception {
+ maxMem = 512 * KB;
+
+ execQuery("select * from T, K where T.id = K.grp_indexed", true);
+
+ assertEquals(0, localResults.size());
+ }
+
/** Check query failure due to global memory quota exceeded. */
@Test
public void testGlobalQuota() throws Exception {
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/oom/LocalQueryMemoryTrackerWithQueryParallelismSelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/oom/LocalQueryMemoryTrackerWithQueryParallelismSelfTest.java
index acb1fc0..4569792 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/oom/LocalQueryMemoryTrackerWithQueryParallelismSelfTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/oom/LocalQueryMemoryTrackerWithQueryParallelismSelfTest.java
@@ -339,4 +339,40 @@ public class LocalQueryMemoryTrackerWithQueryParallelismSelfTest extends Abstrac
assertEquals(1, localResults.size());
assertTrue(100 > localResults.get(0).getRowCount());
}
+
+ /** Check simple query with DISTINCT constraint. */
+ @Test
+ @Override public void testQueryWithDistinctAndLowCardinality() throws Exception {
+ // Distinct on indexed column with small cardinality.
+ execQuery("select DISTINCT K.grp_indexed from K", false);
+
+ assertEquals(5, localResults.size());
+ assertEquals(100, localResults.get(4).getRowCount());
+ }
+
+ /** Check query failure with DISTINCT constraint. */
+ @Test
+ @Override public void testQueryWithDistinctAndHighCardinality() throws Exception {
+ // Distinct on indexed column with unique values.
+ checkQueryExpectOOM("select DISTINCT K.id from K", true);
+
+ assertEquals(5, localResults.size());
+ assertFalse(localResults.stream().limit(4).anyMatch(r -> r.memoryReserved() + 1000 > maxMem));
+ assertTrue(BIG_TABLE_SIZE > localResults.get(4).getRowCount());
+ }
+
+ /** {@inheritDoc} */
+ @Test
+ @Override public void testLazyQueryWithGroupByThenSort() {
+ maxMem = MB / 2;
+
+ // Query failed on map side due too many groups.
+ checkQueryExpectOOM("select K.indexed, sum(K.grp) as a from K " +
+ "GROUP BY K.indexed ORDER BY a DESC", true);
+
+ // Result on reduce side.
+ assertEquals(1, localResults.size());
+ assertEquals(0, localResults.get(0).memoryReserved());
+ assertEquals(0, localResults.get(0).getRowCount());
+ }
}
\ No newline at end of file
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/oom/QueryMemoryTrackerSelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/oom/QueryMemoryTrackerSelfTest.java
index 6c29013..f612df9 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/oom/QueryMemoryTrackerSelfTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/oom/QueryMemoryTrackerSelfTest.java
@@ -176,6 +176,17 @@ public class QueryMemoryTrackerSelfTest extends AbstractQueryMemoryTrackerSelfTe
assertTrue(BIG_TABLE_SIZE > localResults.get(0).getRowCount());
}
+ /** Check simple query with DISTINCT constraint. */
+ @Test
+ @Override public void testQueryWithDistinctAndLowCardinality() throws Exception {
+ // Distinct on indexed column with small cardinality.
+ execQuery("select DISTINCT K.grp_indexed from K", false);
+
+ assertEquals(2, localResults.size());
+ assertEquals(100, localResults.get(0).getRowCount());
+ assertEquals(100, localResults.get(1).getRowCount());
+ }
+
/** {@inheritDoc} */
@Test
@Override public void testLazyQueryWithGroupByIndexedColAndDistinctAggregates() {