Skip to content

Commit 4f28018

Browse files
Yuya Shirakifacebook-github-bot
authored andcommitted
split S3 files into smaller files to send large union file (facebookresearch#77)
Summary: Pull Request resolved: facebookresearch#77 # Context We found that AWS-SDK S3 API would fail when we try to write more than 5GB of data. It is a blocking us to do capacity testing for a larger FARGATE container. In this diff, as mentioned in [the post](https://fb.workplace.com/groups/pidmatchingxfn/posts/493743615908631), we are splitting union file based on number of rows. # Description We have made following changes. - Added new arg `s3api_max_rows` in the private-id-multi-key-client and private-id-multi-key-server binaries. We will use this to split a file for S3 upload. - Added an optional arg `num_split` in save_id_map() and writer_helper(). When `num_split` is specified, it would use the arg `path` as its prefix and save files in `{path}_0`, `{path}_1`, etc. - In rpc_server.rs and client.rs, calculates the num_split based on s3api_max_rows, and passes the num_split arg for S3 only. Then, for each split file, it calls copy_from_local(). Differential Revision: D39219674 fbshipit-source-id: 82dc1788b0d4db5cf9c3de07178b52a8cc11633c
1 parent 84724a1 commit 4f28018

7 files changed

Lines changed: 183 additions & 44 deletions

File tree

protocol-rpc/src/rpc/private-id-multi-key/client.rs

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
100100
.long("run_id")
101101
.default_value("")
102102
.help("A run_id used to identify all the logs in a PL/PA run."),
103+
Arg::with_name("s3api_max_rows")
104+
.long("s3api_max_rows")
105+
.takes_value(true)
106+
.default_value("5000000")
107+
.help("Number of rows per each output S3 file to split."),
103108
])
104109
.groups(&[
105110
ArgGroup::with_name("tls")
@@ -114,6 +119,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
114119
let global_timer = timer::Timer::new_silent("global");
115120
let input_path_str = matches.value_of("input").unwrap_or("input.csv");
116121
let mut input_path = input_path_str.to_string();
122+
let s3api_max_rows_str = matches.value_of("s3api_max_rows").unwrap_or("5000000");
123+
let s3_api_max_rows: usize = s3api_max_rows_str.to_string().parse().unwrap();
117124
if let Ok(s3_path) = S3Path::from_str(input_path_str) {
118125
info!(
119126
"Reading {} from S3 and copying to local path",
@@ -358,27 +365,36 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
358365
let s3_tempfile = tempfile::NamedTempFile::new().unwrap();
359366
let (_file, path) = s3_tempfile.keep().unwrap();
360367
let path = path.to_str().expect("Failed to convert path to str");
368+
let num_split = ((partner_protocol.get_id_map_size() as f32)
369+
/ (s3_api_max_rows as f32))
370+
.ceil() as usize;
361371
partner_protocol
362-
.save_id_map(&String::from(path))
372+
.save_id_map(&String::from(path), Some(num_split))
363373
.expect("Failed to save id map to tempfile");
364-
output_path_s3
365-
.copy_from_local(&path)
366-
.await
367-
.expect("Failed to write to S3");
374+
for n in 0..num_split {
375+
let chunk_path = format!("{}_{}", path, n);
376+
output_path_s3
377+
.copy_from_local(&chunk_path)
378+
.await
379+
.expect("Failed to write to S3");
380+
}
368381
} else if let Ok(output_path_gcp) = GCSPath::from_str(p) {
369382
let gcs_tempfile = tempfile::NamedTempFile::new().unwrap();
370383
let (_file, path) = gcs_tempfile.keep().unwrap();
371384
let path = path.to_str().expect("Failed to convert path to str");
372385
partner_protocol
373-
.save_id_map(&String::from(path))
386+
.save_id_map(&String::from(path), None)
374387
.expect("Failed to save id map to tempfile");
375388
output_path_gcp
376389
.copy_from_local(&path)
377390
.await
378391
.expect("Failed to write to GCS");
379392
} else {
393+
let num_split = ((partner_protocol.get_id_map_size() as f32)
394+
/ (s3_api_max_rows as f32))
395+
.ceil() as usize;
380396
partner_protocol
381-
.save_id_map(&String::from(p))
397+
.save_id_map(&String::from(p), Some(num_split))
382398
.expect("Failed to save id map to output file");
383399
}
384400
}

protocol-rpc/src/rpc/private-id-multi-key/rpc_server.rs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ pub struct PrivateIdMultiKeyService {
4343
input_with_headers: bool,
4444
metrics_path: Option<String>,
4545
metrics_obj: metrics::Metrics,
46+
s3_api_max_rows: usize,
4647
pub killswitch: Arc<AtomicBool>,
4748
}
4849

@@ -52,6 +53,7 @@ impl PrivateIdMultiKeyService {
5253
output_path: Option<&str>,
5354
input_with_headers: bool,
5455
metrics_path: Option<String>,
56+
s3_api_max_rows: usize,
5557
) -> PrivateIdMultiKeyService {
5658
PrivateIdMultiKeyService {
5759
protocol: CompanyPrivateIdMultiKey::new(),
@@ -60,6 +62,7 @@ impl PrivateIdMultiKeyService {
6062
input_with_headers,
6163
metrics_path,
6264
metrics_obj: metrics::Metrics::new("private-id-multi-key".to_string()),
65+
s3_api_max_rows,
6366
killswitch: Arc::new(AtomicBool::new(false)),
6467
}
6568
}
@@ -298,26 +301,35 @@ impl PrivateIdMultiKey for PrivateIdMultiKeyService {
298301
let s3_tempfile = tempfile::NamedTempFile::new().unwrap();
299302
let (_file, path) = s3_tempfile.keep().unwrap();
300303
let path = path.to_str().expect("Failed to convert path to str");
304+
let num_split = ((self.protocol.get_id_map_size() as f32)
305+
/ (self.s3_api_max_rows as f32))
306+
.ceil() as usize;
301307
self.protocol
302-
.save_id_map(&String::from(path))
308+
.save_id_map(&String::from(path), Some(num_split))
303309
.expect("Failed to save id map to tempfile");
304-
output_path_s3
305-
.copy_from_local(&path)
306-
.await
307-
.expect("Failed to write to S3");
310+
for n in 0..num_split {
311+
let chunk_path = format!("{}_{}", path, n);
312+
output_path_s3
313+
.copy_from_local(&chunk_path)
314+
.await
315+
.expect("Failed to write to S3");
316+
}
308317
} else if let Ok(output_path_gcp) = GCSPath::from_str(p) {
309318
let gcs_tempfile = tempfile::NamedTempFile::new().unwrap();
310319
let (_file, path) = gcs_tempfile.keep().unwrap();
311320
let path = path.to_str().expect("Failed to convert path to str");
312321
self.protocol
313-
.save_id_map(&String::from(path))
322+
.save_id_map(&String::from(path), None)
314323
.expect("Failed to save id map to tempfile");
315324
output_path_gcp
316325
.copy_from_local(&path)
317326
.await
318327
.expect("Failed to write to GCS");
319328
} else {
320-
self.protocol.save_id_map(p).unwrap();
329+
let num_split = ((self.protocol.get_id_map_size() as f32)
330+
/ (self.s3_api_max_rows as f32))
331+
.ceil() as usize;
332+
self.protocol.save_id_map(p, Some(num_split)).unwrap();
321333
}
322334
}
323335
None => self.protocol.print_id_map(),

protocol-rpc/src/rpc/private-id-multi-key/server.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
9999
.long("run_id")
100100
.default_value("")
101101
.help("A run_id used to identify all the logs in a PL/PA run."),
102+
Arg::with_name("s3api_max_rows")
103+
.long("s3api_max_rows")
104+
.takes_value(true)
105+
.default_value("5000000")
106+
.help("Number of rows per each output S3 file to split."),
102107
])
103108
.groups(&[
104109
ArgGroup::with_name("tls")
@@ -129,6 +134,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
129134
let input_with_headers = matches.is_present("input-with-headers");
130135
let output_path = matches.value_of("output");
131136
let metric_path = matches.value_of("metric-path");
137+
let s3api_max_rows_str = matches.value_of("s3api_max_rows").unwrap_or("5000000");
138+
let s3_api_max_rows: usize = s3api_max_rows_str.to_string().parse().unwrap();
132139

133140
let no_tls = matches.is_present("no-tls");
134141
let host = matches.value_of("host");
@@ -167,6 +174,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
167174
output_path,
168175
input_with_headers,
169176
metrics_output_path,
177+
s3_api_max_rows,
170178
);
171179

172180
let ks = service.killswitch.clone();

protocol/src/private_id_multi_key/company.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ use std::collections::HashMap;
55
use std::sync::Arc;
66
use std::sync::RwLock;
77

8-
use common::files;
98
use common::permutations::gen_permute_pattern;
109
use common::permutations::permute;
1110
use common::permutations::undo_permute;
@@ -481,23 +480,30 @@ impl CompanyPrivateIdMultiKeyProtocol for CompanyPrivateIdMultiKey {
481480
fn print_id_map(&self) {
482481
match (self.plaintext.clone().read(), self.id_map.clone().read()) {
483482
(Ok(data), Ok(id_map)) => {
484-
writer_helper(&data, &id_map, None);
483+
writer_helper(&data, &id_map, None, None);
485484
}
486485
_ => panic!("Cannot print id_map"),
487486
}
488487
}
489488

490-
fn save_id_map(&self, path: &str) -> Result<(), ProtocolError> {
489+
fn save_id_map(&self, path: &str, num_split: Option<usize>) -> Result<(), ProtocolError> {
491490
match (self.plaintext.clone().read(), self.id_map.clone().read()) {
492491
(Ok(data), Ok(id_map)) => {
493-
writer_helper(&data, &id_map, Some(path.to_string()));
492+
writer_helper(&data, &id_map, Some(path.to_string()), num_split);
494493
Ok(())
495494
}
496495
_ => Err(ProtocolError::ErrorIO(
497496
"Unable to write partner view to file".to_string(),
498497
)),
499498
}
500499
}
500+
501+
fn get_id_map_size(&self) -> usize {
502+
match self.id_map.clone().read() {
503+
Ok(id_map) => id_map.len(),
504+
_ => panic!("Cannot get id_map size"),
505+
}
506+
}
501507
}
502508

503509
#[cfg(test)]

protocol/src/private_id_multi_key/mod.rs

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -62,34 +62,53 @@ fn load_data(plaintext: Arc<RwLock<Vec<Vec<String>>>>, path: &str, input_with_he
6262
t.qps("text read", text_len);
6363
}
6464

65-
fn writer_helper(data: &[Vec<String>], id_map: &[(String, usize, bool)], path: Option<String>) {
66-
let mut device = match path {
67-
Some(path) => {
68-
let wr = csv::WriterBuilder::new()
69-
.flexible(true)
70-
.buffer_capacity(1024)
71-
.from_path(path)
72-
.unwrap();
73-
Some(wr)
74-
}
75-
None => None,
76-
};
65+
fn writer_helper(
66+
data: &[Vec<String>],
67+
id_map: &[(String, usize, bool)],
68+
path: Option<String>,
69+
num_split: Option<usize>,
70+
) {
71+
let mut device_list = Vec::new();
72+
let mut chunk_size = id_map.len();
73+
match path {
74+
Some(path) => match num_split {
75+
Some(num_split) => {
76+
for n in 0..num_split {
77+
let chunk_path = format!("{}_{}", path, n);
78+
let wr = csv::WriterBuilder::new()
79+
.flexible(true)
80+
.buffer_capacity(1024)
81+
.from_path(chunk_path)
82+
.unwrap();
83+
device_list.push(wr);
84+
chunk_size = ((id_map.len() as f32) / (num_split as f32)).ceil() as usize;
85+
}
86+
}
87+
None => {
88+
let wr = csv::WriterBuilder::new()
89+
.flexible(true)
90+
.buffer_capacity(1024)
91+
.from_path(path)
92+
.unwrap();
93+
device_list.push(wr);
94+
}
95+
},
96+
None => (),
97+
}
7798

78-
for (key, idx, flag) in id_map.iter() {
99+
for (pos, (key, idx, flag)) in id_map.iter().enumerate() {
79100
let mut v = vec![(*key).clone()];
80101

81102
match flag {
82103
true => v.extend(data[*idx].clone()),
83104
false => v.push("NA".to_string()),
84105
}
85106

86-
match device {
87-
Some(ref mut wr) => {
88-
wr.write_record(v.as_slice()).unwrap();
89-
}
90-
None => {
91-
println!("{}", v.join(","));
92-
}
107+
if device_list.is_empty() {
108+
println!("{}", v.join(","));
109+
} else {
110+
let device = &mut device_list[pos / chunk_size];
111+
device.write_record(v.as_slice()).unwrap();
93112
}
94113
}
95114
}

0 commit comments

Comments
 (0)