Skip to content

Commit 0f8074c

Browse files
authored
[VL]Use Velox's HashTableCache to cache the BHJ's HashTable (#12163)
1 parent a1caefe commit 0f8074c

9 files changed

Lines changed: 72 additions & 38 deletions

File tree

backends-velox/src/main/java/org/apache/gluten/vectorized/HashJoinBuilder.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,12 @@ public long rtHandle() {
3535
return runtime.getHandle();
3636
}
3737

38-
public static native void clearHashTable(long hashTableData);
38+
public static native void clearHashTable(String cacheKey, long hashTableData);
3939

40-
public static native long cloneHashTable(long hashTableData);
40+
public static native long cloneHashTable(String cacheKey, long hashTableData);
4141

4242
public static native long deserializeHashTableDirect(
43-
long address, int size, boolean ignoreNullKeys, boolean joinHasNullKeys);
43+
String cacheKey, long address, int size, boolean ignoreNullKeys, boolean joinHasNullKeys);
4444

4545
public static native boolean getHashTableIgnoreNullKeys(long hashTableHandle);
4646

backends-velox/src/main/scala/org/apache/gluten/execution/HashJoinExecTransformer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ case class BroadcastHashJoinExecTransformer(
106106
right,
107107
isNullAwareAntiJoin) {
108108

109-
// Unique ID for the build side.
109+
// Unique ID for the build side
110110
lazy val buildBroadcastTableId: String = buildPlan.id.toString
111111

112112
override protected lazy val substraitJoinType: JoinRel.JoinType = joinType match {

backends-velox/src/main/scala/org/apache/gluten/execution/SerializedBroadcastHashTable.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,9 @@ class SerializedBroadcastHashTable(
7373
* @return
7474
* Hash table builder handle
7575
*/
76-
def deserialize(): Long = {
76+
def deserialize(cacheKey: String): Long = {
7777
HashJoinBuilder.deserializeHashTableDirect(
78+
cacheKey,
7879
serializedData.address(),
7980
Math.toIntExact(serializedData.size()),
8081
ignoreNullKeys,
@@ -117,6 +118,7 @@ object SerializedBroadcastHashTable {
117118
*/
118119
def fromHashTable(
119120
hashTableHandle: Long,
121+
cacheKey: String,
120122
buildSideRelation: BuildSideRelation,
121123
droppedDuplicates: Boolean,
122124
numRows: Long): SerializedBroadcastHashTable = {
@@ -148,7 +150,7 @@ object SerializedBroadcastHashTable {
148150
buildSideRelation)
149151
} finally {
150152
synchronized {
151-
HashJoinBuilder.clearHashTable(hashTableHandle)
153+
HashJoinBuilder.clearHashTable(cacheKey, hashTableHandle)
152154
}
153155
}
154156
}

backends-velox/src/main/scala/org/apache/gluten/execution/VeloxBroadcastBuildSideCache.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ object VeloxBroadcastBuildSideCache
161161
val result =
162162
SerializedBroadcastHashTable.fromHashTable(
163163
hashTableHandle,
164+
broadcastId,
164165
relation,
165166
droppedDuplicates,
166167
numRows)
@@ -197,7 +198,7 @@ object VeloxBroadcastBuildSideCache
197198
(_: String) => {
198199
logInfo(s"Deserializing hash table on executor for broadcast ID: $broadcastHashTableId")
199200
val startTime = System.currentTimeMillis()
200-
val hashTableHandle = serialized.deserialize()
201+
val hashTableHandle = serialized.deserialize(broadcastHashTableId)
201202
val timeMs = System.currentTimeMillis() - startTime
202203
deserializeHashTableTimeMetric.foreach(_ += timeMs)
203204
BroadcastHashTable(
@@ -245,7 +246,7 @@ object VeloxBroadcastBuildSideCache
245246
}
246247
}
247248

248-
HashJoinBuilder.clearHashTable(value.pointer)
249+
HashJoinBuilder.clearHashTable(key, value.pointer)
249250
}
250251
}
251252
}

backends-velox/src/main/scala/org/apache/spark/sql/execution/ColumnarBuildSideRelation.scala

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,9 @@ case class ColumnarBuildSideRelation(
242242
(hashTableData(droppedDuplicates), this, droppedDuplicates)
243243
} else {
244244
(
245-
HashJoinBuilder.cloneHashTable(hashTableData(droppedDuplicates)),
245+
HashJoinBuilder.cloneHashTable(
246+
broadcastContext.buildHashTableId,
247+
hashTableData(droppedDuplicates)),
246248
null,
247249
droppedDuplicates)
248250
}
@@ -332,7 +334,12 @@ case class ColumnarBuildSideRelation(
332334

333335
(hashTableData(droppedDuplicates), this, droppedDuplicates)
334336
} else {
335-
(HashJoinBuilder.cloneHashTable(hashTableData(droppedDuplicates)), null, droppedDuplicates)
337+
(
338+
HashJoinBuilder.cloneHashTable(
339+
broadcastContext.buildHashTableId,
340+
hashTableData(droppedDuplicates)),
341+
null,
342+
droppedDuplicates)
336343
}
337344
}
338345

backends-velox/src/main/scala/org/apache/spark/sql/execution/unsafe/UnsafeColumnarBuildSideRelation.scala

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,9 @@ class UnsafeColumnarBuildSideRelation(
214214
(hashTableData(droppedDuplicates), this, droppedDuplicates)
215215
} else {
216216
(
217-
HashJoinBuilder.cloneHashTable(hashTableData(droppedDuplicates)),
217+
HashJoinBuilder.cloneHashTable(
218+
broadcastContext.buildHashTableId,
219+
hashTableData(droppedDuplicates)),
218220
null,
219221
droppedDuplicates)
220222
}
@@ -305,7 +307,12 @@ class UnsafeColumnarBuildSideRelation(
305307

306308
(hashTableData(droppedDuplicates), this, droppedDuplicates)
307309
} else {
308-
(HashJoinBuilder.cloneHashTable(hashTableData(droppedDuplicates)), null, droppedDuplicates)
310+
(
311+
HashJoinBuilder.cloneHashTable(
312+
broadcastContext.buildHashTableId,
313+
hashTableData(droppedDuplicates)),
314+
null,
315+
droppedDuplicates)
309316
}
310317
}
311318

cpp/velox/jni/VeloxJniWrapper.cc

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
#include "velox/common/base/BloomFilter.h"
4747
#include "velox/common/file/FileSystems.h"
4848
#include "velox/exec/HashTable.h"
49+
#include "velox/exec/HashTableCache.h"
4950

5051
#ifdef GLUTEN_ENABLE_GPU
5152
#include "cudf/CudfPlanValidator.h"
@@ -949,7 +950,7 @@ JNIEXPORT jobject JNICALL Java_org_apache_gluten_execution_IcebergWriteJniWrappe
949950
JNIEXPORT jlong JNICALL Java_org_apache_gluten_vectorized_HashJoinBuilder_nativeBuild( // NOLINT
950951
JNIEnv* env,
951952
jobject wrapper,
952-
jstring /*tableId*/,
953+
jstring tableId,
953954
jlongArray batchHandles,
954955
jobjectArray joinKeys,
955956
jobjectArray filterBuildColumns,
@@ -974,6 +975,8 @@ JNIEXPORT jlong JNICALL Java_org_apache_gluten_vectorized_HashJoinBuilder_native
974975
queryConf.get<uint32_t>(kAbandonDedupHashMapMinRows, kAbandonDedupHashMapMinRowsDefault);
975976
const auto abandonHashBuildDedupMinPct =
976977
queryConf.get<uint32_t>(kAbandonDedupHashMapMinPct, kAbandonDedupHashMapMinPctDefault);
978+
const auto hashTableId = jStringToCString(env, tableId);
979+
977980
// Convert Java String array to C++ vector<string>
978981
std::vector<std::string> hashJoinKeys;
979982
jsize joinKeysCount = env->GetArrayLength(joinKeys);
@@ -1052,6 +1055,12 @@ JNIEXPORT jlong JNICALL Java_org_apache_gluten_vectorized_HashJoinBuilder_native
10521055
nullptr);
10531056
builder->setHashTable(std::move(mainTable));
10541057

1058+
auto* cache = facebook::velox::exec::HashTableCache::instance();
1059+
1060+
if (!cache->exist(hashTableId)) {
1061+
cache->add(hashTableId, builder->hashTable(), builder->joinHasNullKeys(), defaultLeafVeloxMemoryPool());
1062+
}
1063+
10551064
return gluten::getHashTableObjStore()->save(builder);
10561065
}
10571066

