@@ -133,16 +133,6 @@ class RocksDBFileManager(
133
133
134
134
import RocksDBImmutableFile ._
135
135
136
- private val versionToRocksDBFiles = new ConcurrentHashMap [Long , Seq [RocksDBImmutableFile ]]
137
-
138
-
139
- // used to keep a mapping of the exact Dfs file that was used to create a local SST file.
140
- // The reason this is a separate map because versionToRocksDBFiles can contain multiple similar
141
- // SST files to a particular local file (for example 1.sst can map to 1-UUID1.sst in v1 and
142
- // 1-UUID2.sst in v2). We need to capture the exact file used to ensure Version ID compatibility
143
- // across SST files and RocksDB manifest.
144
- private [sql] val localFilesToDfsFiles = new ConcurrentHashMap [String , RocksDBImmutableFile ]
145
-
146
136
private lazy val fm = CheckpointFileManager .create(new Path (dfsRootDir), hadoopConf)
147
137
private val fs = new Path (dfsRootDir).getFileSystem(hadoopConf)
148
138
private val onlyZipFiles = new PathFilter {
@@ -157,6 +147,29 @@ class RocksDBFileManager(
157
147
private def codec = CompressionCodec .createCodec(sparkConf, codecName)
158
148
159
149
@ volatile private var rootDirChecked : Boolean = false
150
+ @ volatile private var fileMappings = RocksDBFileMappings (
151
+ new ConcurrentHashMap [Long , Seq [RocksDBImmutableFile ]],
152
+ new ConcurrentHashMap [String , RocksDBImmutableFile ]
153
+ )
154
+
155
+ /**
156
+ * Make a deep copy of versionToRocksDBFiles and localFilesToDfsFiles to avoid
157
+ * current task thread from overwriting the file mapping whenever background maintenance
158
+ * thread attempts to upload a snapshot
159
+ */
160
+ def copyFileMapping () : Unit = {
161
+ val newVersionToRocksDBFiles = new ConcurrentHashMap [Long , Seq [RocksDBImmutableFile ]]
162
+ val newLocalFilesToDfsFiles = new ConcurrentHashMap [String , RocksDBImmutableFile ]
163
+
164
+ newVersionToRocksDBFiles.putAll(fileMappings.versionToRocksDBFiles)
165
+ newLocalFilesToDfsFiles.putAll(fileMappings.localFilesToDfsFiles)
166
+
167
+ fileMappings = RocksDBFileMappings (newVersionToRocksDBFiles, newLocalFilesToDfsFiles)
168
+ }
169
+
170
+ def captureFileMapReference (): RocksDBFileMappings = {
171
+ fileMappings
172
+ }
160
173
161
174
def getChangeLogWriter (
162
175
version : Long ,
@@ -204,11 +217,15 @@ class RocksDBFileManager(
204
217
def latestSaveCheckpointMetrics : RocksDBFileManagerMetrics = saveCheckpointMetrics
205
218
206
219
/** Save all the files in given local checkpoint directory as a committed version in DFS */
207
- def saveCheckpointToDfs (checkpointDir : File , version : Long , numKeys : Long ): Unit = {
220
+ def saveCheckpointToDfs (
221
+ checkpointDir : File ,
222
+ version : Long ,
223
+ numKeys : Long ,
224
+ capturedFileMappings : RocksDBFileMappings ): Unit = {
208
225
logFilesInDir(checkpointDir, log " Saving checkpoint files " +
209
226
log " for version ${MDC (LogKeys .VERSION_NUM , version)}" )
210
227
val (localImmutableFiles, localOtherFiles) = listRocksDBFiles(checkpointDir)
211
- val rocksDBFiles = saveImmutableFilesToDfs(version, localImmutableFiles)
228
+ val rocksDBFiles = saveImmutableFilesToDfs(version, localImmutableFiles, capturedFileMappings )
212
229
val metadata = RocksDBCheckpointMetadata (rocksDBFiles, numKeys)
213
230
val metadataFile = localMetadataFile(checkpointDir)
214
231
metadata.writeToFile(metadataFile)
@@ -243,10 +260,10 @@ class RocksDBFileManager(
243
260
// The unique ids of SST files are checked when opening a rocksdb instance. The SST files
244
261
// in larger versions can't be reused even if they have the same size and name because
245
262
// they belong to another rocksdb instance.
246
- versionToRocksDBFiles.keySet().removeIf(_ >= version)
263
+ fileMappings. versionToRocksDBFiles.keySet().removeIf(_ >= version)
247
264
val metadata = if (version == 0 ) {
248
265
if (localDir.exists) Utils .deleteRecursively(localDir)
249
- localFilesToDfsFiles.clear()
266
+ fileMappings. localFilesToDfsFiles.clear()
250
267
localDir.mkdirs()
251
268
RocksDBCheckpointMetadata (Seq .empty, 0 )
252
269
} else {
@@ -260,7 +277,7 @@ class RocksDBFileManager(
260
277
logInfo(log " Read metadata for version ${MDC (LogKeys .VERSION_NUM , version)}: \n " +
261
278
log " ${MDC (LogKeys .METADATA_JSON , metadata.prettyJson)}" )
262
279
loadImmutableFilesFromDfs(metadata.immutableFiles, localDir)
263
- versionToRocksDBFiles.put(version, metadata.immutableFiles)
280
+ fileMappings. versionToRocksDBFiles.put(version, metadata.immutableFiles)
264
281
metadataFile.delete()
265
282
metadata
266
283
}
@@ -417,9 +434,9 @@ class RocksDBFileManager(
417
434
// Resolve RocksDB files for all the versions and find the max version each file is used
418
435
val fileToMaxUsedVersion = new mutable.HashMap [String , Long ]
419
436
sortedSnapshotVersions.foreach { version =>
420
- val files = Option (versionToRocksDBFiles.get(version)).getOrElse {
437
+ val files = Option (fileMappings. versionToRocksDBFiles.get(version)).getOrElse {
421
438
val newResolvedFiles = getImmutableFilesFromVersionZip(version)
422
- versionToRocksDBFiles.put(version, newResolvedFiles)
439
+ fileMappings. versionToRocksDBFiles.put(version, newResolvedFiles)
423
440
newResolvedFiles
424
441
}
425
442
files.foreach(f => fileToMaxUsedVersion(f.dfsFileName) =
@@ -466,7 +483,7 @@ class RocksDBFileManager(
466
483
val versionFile = dfsBatchZipFile(version)
467
484
try {
468
485
fm.delete(versionFile)
469
- versionToRocksDBFiles.remove(version)
486
+ fileMappings. versionToRocksDBFiles.remove(version)
470
487
logDebug(s " Deleted version $version" )
471
488
} catch {
472
489
case e : Exception =>
@@ -487,7 +504,8 @@ class RocksDBFileManager(
487
504
/** Save immutable files to DFS directory */
488
505
private def saveImmutableFilesToDfs (
489
506
version : Long ,
490
- localFiles : Seq [File ]): Seq [RocksDBImmutableFile ] = {
507
+ localFiles : Seq [File ],
508
+ capturedFileMappings : RocksDBFileMappings ): Seq [RocksDBImmutableFile ] = {
491
509
// Get the immutable files used in previous versions, as some of those uploaded files can be
492
510
// reused for this version
493
511
logInfo(log " Saving RocksDB files to DFS for ${MDC (LogKeys .VERSION_NUM , version)}" )
@@ -497,7 +515,8 @@ class RocksDBFileManager(
497
515
var filesReused = 0L
498
516
499
517
val immutableFiles = localFiles.map { localFile =>
500
- val existingDfsFile = localFilesToDfsFiles.asScala.get(localFile.getName)
518
+ val existingDfsFile =
519
+ capturedFileMappings.localFilesToDfsFiles.asScala.get(localFile.getName)
501
520
if (existingDfsFile.isDefined && existingDfsFile.get.sizeBytes == localFile.length()) {
502
521
val dfsFile = existingDfsFile.get
503
522
filesReused += 1
@@ -521,7 +540,7 @@ class RocksDBFileManager(
521
540
bytesCopied += localFileSize
522
541
523
542
val immutableDfsFile = RocksDBImmutableFile (localFile.getName, dfsFileName, localFileSize)
524
- localFilesToDfsFiles.put(localFileName, immutableDfsFile)
543
+ capturedFileMappings. localFilesToDfsFiles.put(localFileName, immutableDfsFile)
525
544
526
545
immutableDfsFile
527
546
}
@@ -530,7 +549,7 @@ class RocksDBFileManager(
530
549
log " ( ${MDC (LogKeys .NUM_BYTES , bytesCopied)} bytes) from local to " +
531
550
log " DFS for version ${MDC (LogKeys .VERSION_NUM , version)}. " +
532
551
log " ${MDC (LogKeys .NUM_FILES_REUSED , filesReused)} files reused without copying. " )
533
- versionToRocksDBFiles.put(version, immutableFiles)
552
+ capturedFileMappings. versionToRocksDBFiles.put(version, immutableFiles)
534
553
535
554
// Cleanup locally deleted files from the localFilesToDfsFiles map
536
555
// Locally, SST Files can be deleted due to RocksDB compaction. These files need
@@ -570,7 +589,7 @@ class RocksDBFileManager(
570
589
.foreach { existingFile =>
571
590
val existingFileSize = existingFile.length()
572
591
val requiredFile = requiredFileNameToFileDetails.get(existingFile.getName)
573
- val prevDfsFile = localFilesToDfsFiles.asScala.get(existingFile.getName)
592
+ val prevDfsFile = fileMappings. localFilesToDfsFiles.asScala.get(existingFile.getName)
574
593
val isSameFile = if (requiredFile.isDefined && prevDfsFile.isDefined) {
575
594
requiredFile.get.dfsFileName == prevDfsFile.get.dfsFileName &&
576
595
existingFile.length() == requiredFile.get.sizeBytes
@@ -580,7 +599,7 @@ class RocksDBFileManager(
580
599
581
600
if (! isSameFile) {
582
601
existingFile.delete()
583
- localFilesToDfsFiles.remove(existingFile.getName)
602
+ fileMappings. localFilesToDfsFiles.remove(existingFile.getName)
584
603
logInfo(log " Deleted local file ${MDC (LogKeys .FILE_NAME , existingFile)} " +
585
604
log " with size ${MDC (LogKeys .NUM_BYTES , existingFileSize)} mapped " +
586
605
log " to previous dfsFile ${MDC (LogKeys .DFS_FILE , prevDfsFile.getOrElse(" null" ))}" )
@@ -612,7 +631,7 @@ class RocksDBFileManager(
612
631
}
613
632
filesCopied += 1
614
633
bytesCopied += localFileSize
615
- localFilesToDfsFiles.put(localFileName, file)
634
+ fileMappings. localFilesToDfsFiles.put(localFileName, file)
616
635
logInfo(log " Copied ${MDC (LogKeys .DFS_FILE , dfsFile)} to " +
617
636
log " ${MDC (LogKeys .FILE_NAME , localFile)} - " +
618
637
log " ${MDC (LogKeys .NUM_BYTES , localFileSize)} bytes " )
@@ -633,13 +652,13 @@ class RocksDBFileManager(
633
652
private def removeLocallyDeletedSSTFilesFromDfsMapping (localFiles : Seq [File ]): Unit = {
634
653
// clean up deleted SST files from the localFilesToDfsFiles Map
635
654
val currentLocalFiles = localFiles.map(_.getName).toSet
636
- val mappingsToClean = localFilesToDfsFiles.asScala
655
+ val mappingsToClean = fileMappings. localFilesToDfsFiles.asScala
637
656
.keys
638
657
.filterNot(currentLocalFiles.contains)
639
658
640
659
mappingsToClean.foreach { f =>
641
660
logInfo(log " cleaning ${MDC (LogKeys .FILE_NAME , f)} from the localFilesToDfsFiles map " )
642
- localFilesToDfsFiles.remove(f)
661
+ fileMappings. localFilesToDfsFiles.remove(f)
643
662
}
644
663
}
645
664
@@ -749,6 +768,20 @@ class RocksDBFileManager(
749
768
}
750
769
}
751
770
771
+ /**
772
+ * Track file mappings in RocksDB across local and remote directories
773
+ * @param versionToRocksDBFiles Mapping of RocksDB files used across versions for maintenance
774
+ * @param localFilesToDfsFiles Mapping of the exact Dfs file used to create a local SST file
775
+ * The reason localFilesToDfsFiles is a separate map because versionToRocksDBFiles can contain
776
+ * multiple similar SST files to a particular local file (for example 1.sst can map to 1-UUID1.sst
777
+ * in v1 and 1-UUID2.sst in v2). We need to capture the exact file used to ensure Version ID
778
+ * compatibility across SST files and RocksDB manifest.
779
+ */
780
+
781
+ case class RocksDBFileMappings (
782
+ versionToRocksDBFiles : ConcurrentHashMap [Long , Seq [RocksDBImmutableFile ]],
783
+ localFilesToDfsFiles : ConcurrentHashMap [String , RocksDBImmutableFile ])
784
+
752
785
/**
753
786
* Metrics regarding RocksDB file sync between local and DFS.
754
787
*/
0 commit comments