archive.rs 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. use {
  2. crate::{
  3. error::ArchiveSnapshotPackageError, paths, snapshot_archive_info::SnapshotArchiveInfo,
  4. snapshot_hash::SnapshotHash, ArchiveFormat, Result, SnapshotArchiveKind,
  5. },
  6. log::info,
  7. solana_accounts_db::{
  8. account_storage::AccountStoragesOrderer, account_storage_reader::AccountStorageReader,
  9. accounts_db::AccountStorageEntry, accounts_file::AccountsFile,
  10. },
  11. solana_clock::Slot,
  12. solana_measure::measure::Measure,
  13. solana_metrics::datapoint_info,
  14. std::{fs, io::Write, path::Path, sync::Arc},
  15. };
  16. // Balance large and small files order in snapshot tar with bias towards small (4 small + 1 large),
  17. // such that during unpacking large writes are mixed with file metadata operations
  18. // and towards the end of archive (sizes equalize) writes are >256KiB / file.
  19. const INTERLEAVE_TAR_ENTRIES_SMALL_TO_LARGE_RATIO: (usize, usize) = (4, 1);
  20. /// Archives a snapshot into `archive_path`
  21. pub fn archive_snapshot(
  22. snapshot_archive_kind: SnapshotArchiveKind,
  23. snapshot_slot: Slot,
  24. snapshot_hash: SnapshotHash,
  25. snapshot_storages: &[Arc<AccountStorageEntry>],
  26. bank_snapshot_dir: impl AsRef<Path>,
  27. archive_path: impl AsRef<Path>,
  28. archive_format: ArchiveFormat,
  29. ) -> Result<SnapshotArchiveInfo> {
  30. use ArchiveSnapshotPackageError as E;
  31. const ACCOUNTS_DIR: &str = "accounts";
  32. info!("Generating snapshot archive for slot {snapshot_slot}, kind: {snapshot_archive_kind:?}");
  33. let mut timer = Measure::start("snapshot_package-package_snapshots");
  34. let tar_dir = archive_path
  35. .as_ref()
  36. .parent()
  37. .expect("Tar output path is invalid");
  38. fs::create_dir_all(tar_dir).map_err(|err| E::CreateArchiveDir(err, tar_dir.to_path_buf()))?;
  39. // Create the staging directories
  40. let staging_dir_prefix = paths::TMP_SNAPSHOT_ARCHIVE_PREFIX;
  41. let staging_dir = tempfile::Builder::new()
  42. .prefix(&format!("{staging_dir_prefix}{snapshot_slot}-"))
  43. .tempdir_in(tar_dir)
  44. .map_err(|err| E::CreateStagingDir(err, tar_dir.to_path_buf()))?;
  45. let staging_snapshots_dir = staging_dir.path().join(paths::BANK_SNAPSHOTS_DIR);
  46. let slot_str = snapshot_slot.to_string();
  47. let staging_snapshot_dir = staging_snapshots_dir.join(&slot_str);
  48. // Creates staging snapshots/<slot>/
  49. fs::create_dir_all(&staging_snapshot_dir)
  50. .map_err(|err| E::CreateSnapshotStagingDir(err, staging_snapshot_dir.clone()))?;
  51. // To be a source for symlinking and archiving, the path need to be an absolute path
  52. let src_snapshot_dir = bank_snapshot_dir.as_ref().canonicalize().map_err(|err| {
  53. E::CanonicalizeSnapshotSourceDir(err, bank_snapshot_dir.as_ref().to_path_buf())
  54. })?;
  55. let staging_snapshot_file = staging_snapshot_dir.join(&slot_str);
  56. let src_snapshot_file = src_snapshot_dir.join(slot_str);
  57. symlink::symlink_file(&src_snapshot_file, &staging_snapshot_file)
  58. .map_err(|err| E::SymlinkSnapshot(err, src_snapshot_file, staging_snapshot_file))?;
  59. // Following the existing archive format, the status cache is under snapshots/, not under <slot>/
  60. // like in the snapshot dir.
  61. let staging_status_cache = staging_snapshots_dir.join(paths::SNAPSHOT_STATUS_CACHE_FILENAME);
  62. let src_status_cache = src_snapshot_dir.join(paths::SNAPSHOT_STATUS_CACHE_FILENAME);
  63. symlink::symlink_file(&src_status_cache, &staging_status_cache)
  64. .map_err(|err| E::SymlinkStatusCache(err, src_status_cache, staging_status_cache))?;
  65. // The bank snapshot has the version file, so symlink it to the correct staging path
  66. let staging_version_file = staging_dir.path().join(paths::SNAPSHOT_VERSION_FILENAME);
  67. let src_version_file = src_snapshot_dir.join(paths::SNAPSHOT_VERSION_FILENAME);
  68. symlink::symlink_file(&src_version_file, &staging_version_file).map_err(|err| {
  69. E::SymlinkVersionFile(err, src_version_file, staging_version_file.clone())
  70. })?;
  71. // Tar the staging directory into the archive at `staging_archive_path`
  72. let staging_archive_path = tar_dir.join(format!(
  73. "{}{}.{}",
  74. staging_dir_prefix,
  75. snapshot_slot,
  76. archive_format.extension(),
  77. ));
  78. {
  79. let archive_file = fs::File::create(&staging_archive_path)
  80. .map_err(|err| E::CreateArchiveFile(err, staging_archive_path.clone()))?;
  81. let do_archive_files = |encoder: &mut dyn Write| -> std::result::Result<(), E> {
  82. let mut archive = tar::Builder::new(encoder);
  83. // Disable sparse file handling. This seems to be the root cause of an issue when
  84. // upgrading v2.0 to v2.1, and the tar crate from 0.4.41 to 0.4.42.
  85. // Since the tarball will still go through compression (zstd/etc) afterwards, disabling
  86. // sparse handling in the tar itself should be fine.
  87. //
  88. // Likely introduced in [^1]. Tracking resolution in [^2].
  89. // [^1] https://github.com/alexcrichton/tar-rs/pull/375
  90. // [^2] https://github.com/alexcrichton/tar-rs/issues/403
  91. archive.sparse(false);
  92. // Serialize the version and snapshots files before accounts so we can quickly determine the version
  93. // and other bank fields. This is necessary if we want to interleave unpacking with reconstruction
  94. archive
  95. .append_path_with_name(&staging_version_file, paths::SNAPSHOT_VERSION_FILENAME)
  96. .map_err(E::ArchiveVersionFile)?;
  97. archive
  98. .append_dir_all(paths::BANK_SNAPSHOTS_DIR, &staging_snapshots_dir)
  99. .map_err(E::ArchiveSnapshotsDir)?;
  100. let storages_orderer = AccountStoragesOrderer::with_small_to_large_ratio(
  101. snapshot_storages,
  102. INTERLEAVE_TAR_ENTRIES_SMALL_TO_LARGE_RATIO,
  103. );
  104. for storage in storages_orderer.iter() {
  105. let path_in_archive = Path::new(ACCOUNTS_DIR)
  106. .join(AccountsFile::file_name(storage.slot(), storage.id()));
  107. let reader =
  108. AccountStorageReader::new(storage, Some(snapshot_slot)).map_err(|err| {
  109. E::AccountStorageReaderError(err, storage.path().to_path_buf())
  110. })?;
  111. let mut header = tar::Header::new_gnu();
  112. header.set_path(path_in_archive).map_err(|err| {
  113. E::ArchiveAccountStorageFile(err, storage.path().to_path_buf())
  114. })?;
  115. header.set_size(reader.len() as u64);
  116. header.set_cksum();
  117. archive.append(&header, reader).map_err(|err| {
  118. E::ArchiveAccountStorageFile(err, storage.path().to_path_buf())
  119. })?;
  120. }
  121. archive.into_inner().map_err(E::FinishArchive)?;
  122. Ok(())
  123. };
  124. match archive_format {
  125. ArchiveFormat::TarZstd { config } => {
  126. let mut encoder =
  127. zstd::stream::Encoder::new(archive_file, config.compression_level)
  128. .map_err(E::CreateEncoder)?;
  129. do_archive_files(&mut encoder)?;
  130. encoder.finish().map_err(E::FinishEncoder)?;
  131. }
  132. ArchiveFormat::TarLz4 => {
  133. let mut encoder = lz4::EncoderBuilder::new()
  134. .level(1)
  135. .build(archive_file)
  136. .map_err(E::CreateEncoder)?;
  137. do_archive_files(&mut encoder)?;
  138. let (_output, result) = encoder.finish();
  139. result.map_err(E::FinishEncoder)?;
  140. }
  141. };
  142. }
  143. // Atomically move the archive into position for other validators to find
  144. let metadata = fs::metadata(&staging_archive_path)
  145. .map_err(|err| E::QueryArchiveMetadata(err, staging_archive_path.clone()))?;
  146. let archive_path = archive_path.as_ref().to_path_buf();
  147. fs::rename(&staging_archive_path, &archive_path)
  148. .map_err(|err| E::MoveArchive(err, staging_archive_path, archive_path.clone()))?;
  149. timer.stop();
  150. info!(
  151. "Successfully created {}. slot: {}, elapsed ms: {}, size: {}",
  152. archive_path.display(),
  153. snapshot_slot,
  154. timer.as_ms(),
  155. metadata.len()
  156. );
  157. datapoint_info!(
  158. "archive-snapshot-package",
  159. ("slot", snapshot_slot, i64),
  160. ("archive_format", archive_format.to_string(), String),
  161. ("duration_ms", timer.as_ms(), i64),
  162. (
  163. if snapshot_archive_kind == SnapshotArchiveKind::Full {
  164. "full-snapshot-archive-size"
  165. } else {
  166. "incremental-snapshot-archive-size"
  167. },
  168. metadata.len(),
  169. i64
  170. ),
  171. );
  172. Ok(SnapshotArchiveInfo {
  173. path: archive_path,
  174. slot: snapshot_slot,
  175. hash: snapshot_hash,
  176. archive_format,
  177. })
  178. }