@@ -1134,44 +1143,69 @@ JNIEXPORT jlong JNICALL Java_org_apache_gluten_vectorized_HashJoinBuilder_native
11341143
}
11351144

11361145
hashTableBuilders[0]->setHashTable(std::move(mainTable));
1146+
1147+
auto* cache = facebook::velox::exec::HashTableCache::instance();
1148+
if (!cache->exist(hashTableId)) {
1149+
cache->add(
1150+
hashTableId,
1151+
hashTableBuilders[0]->hashTable(),
1152+
hashTableBuilders[0]->joinHasNullKeys(),
1153+
defaultLeafVeloxMemoryPool());
1154+
}
1155+
11371156
return gluten::getHashTableObjStore()->save(hashTableBuilders[0]);
11381157
JNI_METHOD_END(kInvalidObjectHandle)
11391158
}
11401159

11411160
JNIEXPORT jlong JNICALL Java_org_apache_gluten_vectorized_HashJoinBuilder_cloneHashTable( // NOLINT
11421161
JNIEnv* env,
11431162
jclass,
1163+
jstring cacheKey,
11441164
jlong tableHandler) {
11451165
JNI_METHOD_START
1166+
auto cacheKeyStr = jStringToCString(env, cacheKey);
11461167
auto hashTableHandler = ObjectStore::retrieve<gluten::HashTableBuilder>(tableHandler);
1168+
auto* cache = facebook::velox::exec::HashTableCache::instance();
1169+
if (!cache->exist(cacheKeyStr)) {
1170+
cache->add(
1171+
cacheKeyStr, hashTableHandler->hashTable(), hashTableHandler->joinHasNullKeys(), defaultLeafVeloxMemoryPool());
1172+
}
1173+
11471174
return gluten::getHashTableObjStore()->save(hashTableHandler);
11481175
JNI_METHOD_END(kInvalidObjectHandle)
11491176
}
11501177

11511178
JNIEXPORT void JNICALL Java_org_apache_gluten_vectorized_HashJoinBuilder_clearHashTable( // NOLINT
11521179
JNIEnv* env,
11531180
jclass,
1181+
jstring cacheKey,
11541182
jlong tableHandler) {
11551183
JNI_METHOD_START
1156-
auto hashTableHandler = ObjectStore::retrieve<gluten::HashTableBuilder>(tableHandler);
1157-
hashTableHandler->hashTable()->clear(true);
1184+
auto cacheKeyStr = jStringToCString(env, cacheKey);
1185+
facebook::velox::exec::HashTableCache::instance()->drop(cacheKeyStr);
11581186
ObjectStore::release(tableHandler);
11591187
JNI_METHOD_END()
11601188
}
11611189

11621190
JNIEXPORT jlong JNICALL Java_org_apache_gluten_vectorized_HashJoinBuilder_deserializeHashTableDirect( // NOLINT
11631191
JNIEnv* env,
11641192
jclass,
1193+
jstring cacheKey,
11651194
jlong address,
11661195
jint size,
11671196
jboolean ignoreNullKeys,
11681197
jboolean joinHasNullKeys) {
11691198
JNI_METHOD_START
1199+
auto cacheKeyStr = jStringToCString(env, cacheKey);
11701200
auto builder = gluten::deserializeHashTable(
11711201
reinterpret_cast<const uint8_t*>(address),
11721202
static_cast<size_t>(size),
11731203
static_cast<bool>(ignoreNullKeys),
11741204
static_cast<bool>(joinHasNullKeys));
1205+
auto* cache = facebook::velox::exec::HashTableCache::instance();
1206+
if (!cache->exist(cacheKeyStr)) {
1207+
cache->add(cacheKeyStr, builder->hashTable(), builder->joinHasNullKeys(), defaultLeafVeloxMemoryPool());
1208+
}
11751209
return gluten::getHashTableObjStore()->save(builder);
11761210
JNI_METHOD_END(kInvalidObjectHandle)
11771211
}

