@@ -131,16 +131,6 @@ class RocksDBFileManager(
131
131
132
132
import RocksDBImmutableFile ._
133
133
134
- private val versionToRocksDBFiles = new ConcurrentHashMap [Long , Seq [RocksDBImmutableFile ]]
135
-
136
-
137
- // used to keep a mapping of the exact Dfs file that was used to create a local SST file.
138
- // The reason this is a separate map because versionToRocksDBFiles can contain multiple similar
139
- // SST files to a particular local file (for example 1.sst can map to 1-UUID1.sst in v1 and
140
- // 1-UUID2.sst in v2). We need to capture the exact file used to ensure Version ID compatibility
141
- // across SST files and RocksDB manifest.
142
- private [sql] val localFilesToDfsFiles = new ConcurrentHashMap [String , RocksDBImmutableFile ]
143
-
144
134
private lazy val fm = CheckpointFileManager .create(new Path (dfsRootDir), hadoopConf)
145
135
private val fs = new Path (dfsRootDir).getFileSystem(hadoopConf)
146
136
private val onlyZipFiles = new PathFilter {
@@ -154,6 +144,30 @@ class RocksDBFileManager(
154
144
155
145
private def codec = CompressionCodec .createCodec(sparkConf, codecName)
156
146
147
+ @ volatile private var fileMappings = RocksDBFileMappings (
148
+ new ConcurrentHashMap [Long , Seq [RocksDBImmutableFile ]],
149
+ new ConcurrentHashMap [String , RocksDBImmutableFile ]
150
+ )
151
+
152
+ /**
153
+ * Make a deep copy of versionToRocksDBFiles and localFilesToDfsFiles to avoid
154
+ * current task thread from overwriting the file mapping whenever background maintenance
155
+ * thread attempts to upload a snapshot
156
+ */
157
+ def copyFileMapping () : Unit = {
158
+ val newVersionToRocksDBFiles = new ConcurrentHashMap [Long , Seq [RocksDBImmutableFile ]]
159
+ val newLocalFilesToDfsFiles = new ConcurrentHashMap [String , RocksDBImmutableFile ]
160
+
161
+ newVersionToRocksDBFiles.putAll(fileMappings.versionToRocksDBFiles)
162
+ newLocalFilesToDfsFiles.putAll(fileMappings.localFilesToDfsFiles)
163
+
164
+ fileMappings = RocksDBFileMappings (newVersionToRocksDBFiles, newLocalFilesToDfsFiles)
165
+ }
166
+
167
+ def captureFileMapReference (): RocksDBFileMappings = {
168
+ fileMappings
169
+ }
170
+
157
171
def getChangeLogWriter (version : Long ): StateStoreChangelogWriter = {
158
172
val rootDir = new Path (dfsRootDir)
159
173
val changelogFile = dfsChangelogFile(version)
@@ -185,10 +199,14 @@ class RocksDBFileManager(
185
199
def latestSaveCheckpointMetrics : RocksDBFileManagerMetrics = saveCheckpointMetrics
186
200
187
201
/** Save all the files in given local checkpoint directory as a committed version in DFS */
188
- def saveCheckpointToDfs (checkpointDir : File , version : Long , numKeys : Long ): Unit = {
202
+ def saveCheckpointToDfs (
203
+ checkpointDir : File ,
204
+ version : Long ,
205
+ numKeys : Long ,
206
+ capturedFileMappings : RocksDBFileMappings ): Unit = {
189
207
logFilesInDir(checkpointDir, s " Saving checkpoint files for version $version" )
190
208
val (localImmutableFiles, localOtherFiles) = listRocksDBFiles(checkpointDir)
191
- val rocksDBFiles = saveImmutableFilesToDfs(version, localImmutableFiles)
209
+ val rocksDBFiles = saveImmutableFilesToDfs(version, localImmutableFiles, capturedFileMappings )
192
210
val metadata = RocksDBCheckpointMetadata (rocksDBFiles, numKeys)
193
211
val metadataFile = localMetadataFile(checkpointDir)
194
212
metadata.writeToFile(metadataFile)
@@ -219,10 +237,10 @@ class RocksDBFileManager(
219
237
// The unique ids of SST files are checked when opening a rocksdb instance. The SST files
220
238
// in larger versions can't be reused even if they have the same size and name because
221
239
// they belong to another rocksdb instance.
222
- versionToRocksDBFiles.keySet().removeIf(_ >= version)
240
+ fileMappings. versionToRocksDBFiles.keySet().removeIf(_ >= version)
223
241
val metadata = if (version == 0 ) {
224
242
if (localDir.exists) Utils .deleteRecursively(localDir)
225
- localFilesToDfsFiles.clear()
243
+ fileMappings. localFilesToDfsFiles.clear()
226
244
localDir.mkdirs()
227
245
RocksDBCheckpointMetadata (Seq .empty, 0 )
228
246
} else {
@@ -235,7 +253,7 @@ class RocksDBFileManager(
235
253
val metadata = RocksDBCheckpointMetadata .readFromFile(metadataFile)
236
254
logInfo(s " Read metadata for version $version: \n ${metadata.prettyJson}" )
237
255
loadImmutableFilesFromDfs(metadata.immutableFiles, localDir)
238
- versionToRocksDBFiles.put(version, metadata.immutableFiles)
256
+ fileMappings. versionToRocksDBFiles.put(version, metadata.immutableFiles)
239
257
metadataFile.delete()
240
258
metadata
241
259
}
@@ -389,9 +407,9 @@ class RocksDBFileManager(
389
407
// Resolve RocksDB files for all the versions and find the max version each file is used
390
408
val fileToMaxUsedVersion = new mutable.HashMap [String , Long ]
391
409
sortedSnapshotVersions.foreach { version =>
392
- val files = Option (versionToRocksDBFiles.get(version)).getOrElse {
410
+ val files = Option (fileMappings. versionToRocksDBFiles.get(version)).getOrElse {
393
411
val newResolvedFiles = getImmutableFilesFromVersionZip(version)
394
- versionToRocksDBFiles.put(version, newResolvedFiles)
412
+ fileMappings. versionToRocksDBFiles.put(version, newResolvedFiles)
395
413
newResolvedFiles
396
414
}
397
415
files.foreach(f => fileToMaxUsedVersion(f.dfsFileName) =
@@ -436,7 +454,7 @@ class RocksDBFileManager(
436
454
val versionFile = dfsBatchZipFile(version)
437
455
try {
438
456
fm.delete(versionFile)
439
- versionToRocksDBFiles.remove(version)
457
+ fileMappings. versionToRocksDBFiles.remove(version)
440
458
logDebug(s " Deleted version $version" )
441
459
} catch {
442
460
case e : Exception =>
@@ -455,7 +473,8 @@ class RocksDBFileManager(
455
473
/** Save immutable files to DFS directory */
456
474
private def saveImmutableFilesToDfs (
457
475
version : Long ,
458
- localFiles : Seq [File ]): Seq [RocksDBImmutableFile ] = {
476
+ localFiles : Seq [File ],
477
+ capturedFileMappings : RocksDBFileMappings ): Seq [RocksDBImmutableFile ] = {
459
478
// Get the immutable files used in previous versions, as some of those uploaded files can be
460
479
// reused for this version
461
480
logInfo(s " Saving RocksDB files to DFS for $version" )
@@ -465,7 +484,8 @@ class RocksDBFileManager(
465
484
var filesReused = 0L
466
485
467
486
val immutableFiles = localFiles.map { localFile =>
468
- val existingDfsFile = localFilesToDfsFiles.asScala.get(localFile.getName)
487
+ val existingDfsFile =
488
+ capturedFileMappings.localFilesToDfsFiles.asScala.get(localFile.getName)
469
489
if (existingDfsFile.isDefined && existingDfsFile.get.sizeBytes == localFile.length()) {
470
490
val dfsFile = existingDfsFile.get
471
491
filesReused += 1
@@ -487,14 +507,14 @@ class RocksDBFileManager(
487
507
bytesCopied += localFileSize
488
508
489
509
val immutableDfsFile = RocksDBImmutableFile (localFile.getName, dfsFileName, localFileSize)
490
- localFilesToDfsFiles.put(localFileName, immutableDfsFile)
510
+ capturedFileMappings. localFilesToDfsFiles.put(localFileName, immutableDfsFile)
491
511
492
512
immutableDfsFile
493
513
}
494
514
}
495
515
logInfo(s " Copied $filesCopied files ( $bytesCopied bytes) from local to " +
496
516
s " DFS for version $version. $filesReused files reused without copying. " )
497
- versionToRocksDBFiles.put(version, immutableFiles)
517
+ capturedFileMappings. versionToRocksDBFiles.put(version, immutableFiles)
498
518
499
519
// Cleanup locally deleted files from the localFilesToDfsFiles map
500
520
// Locally, SST Files can be deleted due to RocksDB compaction. These files need
@@ -534,7 +554,7 @@ class RocksDBFileManager(
534
554
.foreach { existingFile =>
535
555
val existingFileSize = existingFile.length()
536
556
val requiredFile = requiredFileNameToFileDetails.get(existingFile.getName)
537
- val prevDfsFile = localFilesToDfsFiles.asScala.get(existingFile.getName)
557
+ val prevDfsFile = fileMappings. localFilesToDfsFiles.asScala.get(existingFile.getName)
538
558
val isSameFile = if (requiredFile.isDefined && prevDfsFile.isDefined) {
539
559
requiredFile.get.dfsFileName == prevDfsFile.get.dfsFileName &&
540
560
existingFile.length() == requiredFile.get.sizeBytes
@@ -544,7 +564,7 @@ class RocksDBFileManager(
544
564
545
565
if (! isSameFile) {
546
566
existingFile.delete()
547
- localFilesToDfsFiles.remove(existingFile.getName)
567
+ fileMappings. localFilesToDfsFiles.remove(existingFile.getName)
548
568
logInfo(s " Deleted local file $existingFile with size $existingFileSize mapped " +
549
569
s " to previous dfsFile ${prevDfsFile.getOrElse(" null" )}" )
550
570
} else {
@@ -574,7 +594,7 @@ class RocksDBFileManager(
574
594
}
575
595
filesCopied += 1
576
596
bytesCopied += localFileSize
577
- localFilesToDfsFiles.put(localFileName, file)
597
+ fileMappings. localFilesToDfsFiles.put(localFileName, file)
578
598
logInfo(s " Copied $dfsFile to $localFile - $localFileSize bytes " )
579
599
} else {
580
600
filesReused += 1
@@ -592,13 +612,13 @@ class RocksDBFileManager(
592
612
private def removeLocallyDeletedSSTFilesFromDfsMapping (localFiles : Seq [File ]): Unit = {
593
613
// clean up deleted SST files from the localFilesToDfsFiles Map
594
614
val currentLocalFiles = localFiles.map(_.getName).toSet
595
- val mappingsToClean = localFilesToDfsFiles.asScala
615
+ val mappingsToClean = fileMappings. localFilesToDfsFiles.asScala
596
616
.keys
597
617
.filterNot(currentLocalFiles.contains)
598
618
599
619
mappingsToClean.foreach { f =>
600
620
logInfo(s " cleaning $f from the localFilesToDfsFiles map " )
601
- localFilesToDfsFiles.remove(f)
621
+ fileMappings. localFilesToDfsFiles.remove(f)
602
622
}
603
623
}
604
624
@@ -705,6 +725,20 @@ class RocksDBFileManager(
705
725
}
706
726
}
707
727
728
+ /**
729
+ * Track file mappings in RocksDB across local and remote directories
730
+ * @param versionToRocksDBFiles Mapping of RocksDB files used across versions for maintenance
731
+ * @param localFilesToDfsFiles Mapping of the exact Dfs file used to create a local SST file
732
+ * The reason localFilesToDfsFiles is a separate map because versionToRocksDBFiles can contain
733
+ * multiple similar SST files to a particular local file (for example 1.sst can map to 1-UUID1.sst
734
+ * in v1 and 1-UUID2.sst in v2). We need to capture the exact file used to ensure Version ID
735
+ * compatibility across SST files and RocksDB manifest.
736
+ */
737
+
738
+ case class RocksDBFileMappings (
739
+ versionToRocksDBFiles : ConcurrentHashMap [Long , Seq [RocksDBImmutableFile ]],
740
+ localFilesToDfsFiles : ConcurrentHashMap [String , RocksDBImmutableFile ])
741
+
708
742
/**
709
743
* Metrics regarding RocksDB file sync between local and DFS.
710
744
*/
0 commit comments