Skip to content

HBASE-30186 Port basic HBase operations to spark4 HBaseContext - #158

Open
wchevreuil wants to merge 1 commit into
apache:masterfrom
wchevreuil:HBASE-30186
Open

HBASE-30186 Port basic HBase operations to spark4 HBaseContext#158
wchevreuil wants to merge 1 commit into
apache:masterfrom
wchevreuil:HBASE-30186

Conversation

@wchevreuil

Copy link
Copy Markdown
Contributor

No description provided.

Co-authored-by: Claude Code (claude-opus-4-6) <no-reply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR ports core HBase interaction utilities into the Spark 4 connector by expanding HBaseContext beyond scan-only support to include bulk put/get/delete and connection-aware partition helpers, plus the supporting connection cache and RDD implicit wrappers.

Changes:

  • Add Spark-4 implementations of HBaseConnectionCache and HBaseRDDFunctions (implicit RDD helpers).
  • Extend HBaseContext with foreachPartition, mapPartitions, bulkPut, bulkGet, and bulkDelete implementations backed by the connection cache.
  • Expand/introduce test suites covering bulk operations and the connection cache.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/HBaseContext.scala Adds bulk put/get/delete and connection-aware partition helpers for Spark 4.
spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/HBaseConnectionCache.scala Introduces a cached-connection layer with housekeeping cleanup for Spark 4.
spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/HBaseRDDFunctions.scala Adds implicit RDD extension methods that delegate to HBaseContext bulk/partition APIs.
spark4/hbase-spark4/src/test/scala/org/apache/hadoop/hbase/spark/HBaseContextSuite.scala Adds tests for the newly ported bulk operations and partition helpers.
spark4/hbase-spark4/src/test/scala/org/apache/hadoop/hbase/spark/HBaseConnectionCacheSuite.scala Adds Spark 4 tests validating the connection cache behavior under load.
Comments suppressed due to low confidence (3)

spark4/hbase-spark4/src/test/scala/org/apache/hadoop/hbase/spark/HBaseConnectionCacheSuite.scala:224

  • Threads are created but executed with Thread.run(), which runs sequentially on the current thread. To validate connection-cache behavior under concurrent pressure, start the threads instead.
    for (i <- threads.indices) {
      threads.update(i, new Thread(new TestThread()))
      threads(i).run()
    }

spark4/hbase-spark4/src/test/scala/org/apache/hadoop/hbase/spark/HBaseConnectionCacheSuite.scala:229

  • The InterruptedException is swallowed via println, which clears the interrupt status and can hide test failures. Re-interrupt the thread and rethrow so the test fails appropriately.
    try {
      threads.foreach { x => x.join() }
    } catch {
      case e: InterruptedException => println(e.getMessage)
    }

spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/HBaseContext.scala:286

  • smartConn can be null when HBaseConnectionCache is closed, but smartConn.connection is dereferenced unconditionally, which will throw a NullPointerException. Fail fast with a clear exception before dereferencing, and always close the SmartConnection in finally.
    val config = getConf(configBroadcast)
    val smartConn = HBaseConnectionCache.getConnection(config)
    try {
      mp(it, smartConn.connection)
    } finally {

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +170 to +173
for (i <- 0 to 99) {
threads.update(i, new Thread(new TestThread()))
threads(i).run()
}
Comment on lines +174 to +178
try {
threads.foreach { x => x.join() }
} catch {
case e: InterruptedException => println(e.getMessage)
}
Comment on lines +90 to +108
connectionMap.synchronized {
connectionMap.foreach {
x =>
{
if (x._2.refCount < 0) {
logError(s"Bug to be fixed: negative refCount of connection ${x._2}")
}

if (forceClean || ((x._2.refCount <= 0) && (tsNow - x._2.timestamp > timeout))) {
try {
x._2.connection.close()
} catch {
case e: IOException => logWarning(s"Fail to close connection ${x._2}", e)
}
connectionMap.remove(x._1)
}
}
}
}
Comment on lines +268 to +274
val config = getConf(configBroadcast)
val smartConn = HBaseConnectionCache.getConnection(config)
try {
f(it, smartConn.connection)
} finally {
if (smartConn != null) smartConn.close()
}
Comment on lines +117 to +120
val m = connection.getBufferedMutator(TableName.valueOf(tName))
iterator.foreach(T => m.mutate(f(T)))
m.flush()
m.close()
Comment on lines +304 to +318
val table = connection.getTable(TableName.valueOf(tName))
val mutationList = new java.util.ArrayList[Mutation]
iterator.foreach(
T => {
mutationList.add(f(T))
if (mutationList.size >= batchSize) {
table.batch(mutationList, null)
mutationList.clear()
}
})
if (mutationList.size() > 0) {
table.batch(mutationList, null)
mutationList.clear()
}
table.close()
Comment on lines +331 to +353
def run(iterator: Iterator[T], connection: Connection): Iterator[U] = {
val table = connection.getTable(TableName.valueOf(tName))

val gets = new java.util.ArrayList[Get]()
var res = List[U]()

while (iterator.hasNext) {
gets.add(makeGet(iterator.next()))

if (gets.size() == batchSize) {
val results = table.get(gets)
res = res ++ results.map(convertResult)
gets.clear()
}
}
if (gets.size() > 0) {
val results = table.get(gets)
res = res ++ results.map(convertResult)
gets.clear()
}
table.close()
res.iterator
}
Comment on lines +104 to +115
hc.bulkGet[T, (ImmutableBytesWritable, Result)](
tableName,
batchSize,
rdd,
f,
result =>
if (result != null && result.getRow != null) {
(new ImmutableBytesWritable(result.getRow), result)
} else {
null
})
}
Comment on lines +401 to +411
val tbl = conn.getTable(TableName.valueOf(tName))
val res = new ListBuffer[String]()
it.foreach { rowKey =>
val result = tbl.get(new Get(rowKey))
val cell = result.getColumnLatestCell(Bytes.toBytes(cf), Bytes.toBytes("a"))
if (cell != null) {
res += Bytes.toString(result.getRow) + "=" + Bytes.toString(CellUtil.cloneValue(cell))
}
}
tbl.close()
res.iterator

@taklwu taklwu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

please check those minor optimizations suggested by copilot, otherwise LGTM

}
}

test("bulkGet to test HBase client") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: a failure test for bulkGet (bad table) in HBaseContextSuite of hbase-spark was missing but it should be fine

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.

3 participants