Memory connector: Handle null partitions correctly
This commit is contained in:
parent
98e7e914c6
commit
46ba508d45
|
|
@ -87,7 +87,7 @@ public class MemoryDataFragment
|
|||
return MEMORY_DATA_FRAGMENT_CODEC.fromJson(fragment.getBytes());
|
||||
}
|
||||
|
||||
public static Map<String, List<Integer>> getMergedCount(Map<String, List<Integer>> a, Map<String, List<Integer>> b)
|
||||
public static Map<String, List<Integer>> getMergedPartitionMap(Map<String, List<Integer>> a, Map<String, List<Integer>> b)
|
||||
{
|
||||
Map<String, List<Integer>> result = new HashMap<>();
|
||||
for (Map.Entry<String, List<Integer>> entry : a.entrySet()) {
|
||||
|
|
@ -113,7 +113,7 @@ public class MemoryDataFragment
|
|||
return new MemoryDataFragment(a.getHostAddress(), a.getRows() + b.getRows(), Math.max(a.getLogicalPartCount(), b.getLogicalPartCount()), Collections.emptyMap());
|
||||
}
|
||||
else {
|
||||
return new MemoryDataFragment(a.getHostAddress(), a.getRows() + b.getRows(), 0, getMergedCount(a.getLogicalPartPartitionMap(), b.getLogicalPartPartitionMap()));
|
||||
return new MemoryDataFragment(a.getHostAddress(), a.getRows() + b.getRows(), Math.max(a.getLogicalPartCount(), b.getLogicalPartCount()), getMergedPartitionMap(a.getLogicalPartPartitionMap(), b.getLogicalPartPartitionMap()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import java.util.Map;
|
|||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static io.prestosql.plugin.memory.MemoryErrorCode.MISSING_DATA;
|
||||
|
|
@ -176,9 +177,15 @@ public class MemoryPageSinkProvider
|
|||
@Override
|
||||
public CompletableFuture<Collection<Slice>> finish()
|
||||
{
|
||||
// there could be multiple writers in parallel, wait until the last one
|
||||
if (sinkCount.decrementAndGet() == 0) {
|
||||
tablesManager.finishUpdatingTable(tableId);
|
||||
Map<String, List<Integer>> logicalPartPartitionMap = tablesManager.getTableLogicalPartPartitionMap(tableId);
|
||||
// the JSON parser can't handle null keys in a map, so they must be skipped
|
||||
// see LogicalPart#partitionPage and MemorySplitManager#getSplits for details
|
||||
Map<String, List<Integer>> logicalPartPartitionMap =
|
||||
tablesManager.getTableLogicalPartPartitionMap(tableId).entrySet().stream()
|
||||
.filter(e -> e.getKey() != null)
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
return completedFuture(ImmutableList.of(new MemoryDataFragment(currentHostAddress, addedRows, tablesManager.getTableLogicalPartCount(tableId), logicalPartPartitionMap).toSlice()));
|
||||
}
|
||||
return completedFuture(ImmutableList.of(new MemoryDataFragment(currentHostAddress, addedRows, 0, Collections.emptyMap()).toSlice()));
|
||||
|
|
|
|||
|
|
@ -53,53 +53,79 @@ public final class MemorySplitManager
|
|||
|
||||
List<MemoryDataFragment> dataFragments = metadata.getDataFragments(table.getId());
|
||||
|
||||
// check if there is a predicate on the partition column
|
||||
List<SortedRangeSet> partitionKeyRanges = new ArrayList<>();
|
||||
for (Map.Entry<ColumnHandle, Domain> e : table.getPredicate().getDomains().orElse(Collections.emptyMap()).entrySet()) {
|
||||
if (!e.getKey().isPartitionKey()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// in LogicalPart#partitionPage null partition is a special case
|
||||
// although the partition map in LogicalPart class can have null keys
|
||||
// when the map is sent to coordinator via MemoryDataFragment the null
|
||||
// keys are skipped because the JSON parser can't handle null keys in a map
|
||||
// therefore when query predicate contains a null value, we MUST NOT
|
||||
// do any partition filtering because we would miss data
|
||||
//
|
||||
// e.g. if query is: select * from table where column is null
|
||||
// then schedule all the splits
|
||||
//
|
||||
// see comment in LogicalPart#partitionPage also
|
||||
if (e.getValue().isNullAllowed()) {
|
||||
return allSplits(dataFragments, table);
|
||||
}
|
||||
SortedRangeSet rangeSet = ((SortedRangeSet) e.getValue().getValues());
|
||||
partitionKeyRanges.add(rangeSet);
|
||||
}
|
||||
|
||||
ImmutableList.Builder<ConnectorSplit> splits = ImmutableList.builder();
|
||||
|
||||
if (partitionKeyRanges.isEmpty()) {
|
||||
return allSplits(dataFragments, table);
|
||||
}
|
||||
|
||||
for (MemoryDataFragment dataFragment : dataFragments) {
|
||||
Map<String, List<Integer>> logicalPartPartitionMap = dataFragment.getLogicalPartPartitionMap();
|
||||
int logicalPartCount = dataFragment.getLogicalPartCount();
|
||||
long rows = dataFragment.getRows();
|
||||
|
||||
// logicalPart ids are 1 based
|
||||
if (logicalPartPartitionMap.size() == 0) {
|
||||
int logicalPartCount = dataFragment.getLogicalPartCount();
|
||||
// logicalPart ids are 1 based
|
||||
for (int i = 1; i <= logicalPartCount; i++) {
|
||||
splits.add(new MemorySplit(table.getId(), i, dataFragment.getHostAddress(), rows, OptionalLong.empty()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
// get the predicate from the MemoryTableHandle
|
||||
// filter the splits based on the partitionKey and only schedule them
|
||||
List<SortedRangeSet> partitionKeyRanges = new ArrayList<>();
|
||||
|
||||
// get the type of the column here so that the partitionKeyValue(Object) can be cast
|
||||
for (Map.Entry<ColumnHandle, Domain> e : table.getPredicate().getDomains().orElse(Collections.emptyMap()).entrySet()) {
|
||||
if (!e.getKey().isPartitionKey()) {
|
||||
continue;
|
||||
}
|
||||
SortedRangeSet rangeSet = ((SortedRangeSet) e.getValue().getValues());
|
||||
partitionKeyRanges.add(rangeSet);
|
||||
}
|
||||
|
||||
if (partitionKeyRanges.size() > 0) {
|
||||
for (Map.Entry<String, List<Integer>> entry : logicalPartPartitionMap.entrySet()) {
|
||||
for (SortedRangeSet rangeSet : partitionKeyRanges) {
|
||||
Type rangeSetType = rangeSet.getType();
|
||||
Object value = LogicalPart.deserializeTypedValueFromString(rangeSetType, entry.getKey());
|
||||
if (rangeSet.containsValue(value)) {
|
||||
for (Integer i : entry.getValue()) {
|
||||
splits.add(new MemorySplit(table.getId(), i, dataFragment.getHostAddress(), rows, OptionalLong.empty()));
|
||||
}
|
||||
for (Map.Entry<String, List<Integer>> entry : logicalPartPartitionMap.entrySet()) {
|
||||
for (SortedRangeSet rangeSet : partitionKeyRanges) {
|
||||
Type rangeSetType = rangeSet.getType();
|
||||
Object value = LogicalPart.deserializeTypedValueFromString(rangeSetType, entry.getKey());
|
||||
if (rangeSet.containsValue(value)) {
|
||||
for (Integer i : entry.getValue()) {
|
||||
splits.add(new MemorySplit(table.getId(), i, dataFragment.getHostAddress(), rows, OptionalLong.empty()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (Map.Entry<String, List<Integer>> entry : logicalPartPartitionMap.entrySet()) {
|
||||
for (Integer i : entry.getValue()) {
|
||||
splits.add(new MemorySplit(table.getId(), i, dataFragment.getHostAddress(), rows, OptionalLong.empty()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return new FixedSplitSource(splits.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule entire table
|
||||
*/
|
||||
private ConnectorSplitSource allSplits(List<MemoryDataFragment> dataFragments, MemoryTableHandle table)
|
||||
{
|
||||
ImmutableList.Builder<ConnectorSplit> splits = ImmutableList.builder();
|
||||
|
||||
for (MemoryDataFragment dataFragment : dataFragments) {
|
||||
int logicalPartCount = dataFragment.getLogicalPartCount();
|
||||
long rows = dataFragment.getRows();
|
||||
// logicalPart ids are 1 based
|
||||
for (int i = 1; i <= logicalPartCount; i++) {
|
||||
splits.add(new MemorySplit(table.getId(), i, dataFragment.getHostAddress(), rows, OptionalLong.empty()));
|
||||
}
|
||||
}
|
||||
return new FixedSplitSource(splits.build());
|
||||
|
|
|
|||
|
|
@ -951,7 +951,20 @@ public class LogicalPart
|
|||
int[] retainedPositions = valueAndPosition.getValue().stream().mapToInt(i -> i).toArray();
|
||||
Object valueKey = valueAndPosition.getKey();
|
||||
Page subPage = page.getPositions(retainedPositions, 0, retainedPositions.length);
|
||||
partitions.put(valueKey.toString(), subPage);
|
||||
// NOTE: null partition key is allowed here in the map
|
||||
// but when this partition map is sent to coordinator via MemoryDataFragment
|
||||
// the JSON parser fails and can't handle null keys in the map
|
||||
// the JSON parser will ignore null keys
|
||||
// therefore during scheduling if the query predicate is for null
|
||||
// we MUST NOT do any partition filtering because the partition map
|
||||
// the coordinator has is missing null partitions
|
||||
// the coordinator must schedule all splits if the query predicate is null
|
||||
// see: MemorySplitManager#getSplits
|
||||
//
|
||||
// note: the other option is to use an empty string as the null key
|
||||
// then the JSON parser could send the key to the coordinator
|
||||
// but then this would cause conflicts with actual empty string values
|
||||
partitions.put(valueKey == null ? null : valueKey.toString(), subPage);
|
||||
}
|
||||
return partitions;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue