yujun777 commented on code in PR #68648:
URL: https://github.com/apache/doris/pull/68648#discussion_r4225595317
##########
fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescTransferGenerator.java:
##########
@@ -46,7 +47,8 @@ public class MTMVRelatedPartitionDescTransferGenerator
implements MTMVRelatedPar
public void apply(MTMVPartitionInfo mvPartitionInfo, Map<String, String>
mvProperties,
RelatedPartitionDescResult lastResult, List<Column>
partitionColumns,
Map<List<String>, Set<String>> queryUsedPartitionMap)
throws AnalysisException {
- Map<MTMVRelatedTableIf, Map<PartitionKeyDesc, Set<String>>> descs =
lastResult.getDescs();
+ Map<MTMVRelatedTableIf, Map<PartitionKeyDesc, Set<String>>> descs =
+ mergeOverlappingListDescs(lastResult.getDescs());
Map<PartitionKeyDesc, Map<MTMVRelatedTableIf, Set<String>>> res =
Maps.newHashMap();
Review Comment:
Fixed in 65267ad3698. You are right that merging per table left a
multi-table MV unbuildable: t9's partitions projecting to 2020-2022 and t10's
to 2021-2022 came out as {2020,2021,2022} beside {2021,2022}, and
`checkIntersect` rejects exactly that (the partition creation would repeat a
key for any MV as well).
The keys are now grouped across every table, with path compression over the
keys, and the one partition that holds a group's keys names every table's
partitions of them -- which is what a refresh reads and records for those keys.
Covered by unit tests: the existing t6/t7 case used to raise `PartitionValue is
repeat` and now yields the two MV partitions {1,3} and {2}, each naming both
tables' partitions; the new case is your shape (t9 covering 2020-2022, t10
covering 2021-2022 -> one partition holding all three keys, naming t9's two
partitions and t10's one).
The two-table default/window fixture you ask for on top of that is the same
key question, and it is the next comment's.
##########
fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescTransferGenerator.java:
##########
@@ -65,6 +70,68 @@ public void apply(MTMVPartitionInfo mvPartitionInfo,
Map<String, String> mvPrope
lastResult.setRes(res);
}
+ /**
+ * One MV partition per set of keys, not one per way of writing a set
down. A partition of a list
+ * partitioned base table can hold several keys of the MV's partition
column, so two of them can describe
+ * keys that meet: an expired partition holding one key of a retained
partition's key list, for instance.
+ * An MV's own partitions cannot overlap, so descs whose keys meet are one
partition whose keys are the
+ * union of theirs. Without this an MV over such a table cannot be built
at all -- its partition items
+ * would repeat a key -- which is the shape a default-partition table is
left unwindowed into, and the
+ * one it is recorded with changes with it: the merged partition names
every base partition of the keys
+ * it covers, which is what a refresh reads for it.
+ *
+ * <p>Descs whose keys are disjoint, one desc per table, and every desc
that is not a list of keys are
+ * left exactly as they were, so an MV whose base partitions do not meet
keeps its partitions and their
+ * names.
+ */
+ private Map<MTMVRelatedTableIf, Map<PartitionKeyDesc, Set<String>>>
mergeOverlappingListDescs(
+ Map<MTMVRelatedTableIf, Map<PartitionKeyDesc, Set<String>>> descs)
{
+ Map<MTMVRelatedTableIf, Map<PartitionKeyDesc, Set<String>>> res =
Maps.newHashMap();
+ for (Entry<MTMVRelatedTableIf, Map<PartitionKeyDesc, Set<String>>>
entry : descs.entrySet()) {
+ res.put(entry.getKey(),
mergeOverlappingListDescsOfOneTable(entry.getValue()));
+ }
+ return res;
+ }
+
+ private Map<PartitionKeyDesc, Set<String>>
mergeOverlappingListDescsOfOneTable(
+ Map<PartitionKeyDesc, Set<String>> descs) {
+ Map<PartitionKeyDesc, Set<String>> res = Maps.newHashMap();
+ List<Set<List<PartitionValue>>> mergedKeys = Lists.newArrayList();
+ List<Set<String>> mergedNames = Lists.newArrayList();
+ List<PartitionKeyDesc> mergedDescs = Lists.newArrayList();
+ for (Entry<PartitionKeyDesc, Set<String>> entry : descs.entrySet()) {
+ if (!entry.getKey().hasInValues()) {
+ res.put(entry.getKey(), entry.getValue());
+ continue;
+ }
+ Set<List<PartitionValue>> keys =
Sets.newHashSet(entry.getKey().getInValues());
+ Set<String> names = Sets.newHashSet(entry.getValue());
+ // The desc this group came from, kept while it is the only one,
since a desc that was not
+ // merged is left as it is rather than written out again in
another key order.
+ PartitionKeyDesc mergedDesc = entry.getKey();
+ for (int i = mergedKeys.size() - 1; i >= 0; i--) {
+ if (Collections.disjoint(mergedKeys.get(i), keys)) {
Review Comment:
Fixed in 65267ad3698, by the shape you suggest: the keys are indexed to
their group and only the groups that share a key are unioned (path compression
on the way), so each key is looked up once instead of every prior group being
scanned for every desc. The disjointness of the result is what the union-find
maintains: two descs that share a key end up in one group, so the groups -- and
therefore the MV's partitions -- cannot repeat a key.
The grace-period point is taken as well: the mapping is built on the rewrite
path whether or not anything is compared, so the cost of building it is worth
bounding; it is now one hash lookup per key of each desc.
##########
fe/fe-core/src/main/java/org/apache/doris/catalog/MTMV.java:
##########
@@ -1619,6 +1621,65 @@ public Map<String, Map<MTMVRelatedTableIf, Set<String>>>
calculatePartitionMappi
return res;
}
+ /**
+ * The list partition each base table of this MV has that takes the rows
no other partition of it claims,
+ * by table, or none for a table that has no such partition.
+ *
+ * <p>Read once per mapping rather than per MV partition: the mapping
describes every MV partition and the
+ * answer is the table's, not the partition's. The table's partitions are
read under its read lock, so
+ * that a concurrent ADD or DROP PARTITION cannot be seen half applied --
its name list and the items the
+ * walk resolves against it have to come from one state of the table --
and so that this walk is not one
+ * more reader of a tree another thread is modifying.
+ */
+ private Map<MTMVRelatedTableIf, String> defaultListPartitionsOf() throws
AnalysisException {
+ Map<MTMVRelatedTableIf, String> res = Maps.newHashMap();
+ for (MTMVRelatedTableIf pctTable : mvPartitionInfo.getPctTables()) {
+ if (!(pctTable instanceof OlapTable)) {
+ continue;
+ }
+ OlapTable olapTable = (OlapTable) pctTable;
+ if (!(olapTable.getPartitionInfo() instanceof ListPartitionInfo)) {
+ continue;
+ }
+ olapTable.readLock();
+ try {
+ for (String partitionName : olapTable.getPartitionNames()) {
+ if
(olapTable.getPartitionItemOrAnalysisException(partitionName).isDefaultPartition())
{
+ res.put(pctTable, partitionName);
+ break;
+ }
+ }
+ } finally {
+ olapTable.readUnlock();
+ }
+ }
+ return res;
+ }
+
+ /**
+ * One MV partition's mapping, with every base table's default list
partition named in it.
+ *
+ * <p>Such a partition holds rows for every key its table can be read by,
so it belongs to every MV
+ * partition that reads the table -- not only to the one its own key, the
sentinel those rows were placed
+ * by, maps to. Naming it everywhere is what the read and the record have
to agree on: the refresh reads
+ * the rows of it that belong to the MV partition being refreshed, and the
partition is recorded among the
+ * ones that partition is read through, so an insert into it leaves that
MV partition out of sync instead
+ * of changing nothing the MV compares.
+ */
+ private Map<MTMVRelatedTableIf, Set<String>> withDefaultListPartitions(
+ Map<MTMVRelatedTableIf, Set<String>> mapping,
Map<MTMVRelatedTableIf, String> defaultListPartitions) {
+ if (defaultListPartitions.isEmpty()) {
+ return mapping;
+ }
+ Map<MTMVRelatedTableIf, Set<String>> res = Maps.newHashMap(mapping);
+ for (Entry<MTMVRelatedTableIf, String> entry :
defaultListPartitions.entrySet()) {
+ Set<String> partitions =
Sets.newHashSet(res.getOrDefault(entry.getKey(), Sets.newHashSet()));
+ partitions.add(entry.getValue());
+ res.put(entry.getKey(), partitions);
Review Comment:
Confirmed, and I agree it is a representation gap rather than anything this
PR introduced -- but I do not think it is reachable by the direction this PR
takes, so I have left it alone this round rather than guessed.
Why the merge and the read-scope work do not reach it: the MV's own
partition set comes from the base partitions' keys projected onto the MV's
partition column. A key that only ever lived in the default partition has no
base partition describing it, so it has no MV partition -- and the refresh
reads each MV partition through its own key range, so a key with no range is
read by nobody. That is your k=2: `LIST(k)` with `p1=(1)` and `p_default`, a
committed `k=2` row in `p_default`, an MV partitioned by `k` whose partitions
are key 1 and the sentinel one. Reading the row is not the hard part; routing
it is: `INSERT OVERWRITE` puts a row in the MV partition its key names, and
there is no such partition to put it in. Materializing it into the sentinel
partition would make it invisible to a query for k=2 that prunes to the MV's
partitions.
So it is one of two deliberate choices, and both are user-visible enough
that I would rather ask than take one:
1. Refuse the rewrite for a materialized view whose PCT table has a list
partition's default partition. The MV keeps working for direct queries and
keeps refreshing; queries over that table are answered from the base table.
Narrow, and it cannot serve a row it does not hold; the cost is losing the
rewrite for these MVs.
2. Refuse the shape at `CREATE` -- an MV partitioned on a base table that
has a list partition's default partition -- which is what r4140476259 offered
as the alternative when the fan-out went in. Cheaper to keep sound, but it
takes away the shape rather than the rewrite.
I did not touch either this round. If you want one in this PR, 1 is the
smaller change and the one I would take; tell me and I will do it in the next
round.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]