Skip to content

Conversation

@binshi-bing
Copy link
Contributor

@binshi-bing binshi-bing commented Apr 11, 2019

@dbwong please review this early draft. Haven't completely sorted out local index and cross region boundary condition when generating scan. Still adding unit test and integration test and trying to pass the tests. Please focus on main logic, data structure, main algorithms and interfaces first. The main data structure change is in GuidePostsInfo for now.

@karanmehta93
Copy link
Member

@BinShi-SecularBird can you fix the conflicts?

Copy link
Contributor

@dbwong dbwong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initial high level review. Generally okay with the high level picture but I think the tree classes need unit tests badly before I review in detail.

// The size of the thread pool used for refreshing cached table stats in stats client cache
public static final String STATS_CACHE_THREAD_POOL_SIZE = "phoenix.stats.cache.threadPoolSize";
// The targeted size of the guide post chunk measured in the number of guide posts.
public static final String STATS_TARGETED_CHUNK_SIZE = "phoenix.stats.targeted.chunk.size";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t think this should be on the query level and Should be on the table level sicne we won't be collecting stats for the table multiple times ideally. With you guidepost chunk shouldn't this match the collected size? Is this for a 1 size chunk for backward compatibility?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a cluster configuration applied to all queries and all tables. Isn't it the right place to add a cluster configuration?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmm what about the case where the chunk size here is > the table guide post size for example will you combine chunks?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we are doing it on the table level, we have to add an extra column to store this chunk size. Adding more and more columns on the syscat is not a good idea, what's the best practice here @dbwong ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chunk size is max(1, count of guide posts).

/**
* A guide post chunk is comprised of a group of guide posts, and it has one of key ranges below:
* (UNBOUND, gp_i0], (gp_i0, gp_i1], (gp_i1, gp_i2], ..., (gp_in, gp_n], (gp_n, UNBOUND)
* where gp_x is one of guide post collected on the server side. The last guide post chunk is a DUMMY chunk
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let’s call it something other than dummy maybe residual? And describe that it covers the key range not covered by the guideposts

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed name to ending chunk.

Construct(this.guidePostChunks, 0, n - 1, 0);
} else {
this.treeSize = 0;
this.nodes = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dislike the use of null for presence consider Optional.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't need to check whether nodes is null and there is no nested check for null object, so use Optional class is overkilled here.

*/
public boolean trackGuidePost(ImmutableBytesWritable row, long byteCount, long rowCount,
long updateTimestamp) {
public boolean trackGuidePost(ImmutableBytesWritable row, long byteCount, long rowCount, long updateTimestamp) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't really following the builder pattern. consider rename or handling through exceptions or changing the caller.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This intend not to use builder pattern here. No need to use builder pattern.

/**
* The index of the guide post chunk in the chunk array.
*/
private final int guidePostChunkIndex;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be leaking the top level classes lower into this class. The accessor is never used. Can you briefly explain why this index is needed to be tracked in multiple levels? InnerPointLookupResult in GuidePostsInfo even has 2 separate indexes tracked.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used in DecodedGuidePostCache to track the guide post chunks in the cache.

@binshi-bing
Copy link
Contributor Author

@dbwong , could you please review BaseResultIterators.getParallelScans()? All the required changes should be there now. BaseResultIterators.getRowKeyRanges() contains the logic of generating query key ranges for local index.

Copy link
Contributor

@dbwong dbwong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initial thoughts feedback on the code in BaseResultIterators @BinShi-SecularBird

// We'll never have a case where a table is both salted and local.
assert !(isSalted && isLocalIndex);

Long pageLimit = getUnfilteredPageLimit(scan);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a thought for a followup JIRA. If pageLimit approaches the size of our stored guideposts we can probably improve our estimate from a combination of data.

boolean crossesRegionBoundary = false;

if (j == keyRangeCount - 1) {
crossesRegionBoundary = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is crossesRegionBoundary was only needed for LocalIndexes or Salted Tables. Maybe we should rearrange to indicate? It is also used in addNewScan to force add the scan without useStatsForParallelization, but again I believe that should only be true for local indexes or salted tables. Ideally maybe we remove the logic there in addNewScan?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to keep the same semantics as the old code. See line 1102 "Scan newScan = scanRanges.intersectScan(scan, currentKeyBytes, endKey, keyOffset, true);" in BaseResultIterators.getParallelScans() in original code.

}
regionIndex++;

regionScans = addNewScan(parallelScans, regionScans, newScan, endKey, crossesRegionBoundary, regionLocation);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets discuss the naming and usage a bit of this as in ParallelScanGrouper this is "startKey". This is used for the crossesPrefixBoundary check and indicated in the interface as a startKey and compared to the last scans startRow Prefix looking for for example the salt byte changing or region changing. By using end key is there any cases we might miss?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let double check this. This is exactly one of the places where we need to pay attention to. Hopefully I'll add test cases to cover the scenario.

Bin and others added 3 commits May 3, 2019 15:52
@binshi-bing binshi-bing changed the title PHOENIX-4925 Use Segment tree to organize Guide Post Info PHOENIX-4925 Use a Variant Segment tree to organize Guide Post Info May 3, 2019
}

@Override
public boolean equals(Object o) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In java typically if you need a custom equals you need a custom hashcode function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that it's not necessary. I can see my equals function is called for comparison.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://stackoverflow.com/questions/30026097/equals-without-hashcode

ppl believe without hashcode function might result in weird bugs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added hashCode function, although the bug exists when they are used for hash containers and these objects shouldn't be used for hash containers.

scanStartKey = ScanUtil.getMinKey(schema, newSkipScanFilter.getSlots(), slotSpan);
scanStopKey = ScanUtil.getMaxKey(schema, newSkipScanFilter.getSlots(), slotSpan);
scanStartKey = ScanUtil.getMinKey(
newSkipScanFilter.getSchema(), newSkipScanFilter.getSlots(), newSkipScanFilter.getSlotSpan());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we made this change? Whats' the diff btw 'RowKeySchema' from the ScanRangers and SkipScanFilter.getSchema()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the PK column type is varchar, the column is translated to multiple char[1] columns in SkipScanFilter's schema, and the SkipScanFilter's schema is supposed to use here, otherwise it will cause the test failures in SkipScanFilterIntersectTest.java.


final boolean isStatsEnabled = config.getBoolean(STATS_COLLECTION_ENABLED, DEFAULT_STATS_COLLECTION_ENABLED);
if (queryServices == null || !isStatsEnabled) {
if (queryServices == null || ! isStatsEnabled) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra space?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I always add an extra space between ! and the variable for better readness. I saw the other places use this coding style too.

assertFalse(rs.next());
// Hit the first guide post.
info = getByteRowEstimates(conn, sql, binds);
estimatedRows = 10;
Copy link
Contributor

@yanxinyi yanxinyi Jun 28, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By default, one guide post refers to 10 rows?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the query will hit the first guide post which has key 100. This guide post contains 10 estimated rows.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants