Skip to content

Commit 74293de

Browse files
committed
[VL][Delta] Address DV handoff review feedback
1 parent 55365da commit 74293de

5 files changed

Lines changed: 222 additions & 140 deletions

File tree

backends-velox/src-delta/main/scala/org/apache/gluten/component/VeloxDeltaComponent.scala

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,12 @@ package org.apache.gluten.component
1818

1919
import org.apache.gluten.backendsapi.velox.VeloxBackend
2020
import org.apache.gluten.config.GlutenConfig
21-
import org.apache.gluten.extension.{DeltaPostTransformRules, OffloadDeltaFilter, OffloadDeltaProject, OffloadDeltaScan}
21+
import org.apache.gluten.extension.{DeltaPostTransformRules, OffloadDeltaFilter, OffloadDeltaProject, OffloadDeltaScan, PreprocessDeltaScanWithDeletionVectors}
2222
import org.apache.gluten.extension.columnar.heuristic.HeuristicTransform
2323
import org.apache.gluten.extension.columnar.validator.Validators
2424
import org.apache.gluten.extension.injector.Injector
2525

26-
import org.apache.spark.sql.SparkSession
27-
import org.apache.spark.sql.catalyst.plans.logical.LogicalPlan
28-
import org.apache.spark.sql.catalyst.rules.Rule
29-
import org.apache.spark.util.SparkReflectionUtil
30-
3126
class VeloxDeltaComponent extends Component {
32-
private val deltaDvPreprocessRuleClassName =
33-
"org.apache.gluten.extension.PreprocessDeltaScanWithDeletionVectors"
34-
3527
override def name(): String = "velox-delta"
3628

3729
override def dependencies(): Seq[Class[_ <: Component]] = classOf[VeloxBackend] :: Nil
@@ -42,7 +34,7 @@ class VeloxDeltaComponent extends Component {
4234

4335
override def injectRules(injector: Injector): Unit = {
4436
val legacy = injector.gluten.legacy
45-
injector.spark.injectOptimizerRule(deltaDvPreprocessRule)
37+
injector.spark.injectOptimizerRule(spark => new PreprocessDeltaScanWithDeletionVectors(spark))
4638
legacy.injectTransform {
4739
c =>
4840
val offload = Seq(OffloadDeltaScan(), OffloadDeltaProject(), OffloadDeltaFilter())
@@ -53,22 +45,4 @@ class VeloxDeltaComponent extends Component {
5345
}
5446
DeltaPostTransformRules.rules.foreach(r => legacy.injectPostTransform(_ => r))
5547
}
56-
57-
private def deltaDvPreprocessRule(spark: SparkSession): Rule[LogicalPlan] = {
58-
if (!SparkReflectionUtil.isClassPresent(deltaDvPreprocessRuleClassName)) {
59-
return VeloxDeltaComponent.IdentityRule
60-
}
61-
62-
Class
63-
.forName(deltaDvPreprocessRuleClassName)
64-
.getConstructor(classOf[SparkSession])
65-
.newInstance(spark)
66-
.asInstanceOf[Rule[LogicalPlan]]
67-
}
68-
}
69-
70-
object VeloxDeltaComponent {
71-
private object IdentityRule extends Rule[LogicalPlan] {
72-
override def apply(plan: LogicalPlan): LogicalPlan = plan
73-
}
7448
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.gluten.extension
18+
19+
import org.apache.spark.sql.SparkSession
20+
import org.apache.spark.sql.catalyst.plans.logical.LogicalPlan
21+
import org.apache.spark.sql.catalyst.rules.Rule
22+
23+
/**
24+
* Delta 4 prepares DV scan metadata before Gluten offload, so this compatibility rule does not need
25+
* to add backend-visible DV columns.
26+
*/
27+
class PreprocessDeltaScanWithDeletionVectors(protected val spark: SparkSession)
28+
extends Rule[LogicalPlan] {
29+
30+
override def apply(plan: LogicalPlan): LogicalPlan = plan
31+
}
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.gluten.backendsapi.velox
18+
19+
import org.apache.gluten.substrait.rel.DeltaLocalFilesNode.{DeltaFileReadOptions, RowIndexFilterType}
20+
21+
import org.apache.spark.sql.SparkSession
22+
import org.apache.spark.sql.execution.datasources.PartitionedFile
23+
24+
import java.lang.{Long => JLong}
25+
import java.lang.reflect.Method
26+
import java.util.concurrent.ConcurrentHashMap
27+
28+
import scala.collection.JavaConverters._
29+
30+
private[velox] object DeltaSplitMetadataExtractor {
31+
type NormalizedDeltaSplitMetadata =
32+
(Seq[java.util.Map[String, Object]], Seq[DeltaFileReadOptions])
33+
34+
private val DeltaScanInfoClassName = "org.apache.gluten.delta.DeltaDeletionVectorScanInfo$"
35+
private val scanInfoMethodsCache = new ConcurrentHashMap[Class[_], ScanInfoMethods]()
36+
private val deletionVectorInfoMethodsCache =
37+
new ConcurrentHashMap[Class[_], DeletionVectorInfoMethods]()
38+
39+
private lazy val deltaScanInfoReflection: Option[DeltaScanInfoReflection] = {
40+
try {
41+
// DeltaDeletionVectorScanInfo is compiled only in Delta-enabled source profiles. Keep this
42+
// bridge isolated and cached; all directly linkable Gluten classes should be referenced
43+
// without reflection.
44+
// scalastyle:off classforname
45+
val moduleClass = Class.forName(DeltaScanInfoClassName)
46+
// scalastyle:on classforname
47+
val module = moduleClass.getField("MODULE$").get(null)
48+
val extractAllMethod = moduleClass.getMethod(
49+
"extractAllFromJava",
50+
classOf[SparkSession],
51+
classOf[Int],
52+
classOf[java.util.List[_]])
53+
Some(DeltaScanInfoReflection(module, extractAllMethod))
54+
} catch {
55+
case _: ClassNotFoundException | _: NoSuchMethodException =>
56+
None
57+
}
58+
}
59+
60+
def normalize(
61+
partitionColumnCount: Int,
62+
partitionFiles: Seq[PartitionedFile]): Option[NormalizedDeltaSplitMetadata] = {
63+
deltaScanInfoReflection.flatMap {
64+
reflection =>
65+
val scanInfos = reflection.extractAllFromJava
66+
.invoke(
67+
reflection.module,
68+
activeSparkSession,
69+
Int.box(partitionColumnCount),
70+
partitionFiles.asJava)
71+
.asInstanceOf[java.util.List[_]]
72+
.asScala
73+
.toSeq
74+
val splitMetadata = scanInfos.map(toDeltaSplitMetadata)
75+
if (splitMetadata.exists(_._2.hasDeletionVector())) {
76+
Some((splitMetadata.map(_._1), splitMetadata.map(_._2)))
77+
} else {
78+
None
79+
}
80+
}
81+
}
82+
83+
private def toDeltaSplitMetadata(
84+
scanInfo: Any): (java.util.Map[String, Object], DeltaFileReadOptions) = {
85+
val scanInfoMethods = methodsForScanInfo(scanInfo.getClass)
86+
val metadata = scanInfoMethods.normalizedOtherMetadataColumns
87+
.invoke(scanInfo)
88+
.asInstanceOf[scala.collection.Map[String, Object]]
89+
.asJava
90+
val deletionVectorInfo = scanInfoMethods.deletionVectorInfo.invoke(scanInfo)
91+
val deletionVectorInfoMethods = methodsForDeletionVectorInfo(deletionVectorInfo.getClass)
92+
val rowIndexFilterType = deletionVectorInfoMethods.rowIndexFilterType
93+
.invoke(deletionVectorInfo)
94+
.toString
95+
val hasDeletionVector = deletionVectorInfoMethods.hasDeletionVector
96+
.invoke(deletionVectorInfo)
97+
.asInstanceOf[Boolean]
98+
val cardinality = deletionVectorInfoMethods.cardinality
99+
.invoke(deletionVectorInfo)
100+
.asInstanceOf[JLong]
101+
.longValue()
102+
val serializedDeletionVector = deletionVectorInfoMethods.serializedDeletionVector
103+
.invoke(deletionVectorInfo)
104+
.asInstanceOf[Array[Byte]]
105+
106+
(
107+
metadata,
108+
new DeltaFileReadOptions(
109+
toDeltaRowIndexFilterType(rowIndexFilterType),
110+
hasDeletionVector,
111+
cardinality,
112+
serializedDeletionVector))
113+
}
114+
115+
private def toDeltaRowIndexFilterType(rowIndexFilterType: String): RowIndexFilterType = {
116+
rowIndexFilterType match {
117+
case "IF_CONTAINED" => RowIndexFilterType.IF_CONTAINED
118+
case "IF_NOT_CONTAINED" => RowIndexFilterType.IF_NOT_CONTAINED
119+
case _ => RowIndexFilterType.KEEP_ALL
120+
}
121+
}
122+
123+
private def methodsForScanInfo(scanInfoClass: Class[_]): ScanInfoMethods = {
124+
val cached = scanInfoMethodsCache.get(scanInfoClass)
125+
if (cached != null) {
126+
return cached
127+
}
128+
129+
val methods = ScanInfoMethods(
130+
scanInfoClass.getMethod("normalizedOtherMetadataColumns"),
131+
scanInfoClass.getMethod("deletionVectorInfo"))
132+
val previous = scanInfoMethodsCache.putIfAbsent(scanInfoClass, methods)
133+
if (previous != null) previous else methods
134+
}
135+
136+
private def methodsForDeletionVectorInfo(
137+
deletionVectorInfoClass: Class[_]): DeletionVectorInfoMethods = {
138+
val cached = deletionVectorInfoMethodsCache.get(deletionVectorInfoClass)
139+
if (cached != null) {
140+
return cached
141+
}
142+
143+
val methods = DeletionVectorInfoMethods(
144+
deletionVectorInfoClass.getMethod("rowIndexFilterType"),
145+
deletionVectorInfoClass.getMethod("hasDeletionVector"),
146+
deletionVectorInfoClass.getMethod("cardinality"),
147+
deletionVectorInfoClass.getMethod("serializedDeletionVector")
148+
)
149+
val previous = deletionVectorInfoMethodsCache.putIfAbsent(deletionVectorInfoClass, methods)
150+
if (previous != null) previous else methods
151+
}
152+
153+
private def activeSparkSession: SparkSession = {
154+
SparkSession.getActiveSession
155+
.orElse(SparkSession.getDefaultSession)
156+
.getOrElse {
157+
throw new IllegalStateException(
158+
"Active SparkSession is required to materialize Delta deletion vectors")
159+
}
160+
}
161+
162+
private case class DeltaScanInfoReflection(module: AnyRef, extractAllFromJava: Method)
163+
164+
private case class ScanInfoMethods(
165+
normalizedOtherMetadataColumns: Method,
166+
deletionVectorInfo: Method)
167+
168+
private case class DeletionVectorInfoMethods(
169+
rowIndexFilterType: Method,
170+
hasDeletionVector: Method,
171+
cardinality: Method,
172+
serializedDeletionVector: Method)
173+
}

backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxIteratorApi.scala

Lines changed: 2 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,13 @@ import org.apache.gluten.metrics.{IMetrics, IteratorMetricsJniWrapper}
2525
import org.apache.gluten.sql.shims.SparkShimLoader
2626
import org.apache.gluten.substrait.plan.PlanNode
2727
import org.apache.gluten.substrait.rel.{DeltaLocalFilesBuilder, LocalFilesBuilder, LocalFilesNode, SplitInfo}
28-
import org.apache.gluten.substrait.rel.DeltaLocalFilesNode.{DeltaFileReadOptions, RowIndexFilterType}
28+
import org.apache.gluten.substrait.rel.DeltaLocalFilesNode.DeltaFileReadOptions
2929
import org.apache.gluten.substrait.rel.LocalFilesNode.ReadFileFormat
3030
import org.apache.gluten.vectorized._
3131

3232
import org.apache.spark.{Partition, SparkConf, TaskContext}
3333
import org.apache.spark.internal.Logging
3434
import org.apache.spark.softaffinity.SoftAffinity
35-
import org.apache.spark.sql.SparkSession
3635
import org.apache.spark.sql.catalyst.catalog.ExternalCatalogUtils
3736
import org.apache.spark.sql.catalyst.util.{DateFormatter, TimestampFormatter}
3837
import org.apache.spark.sql.execution.datasources.{FilePartition, PartitionedFile}
@@ -51,11 +50,6 @@ import scala.collection.JavaConverters._
5150
import scala.collection.mutable
5251

5352
class VeloxIteratorApi extends IteratorApi with Logging {
54-
private type NormalizedDeltaSplitMetadata =
55-
(Seq[java.util.Map[String, Object]], Seq[DeltaFileReadOptions])
56-
57-
private val deltaScanInfoClassName = "org.apache.gluten.delta.DeltaDeletionVectorScanInfo$"
58-
5953
private def setFileSchemaForLocalFiles(
6054
localFilesNode: LocalFilesNode,
6155
fileSchema: StructType,
@@ -101,7 +95,7 @@ class VeloxIteratorApi extends IteratorApi with Logging {
10195
.map(
10296
f => SparkShimLoader.getSparkShims.generateMetadataColumns(f, metadataColumnNames).asJava)
10397
val (otherMetadataColumns, deltaReadOptions) =
104-
normalizeDeltaSplitMetadata(partitionSchema.fields.length, partitionFiles)
98+
DeltaSplitMetadataExtractor.normalize(partitionSchema.fields.length, partitionFiles)
10599
.getOrElse {
106100
(
107101
partitionFiles.map {
@@ -214,93 +208,6 @@ class VeloxIteratorApi extends IteratorApi with Logging {
214208
NativePlanEvaluator.injectWriteFilesTempPath(path, fileName)
215209
}
216210

217-
private def normalizeDeltaSplitMetadata(
218-
partitionColumnCount: Int,
219-
partitionFiles: Seq[PartitionedFile]): Option[NormalizedDeltaSplitMetadata] = {
220-
try {
221-
// scalastyle:off classforname
222-
val moduleClass = Class.forName(deltaScanInfoClassName)
223-
// scalastyle:on classforname
224-
val module = moduleClass.getField("MODULE$").get(null)
225-
val extractAllMethod = moduleClass.getMethod(
226-
"extractAllFromJava",
227-
classOf[SparkSession],
228-
classOf[Int],
229-
classOf[java.util.List[_]])
230-
val scanInfos = extractAllMethod
231-
.invoke(module, activeSparkSession, Int.box(partitionColumnCount), partitionFiles.asJava)
232-
.asInstanceOf[java.util.List[_]]
233-
.asScala
234-
.toSeq
235-
val splitMetadata = scanInfos.map(toDeltaSplitMetadata)
236-
if (splitMetadata.exists(_._2.hasDeletionVector())) {
237-
Some((splitMetadata.map(_._1), splitMetadata.map(_._2)))
238-
} else {
239-
None
240-
}
241-
} catch {
242-
case _: ClassNotFoundException | _: NoSuchMethodException =>
243-
None
244-
}
245-
}
246-
247-
private def toDeltaSplitMetadata(
248-
scanInfo: Any): (java.util.Map[String, Object], DeltaFileReadOptions) = {
249-
val metadata = scanInfo
250-
.getClass
251-
.getMethod("normalizedOtherMetadataColumns")
252-
.invoke(scanInfo)
253-
.asInstanceOf[scala.collection.Map[String, Object]]
254-
.asJava
255-
val deletionVectorInfo = scanInfo.getClass.getMethod("deletionVectorInfo").invoke(scanInfo)
256-
val rowIndexFilterType = deletionVectorInfo
257-
.getClass
258-
.getMethod("rowIndexFilterType")
259-
.invoke(deletionVectorInfo)
260-
.toString
261-
val hasDeletionVector = deletionVectorInfo
262-
.getClass
263-
.getMethod("hasDeletionVector")
264-
.invoke(deletionVectorInfo)
265-
.asInstanceOf[Boolean]
266-
val cardinality = deletionVectorInfo
267-
.getClass
268-
.getMethod("cardinality")
269-
.invoke(deletionVectorInfo)
270-
.asInstanceOf[JLong]
271-
.longValue()
272-
val serializedDeletionVector = deletionVectorInfo
273-
.getClass
274-
.getMethod("serializedDeletionVector")
275-
.invoke(deletionVectorInfo)
276-
.asInstanceOf[Array[Byte]]
277-
278-
(
279-
metadata,
280-
new DeltaFileReadOptions(
281-
toDeltaRowIndexFilterType(rowIndexFilterType),
282-
hasDeletionVector,
283-
cardinality,
284-
serializedDeletionVector))
285-
}
286-
287-
private def toDeltaRowIndexFilterType(rowIndexFilterType: String): RowIndexFilterType = {
288-
rowIndexFilterType match {
289-
case "IF_CONTAINED" => RowIndexFilterType.IF_CONTAINED
290-
case "IF_NOT_CONTAINED" => RowIndexFilterType.IF_NOT_CONTAINED
291-
case _ => RowIndexFilterType.KEEP_ALL
292-
}
293-
}
294-
295-
private def activeSparkSession: SparkSession = {
296-
SparkSession.getActiveSession
297-
.orElse(SparkSession.getDefaultSession)
298-
.getOrElse {
299-
throw new IllegalStateException(
300-
"Active SparkSession is required to materialize Delta deletion vectors")
301-
}
302-
}
303-
304211
/** Generate Iterator[ColumnarBatch] for first stage. */
305212
override def genFirstStageIterator(
306213
inputPartition: BaseGlutenPartition,

0 commit comments

Comments
 (0)