>> partitionedConsumption = SettableFuture.create();
+ @GuardedBy("lock")
+ private PartitionedLookupSource postConsumptionOuterSource;
+
+ private final SettableFuture> probeInMemFinish = SettableFuture.create();
+
+ PartitionedLookupSource.OuterPositionTrackerFactory outerPositions;
+
/**
* Cached LookupSource on behalf of LookupJoinOperator (represented by SpillAwareLookupSourceProvider). LookupSource instantiation has non-negligible cost.
*
@@ -122,7 +134,7 @@ public final class PartitionedLookupSourceFactory
*/
private Object restoredJoinPositions;
- public PartitionedLookupSourceFactory(List types, List outputTypes, List hashChannelTypes, int partitionCount, Map layout, boolean outer)
+ public PartitionedLookupSourceFactory(List types, List outputTypes, List hashChannelTypes, int partitionCount, Map layout, boolean outer, boolean spillEnabledForOuter)
{
checkArgument(Integer.bitCount(partitionCount) == 1, "partitionCount must be a power of 2");
@@ -133,6 +145,7 @@ public final class PartitionedLookupSourceFactory
checkArgument(partitionCount > 0);
this.partitions = (Supplier[]) new Supplier>[partitionCount];
this.outer = outer;
+ this.spillEnabledForOuter = spillEnabledForOuter;
spilledLookupSource = new SpilledLookupSource(outputTypes.size());
}
@@ -232,14 +245,18 @@ public final class PartitionedLookupSourceFactory
lock.writeLock().lock();
try {
- if (destroyed.isDone()) {
+ if (partitionedConsumption.isDone() || partitionsNoLongerNeeded.isDone()) {
spilledLookupSourceHandle.dispose();
return;
}
checkState(!spilledPartitions.containsKey(partitionIndex), "Partition already set as spilled");
spilledPartitions.put(partitionIndex, spilledLookupSourceHandle);
- spillingInfo = new SpillingInfo(spillingInfo.spillEpoch() + 1, spilledPartitions.keySet());
+ Map> spillBlooms = spilledPartitions.entrySet().stream()
+ .filter(e -> e.getValue().getSpillBloom().isPresent())
+ .collect(Collectors.toMap(x -> x.getKey(),
+ x -> x.getValue().getSpillBloom().get()));
+ spillingInfo = new SpillingInfo(spillingInfo.spillEpoch() + 1, spilledPartitions.keySet(), spillBlooms);
if (partitions[partitionIndex] != null) {
// Was present and now it's spilled
@@ -258,9 +275,12 @@ public final class PartitionedLookupSourceFactory
* and to prevent probe side accessing the partition.
*/
verify(!completed, "lookupSourceSupplier already exist when completing");
- verify(!outer, "It is not possible to reset lookupSourceSupplier which is tracking for outer join");
verify(partitions.length > 1, "Spill occurred when only one partition");
- lookupSourceSupplier = createPartitionedLookupSourceSupplier(ImmutableList.copyOf(partitions), hashChannelTypes, outer, restoredJoinPositions);
+
+ /* Reinitialize the spilled partition specific outerVisitedPositions */
+ Object capturedStates = lookupSourceSupplier.captureJoinPositions();
+ lookupSourceSupplier = createPartitionedLookupSourceSupplier(ImmutableList.copyOf(partitions),
+ hashChannelTypes, outer, capturedStates);
closeCachedLookupSources();
}
else {
@@ -293,7 +313,11 @@ public final class PartitionedLookupSourceFactory
if (partitionsSet != 1) {
List> partitionList = ImmutableList.copyOf(this.partitions);
- this.lookupSourceSupplier = createPartitionedLookupSourceSupplier(partitionList, hashChannelTypes, outer, restoredJoinPositions);
+ Object capturedStates = restoredJoinPositions;
+ if (lookupSourceSupplier != null) {
+ capturedStates = lookupSourceSupplier.captureJoinPositions();
+ }
+ this.lookupSourceSupplier = createPartitionedLookupSourceSupplier(partitionList, hashChannelTypes, outer, capturedStates);
}
else if (outer) {
this.lookupSourceSupplier = createOuterLookupSourceSupplier(partitions[0], restoredJoinPositions);
@@ -320,6 +344,7 @@ public final class PartitionedLookupSourceFactory
{
lock.writeLock().lock();
try {
+ checkState(!partitionedConsumption.isDone(), "All probe operators are already finished.");
if (!spillingInfo.hasSpilled()) {
finishedProbeOperators++;
return immediateFuture(new PartitionedConsumption<>(
@@ -328,30 +353,20 @@ public final class PartitionedLookupSourceFactory
i -> {
throw new UnsupportedOperationException();
},
- i -> {}));
+ i -> {},
+ i -> {
+ throw new UnsupportedOperationException();
+ }));
}
int operatorsCount = lookupJoinsCount
.orElseThrow(() -> new IllegalStateException("A fixed distribution is required for JOIN when spilling is enabled"));
- checkState(finishedProbeOperators < operatorsCount, "%s probe operators finished out of %s declared", finishedProbeOperators + 1, operatorsCount);
if (!partitionedConsumptionParticipants.isPresent()) {
// This is the first probe to finish after anything has been spilled.
- partitionedConsumptionParticipants = OptionalInt.of(operatorsCount - finishedProbeOperators);
+ partitionedConsumptionParticipants = OptionalInt.of(finishedProbeOperators);
}
-
finishedProbeOperators++;
- if (finishedProbeOperators == operatorsCount) {
- // We can dispose partitions now since as right outer is not supported with spill
- freePartitions();
- verify(!partitionedConsumption.isDone());
- partitionedConsumption.set(new PartitionedConsumption<>(
- partitionedConsumptionParticipants.getAsInt(),
- spilledPartitions.keySet(),
- this::loadSpilledLookupSource,
- this::disposeSpilledLookupSource));
- }
-
return partitionedConsumption;
}
finally {
@@ -361,14 +376,45 @@ public final class PartitionedLookupSourceFactory
private ListenableFuture> loadSpilledLookupSource(int partitionNumber)
{
+ if (outer) {
+ verify(partitionsSet != 1);
+ ListenableFuture> lookupSupplierFuture = getSpilledLookupSourceHandle(partitionNumber).getLookupSource();
+ return Futures.transformAsync(lookupSupplierFuture,
+ lookupSource -> getUpdatedPartitionedLookupSourceSupplier(lookupSource, partitionNumber),
+ directExecutor());
+ }
return getSpilledLookupSourceHandle(partitionNumber).getLookupSource();
}
+ private ListenableFuture> getUpdatedPartitionedLookupSourceSupplier(Supplier lookupSource, int partitionNumber)
+ {
+ postConsumptionOuterSource = (PartitionedLookupSource) this.lookupSourceSupplier.getLookupSource();
+ postConsumptionOuterSource.setPartitionLookup(lookupSource.get(), partitionNumber);
+ return immediateFuture(() -> postConsumptionOuterSource);
+ }
+
+ private void loadOuterIterator(int partitionNumber)
+ {
+ if (postConsumptionOuterSource != null) {
+ /* commit all the partitions visited! */
+ postConsumptionOuterSource.close();
+ postConsumptionOuterSource = null;
+ }
+
+ this.lookupSourceSupplier.setOuterPartitionReady(partitionNumber)
+ .addListener(() -> disposeSpilledLookupSource(partitionNumber), directExecutor());
+ }
+
private void disposeSpilledLookupSource(int partitionNumber)
{
getSpilledLookupSourceHandle(partitionNumber).dispose();
}
+ private ListenableFuture> spilledLookupSourceDisposed(int partitionNumber)
+ {
+ return getSpilledLookupSourceHandle(partitionNumber).getDisposeCompleted();
+ }
+
private SpilledLookupSourceHandle getSpilledLookupSourceHandle(int partitionNumber)
{
lock.readLock().lock();
@@ -380,6 +426,59 @@ public final class PartitionedLookupSourceFactory
}
}
+ @Override
+ public ListenableFuture> whenMemProbeFinishes()
+ {
+ lock.writeLock().lock();
+ try {
+ verify(!partitionedConsumption.isDone());
+ if (!spillingInfo.hasSpilled()) {
+ partitionedConsumption.set(new PartitionedConsumption<>(
+ 1,
+ emptyList(),
+ i -> {
+ throw new UnsupportedOperationException();
+ },
+ i -> {},
+ i -> {
+ throw new UnsupportedOperationException();
+ }));
+
+ return partitionedConsumption;
+ }
+
+ if (outer) {
+ postConsumptionOuterSource = (PartitionedLookupSource) this.lookupSourceSupplier.getLookupSource();
+ partitionedConsumption.set(new PartitionedConsumption<>(
+ finishedProbeOperators - partitionedConsumptionParticipants.getAsInt(),
+ spilledPartitions.keySet(),
+ this::loadSpilledLookupSource,
+ this::loadOuterIterator,
+ this::spilledLookupSourceDisposed));
+ }
+ else {
+ freePartitions();
+ partitionedConsumption.set(new PartitionedConsumption<>(
+ finishedProbeOperators - partitionedConsumptionParticipants.getAsInt(),
+ spilledPartitions.keySet(),
+ this::loadSpilledLookupSource,
+ this::disposeSpilledLookupSource,
+ this::spilledLookupSourceDisposed));
+ }
+
+ return partitionedConsumption;
+ }
+ finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ @Override
+ public boolean isOuterEarlyStartEnabled()
+ {
+ return spillEnabledForOuter;
+ }
+
@Override
public OuterPositionIterator getOuterPositionIterator()
{
@@ -403,6 +502,9 @@ public final class PartitionedLookupSourceFactory
lock.writeLock().lock();
try {
freePartitions();
+ if (postConsumptionOuterSource != null) {
+ postConsumptionOuterSource.close();
+ }
spilledPartitions.values().forEach(SpilledLookupSourceHandle::dispose);
// Setting destroyed must be last because it's a part of the state exposed by isDestroyed() without synchronization.
@@ -519,9 +621,15 @@ public final class PartitionedLookupSourceFactory
{
return spillingInfo.getSpillMask();
}
+
+ @Override
+ public BiPredicate getSpillMatcher()
+ {
+ return spillingInfo.getSpillMatcher();
+ }
}
- private static class SpilledLookupSource
+ protected static class SpilledLookupSource
implements LookupSource
{
private final int channelCount;
@@ -603,11 +711,13 @@ public final class PartitionedLookupSourceFactory
{
private final long spillEpoch;
private final Set spilledPartitions;
+ private final ImmutableMap> spillBlooms;
- SpillingInfo(long spillEpoch, Set spilledPartitions)
+ SpillingInfo(long spillEpoch, Set spilledPartitions, Map> spillBlooms)
{
this.spillEpoch = spillEpoch;
this.spilledPartitions = ImmutableSet.copyOf(requireNonNull(spilledPartitions, "spilledPartitions is null"));
+ this.spillBlooms = ImmutableMap.copyOf(spillBlooms);
}
boolean hasSpilled()
@@ -624,6 +734,16 @@ public final class PartitionedLookupSourceFactory
{
return spilledPartitions::contains;
}
+
+ BiPredicate getSpillMatcher()
+ {
+ return (partition, rawHash) -> {
+ if (!spillBlooms.containsKey(partition)) {
+ return true;
+ }
+ return spillBlooms.get(partition).mightContain(rawHash);
+ };
+ }
}
private static class Marker
diff --git a/presto-main/src/main/java/io/prestosql/operator/SpilledLookupSourceHandle.java b/presto-main/src/main/java/io/prestosql/operator/SpilledLookupSourceHandle.java
index ddcc278ae..98f72e6ad 100644
--- a/presto-main/src/main/java/io/prestosql/operator/SpilledLookupSourceHandle.java
+++ b/presto-main/src/main/java/io/prestosql/operator/SpilledLookupSourceHandle.java
@@ -14,6 +14,7 @@
package io.prestosql.operator;
import com.google.common.collect.ImmutableList;
+import com.google.common.hash.BloomFilter;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
@@ -21,6 +22,7 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
+import java.util.Optional;
import java.util.function.Supplier;
import static com.google.common.base.Preconditions.checkState;
@@ -35,7 +37,7 @@ final class SpilledLookupSourceHandle
SPILLED,
UNSPILLING,
PRODUCED,
- DISPOSED
+ DISPOSE_REQUESTED
}
@GuardedBy("this")
@@ -48,14 +50,27 @@ final class SpilledLookupSourceHandle
private SettableFuture> unspilledLookupSource;
private final SettableFuture> disposeRequested = SettableFuture.create();
+ private final SettableFuture> disposeCompleted = SettableFuture.create();
private final ListenableFuture> unspillingOrDisposeRequested = whenAnyComplete(ImmutableList.of(unspillingRequested, disposeRequested));
+ private final Optional> spillBloom;
+
+ public SpilledLookupSourceHandle(BloomFilter bloom)
+ {
+ spillBloom = Optional.ofNullable(bloom);
+ }
+
public SettableFuture> getUnspillingRequested()
{
return unspillingRequested;
}
+ public Optional> getSpillBloom()
+ {
+ return spillBloom;
+ }
+
public synchronized ListenableFuture> getLookupSource()
{
assertState(State.SPILLED);
@@ -70,7 +85,7 @@ final class SpilledLookupSourceHandle
{
requireNonNull(lookupSource, "lookupSource is null");
- if (state == State.DISPOSED) {
+ if (state == State.DISPOSE_REQUESTED) {
return;
}
@@ -85,7 +100,7 @@ final class SpilledLookupSourceHandle
{
disposeRequested.set(null);
unspilledLookupSource = null; // let the memory go
- setState(State.DISPOSED);
+ setState(State.DISPOSE_REQUESTED);
}
public SettableFuture> getDisposeRequested()
@@ -93,6 +108,17 @@ final class SpilledLookupSourceHandle
return disposeRequested;
}
+ public synchronized void setDisposeCompleted()
+ {
+ assertState(State.DISPOSE_REQUESTED);
+ disposeCompleted.set(null);
+ }
+
+ public SettableFuture> getDisposeCompleted()
+ {
+ return disposeCompleted;
+ }
+
public ListenableFuture> getUnspillingOrDisposeRequested()
{
return unspillingOrDisposeRequested;
diff --git a/presto-main/src/main/java/io/prestosql/operator/TrackingLookupSourceSupplier.java b/presto-main/src/main/java/io/prestosql/operator/TrackingLookupSourceSupplier.java
index 9abfb0650..07784a9ce 100644
--- a/presto-main/src/main/java/io/prestosql/operator/TrackingLookupSourceSupplier.java
+++ b/presto-main/src/main/java/io/prestosql/operator/TrackingLookupSourceSupplier.java
@@ -13,6 +13,8 @@
*/
package io.prestosql.operator;
+import com.google.common.util.concurrent.ListenableFuture;
+
import java.util.function.Supplier;
import static java.util.Objects.requireNonNull;
@@ -23,6 +25,15 @@ public interface TrackingLookupSourceSupplier
OuterPositionIterator getOuterPositionIterator();
+ default ListenableFuture> setOuterPartitionReady(int partition)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ default void updateUnspilledPositions(int partition, long positions)
+ {
+ }
+
Object captureJoinPositions();
void restoreJoinPositions(Object state);
@@ -47,13 +58,14 @@ public interface TrackingLookupSourceSupplier
@Override
public Object captureJoinPositions()
{
- throw new UnsupportedOperationException();
+ /* do nothing */
+ return null;
}
@Override
public void restoreJoinPositions(Object state)
{
- throw new UnsupportedOperationException();
+ /* do nothing*/
}
};
}
diff --git a/presto-main/src/main/java/io/prestosql/spiller/GenericPartitioningSpiller.java b/presto-main/src/main/java/io/prestosql/spiller/GenericPartitioningSpiller.java
index 0fecbd09b..9f0976dfb 100644
--- a/presto-main/src/main/java/io/prestosql/spiller/GenericPartitioningSpiller.java
+++ b/presto-main/src/main/java/io/prestosql/spiller/GenericPartitioningSpiller.java
@@ -39,6 +39,8 @@ import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
+import java.util.function.BiFunction;
+import java.util.function.BiPredicate;
import java.util.function.IntPredicate;
import java.util.function.Predicate;
@@ -50,7 +52,7 @@ import static java.util.Objects.requireNonNull;
@ThreadSafe
@RestorableConfig(uncapturedFields = {"types", "partitionFunction", "closer",
- "spillerFactory", "spillContext", "memoryContext", "pageBuilders"})
+ "spillerFactory", "spillContext", "memoryContext", "pageBuilders", "getRawHash"})
public class GenericPartitioningSpiller
implements PartitioningSpiller
{
@@ -63,6 +65,7 @@ public class GenericPartitioningSpiller
private final List pageBuilders;
private final List> spillers;
+ private final BiFunction getRawHash;
private boolean readingStarted;
private final Set spilledPartitions = new HashSet<>();
@@ -72,7 +75,8 @@ public class GenericPartitioningSpiller
PartitionFunction partitionFunction,
SpillContext spillContext,
AggregatedMemoryContext memoryContext,
- SingleStreamSpillerFactory spillerFactory)
+ SingleStreamSpillerFactory spillerFactory,
+ BiFunction getRawHash)
{
requireNonNull(spillContext, "spillContext is null");
@@ -93,6 +97,7 @@ public class GenericPartitioningSpiller
spillers.add(Optional.empty());
}
this.pageBuilders = tmpPageBuilders.build();
+ this.getRawHash = getRawHash;
}
@Override
@@ -112,19 +117,25 @@ public class GenericPartitioningSpiller
@Override
public synchronized PartitioningSpillResult partitionAndSpill(Page page, IntPredicate spillPartitionMask)
+ {
+ return partitionAndSpill(page, spillPartitionMask, (ign1, ign2) -> true);
+ }
+
+ @Override
+ public PartitioningSpillResult partitionAndSpill(Page page, IntPredicate spillPartitionMask, BiPredicate spillPartitionMatcher)
{
requireNonNull(page, "page is null");
requireNonNull(spillPartitionMask, "spillPartitionMask is null");
checkArgument(page.getChannelCount() == types.size(), "Wrong page channel count, expected %s but got %s", types.size(), page.getChannelCount());
checkState(!readingStarted, "reading already started");
- IntArrayList unspilledPositions = partitionPage(page, spillPartitionMask);
+ IntArrayList unspilledPositions = partitionPage(page, spillPartitionMask, spillPartitionMatcher);
ListenableFuture> future = flushFullBuilders();
return new PartitioningSpillResult(future, page.getPositions(unspilledPositions.elements(), 0, unspilledPositions.size()));
}
- private synchronized IntArrayList partitionPage(Page page, IntPredicate spillPartitionMask)
+ private synchronized IntArrayList partitionPage(Page page, IntPredicate spillPartitionMask, BiPredicate spillPartitionMatcher)
{
IntArrayList unspilledPositions = new IntArrayList();
@@ -136,6 +147,10 @@ public class GenericPartitioningSpiller
continue;
}
+ if (getRawHash != null && !spillPartitionMatcher.test(partition, getRawHash.apply(position, page))) {
+ continue;
+ }
+
spilledPartitions.add(partition);
PageBuilder pageBuilder = pageBuilders.get(partition);
pageBuilder.declarePosition();
diff --git a/presto-main/src/main/java/io/prestosql/spiller/GenericPartitioningSpillerFactory.java b/presto-main/src/main/java/io/prestosql/spiller/GenericPartitioningSpillerFactory.java
index 941f2508f..93138d7f8 100644
--- a/presto-main/src/main/java/io/prestosql/spiller/GenericPartitioningSpillerFactory.java
+++ b/presto-main/src/main/java/io/prestosql/spiller/GenericPartitioningSpillerFactory.java
@@ -17,9 +17,11 @@ import com.google.inject.Inject;
import io.prestosql.memory.context.AggregatedMemoryContext;
import io.prestosql.operator.PartitionFunction;
import io.prestosql.operator.SpillContext;
+import io.prestosql.spi.Page;
import io.prestosql.spi.type.Type;
import java.util.List;
+import java.util.function.BiFunction;
import static java.util.Objects.requireNonNull;
@@ -41,6 +43,12 @@ public class GenericPartitioningSpillerFactory
SpillContext spillContext,
AggregatedMemoryContext memoryContext)
{
- return new GenericPartitioningSpiller(types, partitionFunction, spillContext, memoryContext, singleStreamSpillerFactory);
+ return new GenericPartitioningSpiller(types, partitionFunction, spillContext, memoryContext, singleStreamSpillerFactory, null);
+ }
+
+ @Override
+ public PartitioningSpiller create(List types, PartitionFunction partitionFunction, SpillContext spillContext, AggregatedMemoryContext memoryContext, BiFunction getRawHash)
+ {
+ return new GenericPartitioningSpiller(types, partitionFunction, spillContext, memoryContext, singleStreamSpillerFactory, getRawHash);
}
}
diff --git a/presto-main/src/main/java/io/prestosql/spiller/PartitioningSpiller.java b/presto-main/src/main/java/io/prestosql/spiller/PartitioningSpiller.java
index 5b5e1415d..3ab781e13 100644
--- a/presto-main/src/main/java/io/prestosql/spiller/PartitioningSpiller.java
+++ b/presto-main/src/main/java/io/prestosql/spiller/PartitioningSpiller.java
@@ -20,6 +20,7 @@ import io.prestosql.spi.snapshot.Restorable;
import java.io.Closeable;
import java.io.IOException;
import java.util.Iterator;
+import java.util.function.BiPredicate;
import java.util.function.IntPredicate;
import static java.util.Objects.requireNonNull;
@@ -35,6 +36,8 @@ public interface PartitioningSpiller
*/
PartitioningSpillResult partitionAndSpill(Page page, IntPredicate spillPartitionMask);
+ PartitioningSpillResult partitionAndSpill(Page page, IntPredicate spillPartitionMask, BiPredicate spillPartitionMatcher);
+
/**
* Returns iterator of previously spilled pages from given partition. Callers are expected to call
* this method once. Calling multiple times can results in undefined behavior.
diff --git a/presto-main/src/main/java/io/prestosql/spiller/PartitioningSpillerFactory.java b/presto-main/src/main/java/io/prestosql/spiller/PartitioningSpillerFactory.java
index 406798da6..eec38fd45 100644
--- a/presto-main/src/main/java/io/prestosql/spiller/PartitioningSpillerFactory.java
+++ b/presto-main/src/main/java/io/prestosql/spiller/PartitioningSpillerFactory.java
@@ -16,9 +16,11 @@ package io.prestosql.spiller;
import io.prestosql.memory.context.AggregatedMemoryContext;
import io.prestosql.operator.PartitionFunction;
import io.prestosql.operator.SpillContext;
+import io.prestosql.spi.Page;
import io.prestosql.spi.type.Type;
import java.util.List;
+import java.util.function.BiFunction;
public interface PartitioningSpillerFactory
{
@@ -28,6 +30,16 @@ public interface PartitioningSpillerFactory
SpillContext spillContext,
AggregatedMemoryContext memoryContext);
+ default PartitioningSpiller create(
+ List types,
+ PartitionFunction partitionFunction,
+ SpillContext spillContext,
+ AggregatedMemoryContext memoryContext,
+ BiFunction getRawHash)
+ {
+ return create(types, partitionFunction, spillContext, memoryContext);
+ }
+
static PartitioningSpillerFactory unsupportedPartitioningSpillerFactory()
{
return (types, partitionFunction, spillContext, memoryContext) -> {
diff --git a/presto-main/src/main/java/io/prestosql/sql/analyzer/FeaturesConfig.java b/presto-main/src/main/java/io/prestosql/sql/analyzer/FeaturesConfig.java
index 71f94a606..774375411 100644
--- a/presto-main/src/main/java/io/prestosql/sql/analyzer/FeaturesConfig.java
+++ b/presto-main/src/main/java/io/prestosql/sql/analyzer/FeaturesConfig.java
@@ -118,6 +118,8 @@ public class FeaturesConfig
private boolean spillOrderBy = true;
private boolean nonBlockingSpill;
private boolean spillWindowOperator = true;
+ private boolean spillBuildForOuterJoinEnabled;
+ private boolean innerJoinSpillFilterEnabled;
private DataSize aggregationOperatorUnspillMemoryLimit = new DataSize(4, DataSize.Unit.MEGABYTE);
private List spillerSpillPaths = ImmutableList.of();
private int spillerThreads = 4;
@@ -727,6 +729,30 @@ public class FeaturesConfig
return nonBlockingSpill;
}
+ public boolean isSpillBuildForOuterJoinEnabled()
+ {
+ return spillBuildForOuterJoinEnabled;
+ }
+
+ @Config("experimental.spill-build-for-outer-join-enabled")
+ public FeaturesConfig setSpillBuildForOuterJoinEnabled(boolean spillBuildForOuterJoinEnabled)
+ {
+ this.spillBuildForOuterJoinEnabled = spillBuildForOuterJoinEnabled;
+ return this;
+ }
+
+ public boolean isInnerJoinSpillFilterEnabled()
+ {
+ return innerJoinSpillFilterEnabled;
+ }
+
+ @Config("experimental.inner-join-spill-filter-enabled")
+ public FeaturesConfig setInnerJoinSpillFilterEnabled(boolean innerJoinSpillFilterEnabled)
+ {
+ this.innerJoinSpillFilterEnabled = innerJoinSpillFilterEnabled;
+ return this;
+ }
+
@Config("experimental.spill-order-by")
public FeaturesConfig setSpillOrderBy(boolean spillOrderBy)
{
diff --git a/presto-main/src/main/java/io/prestosql/sql/planner/LocalExecutionPlanner.java b/presto-main/src/main/java/io/prestosql/sql/planner/LocalExecutionPlanner.java
index 7b567fa48..2fae9cdfc 100644
--- a/presto-main/src/main/java/io/prestosql/sql/planner/LocalExecutionPlanner.java
+++ b/presto-main/src/main/java/io/prestosql/sql/planner/LocalExecutionPlanner.java
@@ -273,6 +273,7 @@ import static io.prestosql.SystemSessionProperties.isCrossRegionDynamicFilterEna
import static io.prestosql.SystemSessionProperties.isEnableDynamicFiltering;
import static io.prestosql.SystemSessionProperties.isNonBlockingSpillOrderby;
import static io.prestosql.SystemSessionProperties.isSpillEnabled;
+import static io.prestosql.SystemSessionProperties.isSpillForOuterJoinEnabled;
import static io.prestosql.SystemSessionProperties.isSpillOrderBy;
import static io.prestosql.SystemSessionProperties.isSpillReuseExchange;
import static io.prestosql.SystemSessionProperties.isSpillWindowOperator;
@@ -2440,7 +2441,9 @@ public class LocalExecutionPlanner
PhysicalOperation probeSource = probeNode.accept(this, context);
// Plan build
- boolean spillEnabled = isSpillEnabled(session) && node.isSpillable().orElseThrow(() -> new IllegalArgumentException("spillable not yet set"));
+ boolean spillEnabled = isSpillEnabled(session)
+ && node.isSpillable().orElseThrow(() -> new IllegalArgumentException("spillable not yet set"))
+ && probeSource.getPipelineExecutionStrategy() == UNGROUPED_EXECUTION;
JoinBridgeManager lookupSourceFactory =
createLookupSourceFactory(node, buildNode, buildSymbols, buildHashSymbol, probeSource, context, spillEnabled);
@@ -2517,6 +2520,12 @@ public class LocalExecutionPlanner
boolean buildOuter = node.getType() == RIGHT || node.getType() == FULL;
int taskCount = buildContext.getDriverInstanceCount().orElse(1);
+ /* Spill can take outer */
+ boolean canOuterSpill = isSpillForOuterJoinEnabled(session);
+ boolean spillAllowed = spillEnabled;
+ if (buildOuter && spillEnabled) {
+ spillAllowed = canOuterSpill;
+ }
Optional filterFunctionFactory = node.getFilter()
.map(filterExpression -> compileJoinFilterFunction(
@@ -2559,7 +2568,8 @@ public class LocalExecutionPlanner
.collect(toImmutableList()),
taskCount,
buildSource.getLayout(),
- buildOuter),
+ buildOuter,
+ canOuterSpill),
buildOutputTypes);
ImmutableList.Builder factoriesBuilder = new ImmutableList.Builder();
@@ -2600,7 +2610,7 @@ public class LocalExecutionPlanner
searchFunctionFactories,
10_000,
pagesIndexFactory,
- spillEnabled && !buildOuter && taskCount > 1,
+ spillAllowed && taskCount > 1,
singleStreamSpillerFactory);
factoriesBuilder.add(hashBuilderOperatorFactory);
diff --git a/presto-main/src/test/java/io/prestosql/operator/BenchmarkHashBuildAndJoinOperators.java b/presto-main/src/test/java/io/prestosql/operator/BenchmarkHashBuildAndJoinOperators.java
index e1f9eb90a..46c1d6af7 100644
--- a/presto-main/src/test/java/io/prestosql/operator/BenchmarkHashBuildAndJoinOperators.java
+++ b/presto-main/src/test/java/io/prestosql/operator/BenchmarkHashBuildAndJoinOperators.java
@@ -294,7 +294,7 @@ public class BenchmarkHashBuildAndJoinOperators
.collect(toImmutableList()),
1,
requireNonNull(ImmutableMap.of(), "layout is null"),
- false));
+ false, false));
HashBuilderOperatorFactory hashBuilderOperatorFactory = new HashBuilderOperatorFactory(
HASH_BUILD_OPERATOR_ID,
TEST_PLAN_NODE_ID,
diff --git a/presto-main/src/test/java/io/prestosql/operator/OperatorAssertion.java b/presto-main/src/test/java/io/prestosql/operator/OperatorAssertion.java
index f27ecdc96..06bc7b43c 100644
--- a/presto-main/src/test/java/io/prestosql/operator/OperatorAssertion.java
+++ b/presto-main/src/test/java/io/prestosql/operator/OperatorAssertion.java
@@ -479,6 +479,7 @@ public final class OperatorAssertion
public static List toPagesCompareStateSimple(OperatorFactory operatorFactory, DriverContext driverContext, List input, boolean revokeMemoryWhenAddingPages, Map expectedMapping)
{
try (Operator operator = operatorFactory.createOperator(driverContext)) {
+ operatorFactory.noMoreOperators(driverContext.getLifespan());
operatorFactory.noMoreOperators();
return toPagesCompareStateSimple(operator, input, revokeMemoryWhenAddingPages, expectedMapping);
}
diff --git a/presto-main/src/test/java/io/prestosql/operator/TestHashJoinOperator.java b/presto-main/src/test/java/io/prestosql/operator/TestHashJoinOperator.java
index e11c9698f..8401f49cb 100644
--- a/presto-main/src/test/java/io/prestosql/operator/TestHashJoinOperator.java
+++ b/presto-main/src/test/java/io/prestosql/operator/TestHashJoinOperator.java
@@ -51,11 +51,14 @@ import io.prestosql.sql.gen.JoinFilterFunctionCompiler.JoinFilterFunctionFactory
import io.prestosql.testing.MaterializedResult;
import io.prestosql.testing.MaterializedRow;
import io.prestosql.testing.TestingTaskContext;
+import org.roaringbitmap.RoaringBitmap;
+import org.roaringbitmap.buffer.ImmutableRoaringBitmap;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
+import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
@@ -564,6 +567,7 @@ public class TestHashJoinOperator
checkState(buildOperatorCount == whenSpill.size());
LookupSourceFactory lookupSourceFactory = lookupSourceFactoryManager.getJoinBridge(Lifespan.taskWide());
+ boolean closed = false;
try (Operator joinOperator = joinOperatorFactory.createOperator(joinDriverContext)) {
// build lookup source
ListenableFuture lookupSourceProvider = lookupSourceFactory.createLookupSourceProvider();
@@ -593,6 +597,10 @@ public class TestHashJoinOperator
runDriverInThread(executor, buildDriver);
}
+ joinOperatorFactory.noMoreOperators(joinDriverContext.getLifespan());
+ joinOperatorFactory.noMoreOperators();
+ closed = true;
+
ValuesOperatorFactory valuesOperatorFactory = new ValuesOperatorFactory(17, new PlanNodeId("values"), probePages.build());
PageBuffer pageBuffer = new PageBuffer(10);
@@ -635,7 +643,10 @@ public class TestHashJoinOperator
assertEqualsIgnoreOrder(getProperColumns(joinOperator, concat(probePages.getTypes(), buildPages.getTypes()), probePages, actualPages).getMaterializedRows(), expected.getMaterializedRows());
}
finally {
- joinOperatorFactory.noMoreOperators();
+ if (!closed) {
+ joinOperatorFactory.noMoreOperators(joinDriverContext.getLifespan());
+ joinOperatorFactory.noMoreOperators();
+ }
}
}
@@ -1512,7 +1523,7 @@ public class TestHashJoinOperator
.collect(toImmutableList()),
partitionCount,
requireNonNull(ImmutableMap.of(), "layout is null"),
- outer);
+ outer, false);
JoinBridgeManager lookupSourceFactoryManager = new JoinBridgeManager<>(
outer,
UNGROUPED_EXECUTION,
@@ -1925,9 +1936,52 @@ public class TestHashJoinOperator
matched = Booleans.asList(positions).stream().filter(e -> e).count();
}
else {
- boolean[][] positions = (boolean[][]) state;
- matched = Arrays.stream(positions).flatMap(array -> Booleans.asList(array).stream()).filter(e -> e).count();
+ ByteBuffer bb = ByteBuffer.wrap((byte[]) state);
+ List visitedPositions = new ArrayList<>();
+ for (int i = 0; i < (parallelBuild ? PARTITION_COUNT : 1); i++) {
+ ImmutableRoaringBitmap bm = new ImmutableRoaringBitmap(bb);
+ visitedPositions.add(new RoaringBitmap(bm));
+ bb.position(bb.position() + visitedPositions.get(i).serializedSizeInBytes());
+ }
+
+ matched = visitedPositions.stream().mapToLong(rr -> rr.getCardinality()).sum();
}
assertEquals(matched, 5);
}
+
+ @Test(timeOut = 30_000)
+ public void testBuildGracefulSpill()
+ throws Exception
+ {
+ TaskStateMachine taskStateMachine = new TaskStateMachine(new TaskId("query", 0, 0), executor);
+ TaskContext taskContext = TestingTaskContext.createTaskContext(executor, scheduledExecutor, TEST_SESSION, taskStateMachine);
+
+ // build factory
+ RowPagesBuilder buildPages = rowPagesBuilder(ImmutableList.of(VARCHAR, BIGINT))
+ .addSequencePage(4, 20, 200);
+
+ DummySpillerFactory buildSpillerFactory = new DummySpillerFactory();
+
+ BuildSideSetup buildSideSetup = setupBuildSide(true, taskContext, Ints.asList(0), buildPages, Optional.empty(), true, buildSpillerFactory);
+ instantiateBuildDrivers(buildSideSetup, taskContext);
+
+ JoinBridgeManager lookupSourceFactoryManager = buildSideSetup.getLookupSourceFactoryManager();
+ PartitionedLookupSourceFactory lookupSourceFactory = lookupSourceFactoryManager.getJoinBridge(Lifespan.taskWide());
+
+ // finish probe before any build partition is spilled
+ lookupSourceFactory.finishProbeOperator(OptionalInt.of(1));
+ lookupSourceFactory.whenMemProbeFinishes();
+
+ // spill build partition after probe is finished
+ HashBuilderOperator hashBuilderOperator = buildSideSetup.getBuildOperators().get(0);
+ hashBuilderOperator.startMemoryRevoke().get();
+ hashBuilderOperator.finishMemoryRevoke();
+ hashBuilderOperator.finish();
+
+ // hash builder operator should not deadlock waiting for spilled lookup source to be disposed
+ hashBuilderOperator.isBlocked().get();
+
+ lookupSourceFactory.destroy();
+ assertTrue(hashBuilderOperator.isFinished());
+ }
}
diff --git a/presto-main/src/test/java/io/prestosql/sql/analyzer/TestFeaturesConfig.java b/presto-main/src/test/java/io/prestosql/sql/analyzer/TestFeaturesConfig.java
index 98424827d..fbf683a90 100644
--- a/presto-main/src/test/java/io/prestosql/sql/analyzer/TestFeaturesConfig.java
+++ b/presto-main/src/test/java/io/prestosql/sql/analyzer/TestFeaturesConfig.java
@@ -81,6 +81,8 @@ public class TestFeaturesConfig
.setRe2JDfaRetries(5)
.setSpillEnabled(false)
.setNonBlockingSpill(false)
+ .setSpillBuildForOuterJoinEnabled(false)
+ .setInnerJoinSpillFilterEnabled(false)
.setSpillOrderBy(true)
.setSpillWindowOperator(true)
.setAggregationOperatorUnspillMemoryLimit(DataSize.valueOf("4MB"))
@@ -197,6 +199,8 @@ public class TestFeaturesConfig
.put("re2j.dfa-retries", "42")
.put("experimental.spill-enabled", "true")
.put("experimental.spill-non-blocking-orderby", "true")
+ .put("experimental.spill-build-for-outer-join-enabled", "true")
+ .put("experimental.inner-join-spill-filter-enabled", "true")
.put("experimental.spill-order-by", "false")
.put("experimental.spill-window-operator", "false")
.put("experimental.aggregation-operator-unspill-memory-limit", "100MB")
@@ -299,6 +303,8 @@ public class TestFeaturesConfig
.setRe2JDfaRetries(42)
.setSpillEnabled(true)
.setNonBlockingSpill(true)
+ .setSpillBuildForOuterJoinEnabled(true)
+ .setInnerJoinSpillFilterEnabled(true)
.setSpillOrderBy(false)
.setSpillWindowOperator(false)
.setAggregationOperatorUnspillMemoryLimit(DataSize.valueOf("100MB"))