Description
NodeServiceImpl.getRetinaList() returns one NodeInfo per hash-ring virtual-node slot instead of per physical Retina node. Since Trino builds Retina buffer splits as getRetinaList().size() × node.virtual.num, each memtable shard gets read repeatedly and rows are duplicated in query results.
In a single-Retina deployment with node.virtual.num=16, one inserted row is returned 15 times.
Root Cause
addNodeInternal puts node.virtual.num entries on the ring, each with a distinct virtualNodeId. getRetinaList() then does:
hashRing.values().stream().distinct().forEach(responseBuilder::addNodes);
NodeInfo includes virtualNodeId (proto/node.proto), so protobuf equals() never collapses entries of the same host. Each (address, i) also occupies a unique hash slot, so the values are already unique — .distinct() is a no-op.
The init log makes the intent explicit, and is likewise wrong:
logger.info("Initial hash ring loaded with {} physical nodes and {} virtual nodes.",
hashRing.values().stream().distinct().count(), bucketNum);
Evidence
Probed against a live single-Retina cluster:
node.virtual.num = 16
getRetinaList().size() = 15
distinct addresses = [<host>] // only 1 physical node
distinct virtualNodeIds = [0..9, 11..15] // vNodeId=10 lost to a hash collision
- Trino split count for a table whose only data is in the Retina buffer: 240 = 15 × 16
- The CDC sink called
updateRecord exactly once and got one ack; insertRecord(..., vNodeId) stores the row once
- Query for that key returns 15 identical rows
Because the factor comes from ring hash collisions, it is host-dependent (14/15/16), not a stable constant.
Impact
Affects unflushed Retina active-memtable data read via Trino buffer splits (retina.enable=true). Flushed object-storage files are unaffected.
Suggested Fix
Dedupe by physical address in getRetinaList(), and clear virtualNodeId in the response:
hashRing.values().stream()
.collect(Collectors.toMap(
NodeProto.NodeInfo::getAddress,
n -> n.toBuilder().clearVirtualNodeId().build(),
(a, b) -> a,
LinkedHashMap::new))
.values()
.forEach(responseBuilder::addNodes);
Fix the init log to count distinct addresses as well.
getRetinaByBucket correctly returns a single ring entry with its virtualNodeId and should not change. pixels-trino's getBufferSplits() assumes physical hosts and is correct once this API is fixed — no change needed there.
Environment
1 physical Retina node; node.virtual.num=16, node.bucket.num=128, retina.enable=true.
Description
NodeServiceImpl.getRetinaList()returns oneNodeInfoper hash-ring virtual-node slot instead of per physical Retina node. Since Trino builds Retina buffer splits asgetRetinaList().size() × node.virtual.num, each memtable shard gets read repeatedly and rows are duplicated in query results.In a single-Retina deployment with
node.virtual.num=16, one inserted row is returned 15 times.Root Cause
addNodeInternalputsnode.virtual.numentries on the ring, each with a distinctvirtualNodeId.getRetinaList()then does:NodeInfoincludesvirtualNodeId(proto/node.proto), so protobufequals()never collapses entries of the same host. Each(address, i)also occupies a unique hash slot, so the values are already unique —.distinct()is a no-op.The init log makes the intent explicit, and is likewise wrong:
Evidence
Probed against a live single-Retina cluster:
updateRecordexactly once and got one ack;insertRecord(..., vNodeId)stores the row onceBecause the factor comes from ring hash collisions, it is host-dependent (14/15/16), not a stable constant.
Impact
Affects unflushed Retina active-memtable data read via Trino buffer splits (
retina.enable=true). Flushed object-storage files are unaffected.Suggested Fix
Dedupe by physical address in
getRetinaList(), and clearvirtualNodeIdin the response:Fix the init log to count distinct addresses as well.
getRetinaByBucketcorrectly returns a single ring entry with itsvirtualNodeIdand should not change.pixels-trino'sgetBufferSplits()assumes physical hosts and is correct once this API is fixed — no change needed there.Environment
1 physical Retina node;
node.virtual.num=16,node.bucket.num=128,retina.enable=true.