@@ -131,12 +131,21 @@ private ObjectMetadata getObjectMetadata(File file) {
131131
132132 private long uploadMultipart (AbstractBackupPath path , Instant target )
133133 throws BackupRestoreException {
134+ if (config .useReusableBufferForMultipartUploads ()) {
135+ return uploadMultipartWithBuffers (path , target );
136+ } else {
137+ return uploadMultipartLegacy (path , target );
138+ }
139+ }
140+
141+ private long uploadMultipartWithBuffers (AbstractBackupPath path , Instant target )
142+ throws BackupRestoreException {
134143 Path localPath = Paths .get (path .getBackupFile ().getAbsolutePath ());
135144 String remotePath = path .getRemotePath ();
136145 long chunkSize = getChunkSize (localPath );
137146 String prefix = config .getBackupPrefix ();
138147 if (logger .isDebugEnabled ())
139- logger .debug ("Uploading to {}/{} with chunk size {}" , prefix , remotePath , chunkSize );
148+ logger .debug ("Uploading to {}/{} with chunk size {} (using ByteBuffer implementation) " , prefix , remotePath , chunkSize );
140149 File localFile = localPath .toFile ();
141150 long fileSize = localFile .length ();
142151
@@ -244,13 +253,74 @@ public UploadPartResult retriableCall() {
244253 }
245254 }
246255
256+ private long uploadMultipartLegacy (AbstractBackupPath path , Instant target )
257+ throws BackupRestoreException {
258+ Path localPath = Paths .get (path .getBackupFile ().getAbsolutePath ());
259+ String remotePath = path .getRemotePath ();
260+ long chunkSize = getChunkSize (localPath );
261+ String prefix = config .getBackupPrefix ();
262+ if (logger .isDebugEnabled ())
263+ logger .debug ("Uploading to {}/{} with chunk size {} (using legacy implementation)" , prefix , remotePath , chunkSize );
264+ File localFile = localPath .toFile ();
265+ InitiateMultipartUploadRequest initRequest =
266+ new InitiateMultipartUploadRequest (prefix , remotePath )
267+ .withObjectMetadata (getObjectMetadata (localFile ));
268+ String uploadId = s3Client .initiateMultipartUpload (initRequest ).getUploadId ();
269+ DataPart part = new DataPart (prefix , remotePath , uploadId );
270+ List <PartETag > partETags = Collections .synchronizedList (new ArrayList <>());
271+
272+ try (InputStream in = new FileInputStream (localFile )) {
273+ Iterator <byte []> chunks = new ChunkedStream (in , chunkSize , path .getCompression ());
274+ int partNum = 0 ;
275+ AtomicInteger partsPut = new AtomicInteger (0 );
276+ long compressedFileSize = 0 ;
277+
278+ while (chunks .hasNext ()) {
279+ byte [] chunk = chunks .next ();
280+ rateLimiter .acquire (chunk .length );
281+ dynamicRateLimiter .acquire (path , target , chunk .length );
282+ DataPart dp = new DataPart (++partNum , chunk , prefix , remotePath , uploadId );
283+ S3PartUploader partUploader = new S3PartUploader (s3Client , dp , partETags , partsPut );
284+ compressedFileSize += chunk .length ;
285+ executor .submit (partUploader );
286+ }
287+
288+ executor .sleepTillEmpty ();
289+ logger .info ("{} done. part count: {} expected: {}" , localFile , partsPut .get (), partNum );
290+ Preconditions .checkState (partNum == partETags .size (), "part count mismatch" );
291+ CompleteMultipartUploadResult resultS3MultiPartUploadComplete =
292+ new S3PartUploader (s3Client , part , partETags ).completeUpload ();
293+ checkSuccessfulUpload (resultS3MultiPartUploadComplete , localPath );
294+
295+ if (logger .isDebugEnabled ()) {
296+ final S3ResponseMetadata info = s3Client .getCachedResponseMetadata (initRequest );
297+ logger .debug ("Request Id: {}, Host Id: {}" , info .getRequestId (), info .getHostId ());
298+ }
299+
300+ return compressedFileSize ;
301+ } catch (Exception e ) {
302+ new S3PartUploader (s3Client , part , partETags ).abortUpload ();
303+ throw new BackupRestoreException ("Error uploading file: " + localPath .toString (), e );
304+ }
305+ }
306+
247307 protected long uploadFileImpl (AbstractBackupPath path , Instant target )
248308 throws BackupRestoreException {
249309 File localFile = Paths .get (path .getBackupFile ().getAbsolutePath ()).toFile ();
250310
251311 if (localFile .length () >= config .getBackupChunkSize ())
252312 return uploadMultipart (path , target );
253313
314+ if (config .useReusableBufferForMultipartUploads ()) {
315+ return uploadFileWithByteBuffer (path , target );
316+ } else {
317+ return uploadFileWithByteArray (path , target );
318+ }
319+ }
320+
321+ private long uploadFileWithByteBuffer (AbstractBackupPath path , Instant target )
322+ throws BackupRestoreException {
323+ File localFile = Paths .get (path .getBackupFile ().getAbsolutePath ()).toFile ();
254324 ByteBuffer buffer = getFileByteBuffer (path );
255325 // C* snapshots may have empty files. That is probably unintentional.
256326 if (buffer .remaining () > 0 ) {
@@ -261,7 +331,7 @@ protected long uploadFileImpl(AbstractBackupPath path, Instant target)
261331 new BoundedExponentialRetryCallable <PutObjectResult >(1000 , 10000 , 5 ) {
262332 @ Override
263333 public PutObjectResult retriableCall () {
264- return s3Client .putObject (generatePut (path , buffer ));
334+ return s3Client .putObject (generatePutFromBuffer (path , buffer ));
265335 }
266336 }.call ();
267337 } catch (Exception e ) {
@@ -270,7 +340,29 @@ public PutObjectResult retriableCall() {
270340 return buffer .remaining ();
271341 }
272342
273- private PutObjectRequest generatePut (AbstractBackupPath path , ByteBuffer buffer ) {
343+ private long uploadFileWithByteArray (AbstractBackupPath path , Instant target )
344+ throws BackupRestoreException {
345+ File localFile = Paths .get (path .getBackupFile ().getAbsolutePath ()).toFile ();
346+ byte [] chunk = getFileContents (path );
347+ // C* snapshots may have empty files. That is probably unintentional.
348+ if (chunk .length > 0 ) {
349+ rateLimiter .acquire (chunk .length );
350+ dynamicRateLimiter .acquire (path , target , chunk .length );
351+ }
352+ try {
353+ new BoundedExponentialRetryCallable <PutObjectResult >(1000 , 10000 , 5 ) {
354+ @ Override
355+ public PutObjectResult retriableCall () {
356+ return s3Client .putObject (generatePutFromByteArray (path , chunk ));
357+ }
358+ }.call ();
359+ } catch (Exception e ) {
360+ throw new BackupRestoreException ("Error uploading file: " + localFile .getName (), e );
361+ }
362+ return chunk .length ;
363+ }
364+
365+ private PutObjectRequest generatePutFromBuffer (AbstractBackupPath path , ByteBuffer buffer ) {
274366 File localFile = Paths .get (path .getBackupFile ().getAbsolutePath ()).toFile ();
275367 ObjectMetadata metadata = getObjectMetadata (localFile );
276368 metadata .setContentLength (buffer .remaining ());
@@ -286,6 +378,22 @@ private PutObjectRequest generatePut(AbstractBackupPath path, ByteBuffer buffer)
286378 return put ;
287379 }
288380
381+ private PutObjectRequest generatePutFromByteArray (AbstractBackupPath path , byte [] chunk ) {
382+ File localFile = Paths .get (path .getBackupFile ().getAbsolutePath ()).toFile ();
383+ ObjectMetadata metadata = getObjectMetadata (localFile );
384+ metadata .setContentLength (chunk .length );
385+ PutObjectRequest put =
386+ new PutObjectRequest (
387+ config .getBackupPrefix (),
388+ path .getRemotePath (),
389+ new ByteArrayInputStream (chunk ),
390+ metadata );
391+ if (config .addMD5ToBackupUploads ()) {
392+ put .getMetadata ().setContentMD5 (SystemUtils .toBase64 (SystemUtils .md5 (chunk )));
393+ }
394+ return put ;
395+ }
396+
289397 @ VisibleForTesting
290398 public ByteBuffer getFileByteBuffer (AbstractBackupPath path ) throws BackupRestoreException {
291399 File localFile = Paths .get (path .getBackupFile ().getAbsolutePath ()).toFile ();
@@ -314,4 +422,19 @@ public ByteBuffer getFileByteBuffer(AbstractBackupPath path) throws BackupRestor
314422 throw new BackupRestoreException ("Error reading file: " + localFile .getName (), e );
315423 }
316424 }
425+
426+ private byte [] getFileContents (AbstractBackupPath path ) throws BackupRestoreException {
427+ File localFile = Paths .get (path .getBackupFile ().getAbsolutePath ()).toFile ();
428+ try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream ();
429+ InputStream in = new BufferedInputStream (new FileInputStream (localFile ))) {
430+ Iterator <byte []> chunks =
431+ new ChunkedStream (in , config .getBackupChunkSize (), path .getCompression ());
432+ while (chunks .hasNext ()) {
433+ byteArrayOutputStream .write (chunks .next ());
434+ }
435+ return byteArrayOutputStream .toByteArray ();
436+ } catch (Exception e ) {
437+ throw new BackupRestoreException ("Error reading file: " + localFile .getName (), e );
438+ }
439+ }
317440}
0 commit comments