Skip to content

Commit 76dc706

Browse files
committed
Add retry logic for COPY operation in SnowflakeCopyBatchInsert with configurable max retries
- Introduced a new configuration parameter max_copy_retries to specify the maximum number of retries for the COPY operation in Snowflake. - Updated SnowflakeOutputPlugin to include max_copy_retries configuration. - Modified SnowflakeCopyBatchInsert to handle retries for the COPY operation using the newly added parameter. - Added a method isRetryableForCopyTask to determine whether a COPY error is retryable. - Adjusted constructor and method signatures to include maxCopyRetries where necessary. - Ensured proper resource cleanup and retry handling for the COPY task. - Improved logging for retries to provide more context during errors.
1 parent b7ab45d commit 76dc706

File tree

2 files changed

+59
-23
lines changed

2 files changed

+59
-23
lines changed

src/main/java/org/embulk/output/SnowflakeOutputPlugin.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ public interface SnowflakePluginTask extends PluginTask {
7575
@ConfigDefault("3")
7676
public int getMaxUploadRetries();
7777

78+
@Config("max_copy_retries")
79+
@ConfigDefault("3")
80+
public int getMaxCopyRetries();
81+
7882
@Config("empty_field_as_null")
7983
@ConfigDefault("true")
8084
public boolean getEmtpyFieldAsNull();
@@ -321,6 +325,7 @@ protected BatchInsert newBatchInsert(PluginTask task, Optional<MergeConfig> merg
321325
pluginTask.getCopyIntoCSVColumnNumbers(),
322326
false,
323327
pluginTask.getMaxUploadRetries(),
328+
pluginTask.getMaxCopyRetries(),
324329
pluginTask.getEmtpyFieldAsNull());
325330
}
326331

src/main/java/org/embulk/output/snowflake/SnowflakeCopyBatchInsert.java

Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public class SnowflakeCopyBatchInsert implements BatchInsert {
2727
protected static final String newLineString = "\n";
2828
protected static final String delimiterString = "\t";
2929
private final int maxUploadRetries;
30+
private final int maxCopyRetries;
3031

3132
private SnowflakeOutputConnection connection = null;
3233
private TableIdentifier tableIdentifier = null;
@@ -51,6 +52,7 @@ public SnowflakeCopyBatchInsert(
5152
int[] copyIntoCSVColumnNumbers,
5253
boolean deleteStageFile,
5354
int maxUploadRetries,
55+
int maxCopyRetries,
5456
boolean emptyFieldAsNull)
5557
throws IOException {
5658
this.index = 0;
@@ -63,6 +65,7 @@ public SnowflakeCopyBatchInsert(
6365
this.deleteStageFile = deleteStageFile;
6466
this.uploadAndCopyFutures = new ArrayList();
6567
this.maxUploadRetries = maxUploadRetries;
68+
this.maxCopyRetries = maxCopyRetries;
6669
this.emptyFieldAsNull = emptyFieldAsNull;
6770
}
6871

@@ -270,7 +273,8 @@ public void flush() throws IOException, SQLException {
270273
Future<Void> uploadFuture = executorService.submit(uploadTask);
271274
uploadAndCopyFutures.add(uploadFuture);
272275

273-
CopyTask copyTask = new CopyTask(uploadFuture, snowflakeStageFileName, emptyFieldAsNull);
276+
CopyTask copyTask =
277+
new CopyTask(uploadFuture, snowflakeStageFileName, emptyFieldAsNull, maxCopyRetries);
274278
uploadAndCopyFutures.add(executorService.submit(copyTask));
275279

276280
fileCount++;
@@ -404,40 +408,62 @@ private class CopyTask implements Callable<Void> {
404408
private final Future<Void> uploadFuture;
405409
private final String snowflakeStageFileName;
406410
private final boolean emptyFieldAsNull;
411+
private final int maxCopyRetries;
407412

408413
public CopyTask(
409-
Future<Void> uploadFuture, String snowflakeStageFileName, boolean emptyFieldAsNull) {
414+
Future<Void> uploadFuture,
415+
String snowflakeStageFileName,
416+
boolean emptyFieldAsNull,
417+
int maxCopyRetries) {
410418
this.uploadFuture = uploadFuture;
411419
this.snowflakeStageFileName = snowflakeStageFileName;
412420
this.emptyFieldAsNull = emptyFieldAsNull;
421+
this.maxCopyRetries = maxCopyRetries;
413422
}
414423

415424
public Void call() throws SQLException, InterruptedException, ExecutionException {
416425
try {
417426
uploadFuture.get();
418427

419428
SnowflakeOutputConnection con = (SnowflakeOutputConnection) connector.connect(true);
420-
try {
421-
logger.info("Running COPY from file {}", snowflakeStageFileName);
422-
423-
long startTime = System.currentTimeMillis();
424-
con.runCopy(
425-
tableIdentifier,
426-
stageIdentifier,
427-
snowflakeStageFileName,
428-
copyIntoTableColumnNames,
429-
copyIntoCSVColumnNumbers,
430-
delimiterString,
431-
emptyFieldAsNull);
432-
433-
double seconds = (System.currentTimeMillis() - startTime) / 1000.0;
434-
435-
logger.info(
436-
String.format(
437-
"Loaded file %s (%.2f seconds for COPY)", snowflakeStageFileName, seconds));
438-
439-
} finally {
440-
con.close();
429+
int retries = 0;
430+
while (true) {
431+
try {
432+
logger.info("Running COPY from file {}", snowflakeStageFileName);
433+
434+
long startTime = System.currentTimeMillis();
435+
con.runCopy(
436+
tableIdentifier,
437+
stageIdentifier,
438+
snowflakeStageFileName,
439+
copyIntoTableColumnNames,
440+
copyIntoCSVColumnNumbers,
441+
delimiterString,
442+
emptyFieldAsNull);
443+
444+
double seconds = (System.currentTimeMillis() - startTime) / 1000.0;
445+
446+
logger.info(
447+
String.format(
448+
"Loaded file %s (%.2f seconds for COPY)", snowflakeStageFileName, seconds));
449+
450+
break;
451+
} catch (SQLException e) {
452+
if (!isRetryableForCopyTask(e)) {
453+
throw e;
454+
}
455+
456+
retries++;
457+
if (retries > this.maxCopyRetries) {
458+
throw e;
459+
}
460+
logger.warn(
461+
String.format(
462+
"Copy error %s file %s retries: %d", e, snowflakeStageFileName, retries));
463+
Thread.sleep(retries * retries * 1000);
464+
} finally {
465+
con.close();
466+
}
441467
}
442468
} finally {
443469
if (deleteStageFile) {
@@ -447,5 +473,10 @@ public Void call() throws SQLException, InterruptedException, ExecutionException
447473

448474
return null;
449475
}
476+
477+
private boolean isRetryableForCopyTask(SQLException e) {
478+
String message = e.getMessage();
479+
return message != null && message.contains("JDBC driver encountered communication error");
480+
}
450481
}
451482
}

0 commit comments

Comments
 (0)