cpp/velox/substrait/SubstraitToVeloxPlan.cc

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -458,26 +458,6 @@ core::PlanNodePtr SubstraitToVeloxPlanConverter::toVeloxPlan(const ::substrait::
458458
} else if (
459459
sJoin.has_advanced_extension() &&
460460
SubstraitParser::configSetInOptimization(sJoin.advanced_extension(), "isBHJ=")) {
461-
std::string hashTableId = sJoin.hashtableid();
462-
463-
std::shared_ptr<core::OpaqueHashTable> opaqueSharedHashTable = nullptr;
464-
bool joinHasNullKeys = false;
465-
466-
try {
467-
auto hashTableBuilder = ObjectStore::retrieve<gluten::HashTableBuilder>(getJoin(hashTableId));
468-
joinHasNullKeys = hashTableBuilder->joinHasNullKeys();
469-
auto originalShared = hashTableBuilder->hashTable();
470-
opaqueSharedHashTable = std::shared_ptr<core::OpaqueHashTable>(
471-
originalShared, reinterpret_cast<core::OpaqueHashTable*>(originalShared.get()));
472-
473-
LOG(INFO) << "Successfully retrieved and aliased HashTable for reuse. ID: " << hashTableId;
474-
} catch (const std::exception& e) {
475-
LOG(WARNING)
476-
<< "Error retrieving HashTable from ObjectStore: " << e.what()
477-
<< ". Falling back to building new table. To ensure correct results, please verify that spark.gluten.velox.buildHashTableOncePerExecutor.enabled is set to false.";
478-
opaqueSharedHashTable = nullptr;
479-
}
480-
481461
// Create HashJoinNode node
482462
return std::make_shared<core::HashJoinNode>(
483463
nextPlanNodeId(),
@@ -489,10 +469,11 @@ core::PlanNodePtr SubstraitToVeloxPlanConverter::toVeloxPlan(const ::substrait::
489469
leftNode,
490470
rightNode,
491471
getJoinOutputType(leftNode, rightNode, joinType),
472+
true,
492473
false,
493474
false,
494-
joinHasNullKeys,
495-
opaqueSharedHashTable);
475+
nullptr,
476+
sJoin.hashtableid());
496477
} else {
497478
// Create HashJoinNode node
498479
return std::make_shared<core::HashJoinNode>(

gluten-ut/spark35/src/test/scala/org/apache/spark/sql/execution/adaptive/velox/VeloxAdaptiveQueryExecSuite.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,9 @@ class VeloxAdaptiveQueryExecSuite extends AdaptiveQueryExecSuite with GlutenSQLT
522522
withSQLConf(
523523
SQLConf.ADAPTIVE_EXECUTION_ENABLED.key -> "true",
524524
SQLConf.AUTO_BROADCASTJOIN_THRESHOLD.key -> "20000000",
525-
SQLConf.SUBQUERY_REUSE_ENABLED.key -> "false") {
525+
SQLConf.ADAPTIVE_OPTIMIZER_EXCLUDED_RULES.key -> AQEPropagateEmptyRelation.ruleName,
526+
SQLConf.SUBQUERY_REUSE_ENABLED.key -> "false"
527+
) {
526528
val (plan, adaptivePlan) = runAdaptiveAndVerifyResult(
527529
"SELECT a FROM testData join testData2 ON key = a " +
528530
"where value >= (" +

0 commit comments

Comments
 (0)