clone_solana_ledger/
bigtable_upload.rs1use {
2 crate::blockstore::Blockstore,
3 crossbeam_channel::{bounded, unbounded},
4 log::*,
5 clone_solana_measure::measure::Measure,
6 clone_solana_sdk::clock::Slot,
7 std::{
8 cmp::{max, min},
9 collections::HashSet,
10 result::Result,
11 sync::{
12 atomic::{AtomicBool, Ordering},
13 Arc,
14 },
15 time::{Duration, Instant},
16 },
17};
18
19#[derive(Clone)]
20pub struct ConfirmedBlockUploadConfig {
21 pub force_reupload: bool,
22 pub max_num_slots_to_check: usize,
23 pub num_blocks_to_upload_in_parallel: usize,
24 pub block_read_ahead_depth: usize, }
26
27impl Default for ConfirmedBlockUploadConfig {
28 fn default() -> Self {
29 let num_blocks_to_upload_in_parallel = num_cpus::get() / 2;
30 ConfirmedBlockUploadConfig {
31 force_reupload: false,
32 max_num_slots_to_check: num_blocks_to_upload_in_parallel * 4,
33 num_blocks_to_upload_in_parallel,
34 block_read_ahead_depth: num_blocks_to_upload_in_parallel * 2,
35 }
36 }
37}
38
39struct BlockstoreLoadStats {
40 pub num_blocks_read: usize,
41 pub elapsed: Duration,
42}
43
44pub async fn upload_confirmed_blocks(
48 blockstore: Arc<Blockstore>,
49 bigtable: clone_solana_storage_bigtable::LedgerStorage,
50 starting_slot: Slot,
51 ending_slot: Slot,
52 config: ConfirmedBlockUploadConfig,
53 exit: Arc<AtomicBool>,
54) -> Result<Slot, Box<dyn std::error::Error>> {
55 let mut measure = Measure::start("entire upload");
56
57 info!(
58 "Loading ledger slots from {} to {}",
59 starting_slot, ending_slot
60 );
61 let blockstore_slots: Vec<_> = blockstore
62 .rooted_slot_iterator(starting_slot)
63 .map_err(|err| {
64 format!("Failed to load entries starting from slot {starting_slot}: {err:?}")
65 })?
66 .take_while(|slot| *slot <= ending_slot)
67 .collect();
68
69 if blockstore_slots.is_empty() {
70 warn!("Ledger has no slots from {starting_slot} to {ending_slot:?}");
71 return Ok(ending_slot);
72 }
73
74 let first_blockstore_slot = *blockstore_slots.first().unwrap();
75 let last_blockstore_slot = *blockstore_slots.last().unwrap();
76 info!(
77 "Found {} slots in the range ({}, {})",
78 blockstore_slots.len(),
79 first_blockstore_slot,
80 last_blockstore_slot,
81 );
82
83 let bigtable_slots = if !config.force_reupload {
85 let mut bigtable_slots = vec![];
86 info!(
87 "Loading list of bigtable blocks between slots {} and {}...",
88 first_blockstore_slot, last_blockstore_slot
89 );
90
91 let mut start_slot = first_blockstore_slot;
92 while start_slot <= last_blockstore_slot {
93 let mut next_bigtable_slots = loop {
94 let num_bigtable_blocks = min(1000, config.max_num_slots_to_check * 2);
95 match bigtable
96 .get_confirmed_blocks(start_slot, num_bigtable_blocks)
97 .await
98 {
99 Ok(slots) => break slots,
100 Err(err) => {
101 error!("get_confirmed_blocks for {} failed: {:?}", start_slot, err);
102 tokio::time::sleep(Duration::from_secs(2)).await;
104 }
105 }
106 };
107 if next_bigtable_slots.is_empty() {
108 break;
109 }
110 bigtable_slots.append(&mut next_bigtable_slots);
111 start_slot = bigtable_slots.last().unwrap() + 1;
112 }
113 bigtable_slots
114 .into_iter()
115 .filter(|slot| *slot <= last_blockstore_slot)
116 .collect::<Vec<_>>()
117 } else {
118 Vec::new()
119 };
120
121 let blocks_to_upload = {
124 let blockstore_slots = blockstore_slots.into_iter().collect::<HashSet<_>>();
125 let bigtable_slots = bigtable_slots.into_iter().collect::<HashSet<_>>();
126
127 let mut blocks_to_upload = blockstore_slots
128 .difference(&bigtable_slots)
129 .cloned()
130 .collect::<Vec<_>>();
131 blocks_to_upload.sort_unstable();
132 blocks_to_upload.truncate(config.max_num_slots_to_check);
133 blocks_to_upload
134 };
135
136 if blocks_to_upload.is_empty() {
137 info!(
138 "No blocks between {} and {} need to be uploaded to bigtable",
139 starting_slot, ending_slot
140 );
141 return Ok(ending_slot);
142 }
143 let last_slot = *blocks_to_upload.last().unwrap();
144 info!(
145 "{} blocks to be uploaded to the bucket in the range ({}, {})",
146 blocks_to_upload.len(),
147 blocks_to_upload.first().unwrap(),
148 last_slot
149 );
150
151 let (loader_threads, receiver): (Vec<_>, _) = {
153 let exit = exit.clone();
154
155 let (sender, receiver) = bounded(config.block_read_ahead_depth);
156
157 let (slot_sender, slot_receiver) = unbounded();
158 blocks_to_upload
159 .into_iter()
160 .for_each(|b| slot_sender.send(b).unwrap());
161 drop(slot_sender);
162
163 (
164 (0..config.num_blocks_to_upload_in_parallel)
165 .map(|i| {
166 let blockstore = blockstore.clone();
167 let sender = sender.clone();
168 let slot_receiver = slot_receiver.clone();
169 let exit = exit.clone();
170 std::thread::Builder::new()
171 .name(format!("solBigTGetBlk{i:02}"))
172 .spawn(move || {
173 let start = Instant::now();
174 let mut num_blocks_read = 0;
175
176 while let Ok(slot) = slot_receiver.recv() {
177 if exit.load(Ordering::Relaxed) {
178 break;
179 }
180
181 let _ = match blockstore.get_rooted_block_with_entries(slot, true) {
182 Ok(confirmed_block_with_entries) => {
183 num_blocks_read += 1;
184 sender.send((slot, Some(confirmed_block_with_entries)))
185 }
186 Err(err) => {
187 warn!(
188 "Failed to get load confirmed block from slot {}: {:?}",
189 slot, err
190 );
191 sender.send((slot, None))
192 }
193 };
194 }
195 BlockstoreLoadStats {
196 num_blocks_read,
197 elapsed: start.elapsed(),
198 }
199 })
200 .unwrap()
201 })
202 .collect(),
203 receiver,
204 )
205 };
206
207 let mut failures = 0;
208 use futures::stream::StreamExt;
209
210 let mut stream =
211 tokio_stream::iter(receiver.into_iter()).chunks(config.num_blocks_to_upload_in_parallel);
212
213 while let Some(blocks) = stream.next().await {
214 if exit.load(Ordering::Relaxed) {
215 break;
216 }
217
218 let mut measure_upload = Measure::start("Upload");
219 let mut num_blocks = blocks.len();
220 info!("Preparing the next {} blocks for upload", num_blocks);
221
222 let uploads = blocks.into_iter().filter_map(|(slot, block)| match block {
223 None => {
224 num_blocks -= 1;
225 None
226 }
227 Some(confirmed_block) => {
228 let bt = bigtable.clone();
229 Some(tokio::spawn(async move {
230 bt.upload_confirmed_block_with_entries(slot, confirmed_block)
231 .await
232 }))
233 }
234 });
235
236 for result in futures::future::join_all(uploads).await {
237 if let Err(err) = result {
238 error!("upload_confirmed_block() join failed: {:?}", err);
239 failures += 1;
240 } else if let Err(err) = result.unwrap() {
241 error!("upload_confirmed_block() upload failed: {:?}", err);
242 failures += 1;
243 }
244 }
245
246 measure_upload.stop();
247 info!("{} for {} blocks", measure_upload, num_blocks);
248 }
249
250 measure.stop();
251 info!("{}", measure);
252
253 let blockstore_results = loader_threads.into_iter().map(|t| t.join());
254
255 let mut blockstore_num_blocks_read = 0;
256 let mut blockstore_load_wallclock = Duration::default();
257 let mut blockstore_errors = 0;
258
259 for r in blockstore_results {
260 match r {
261 Ok(stats) => {
262 blockstore_num_blocks_read += stats.num_blocks_read;
263 blockstore_load_wallclock = max(stats.elapsed, blockstore_load_wallclock);
264 }
265 Err(e) => {
266 error!("error joining blockstore thread: {:?}", e);
267 blockstore_errors += 1;
268 }
269 }
270 }
271
272 info!(
273 "blockstore upload took {:?} for {} blocks ({:.2} blocks/s) errors: {}",
274 blockstore_load_wallclock,
275 blockstore_num_blocks_read,
276 blockstore_num_blocks_read as f64 / blockstore_load_wallclock.as_secs_f64(),
277 blockstore_errors
278 );
279
280 if failures > 0 {
281 Err(format!("Incomplete upload, {failures} operations failed").into())
282 } else {
283 Ok(last_slot)
284 }
285}