Skip to content

[FLINK-40278][connector-http] Document and test that HTTP sink retries resend the whole batch - #57

Merged
MartijnVisser merged 1 commit into
apache:mainfrom
bharathgunapati:FLINK-40278-sink-batch-retry-semantics
Oct 26, 2026
Merged

MartijnVisser merged 1 commit into
apache:mainfrom
bharathgunapati:FLINK-40278-sink-batch-retry-semantics

Conversation

@bharathgunapati

@bharathgunapati bharathgunapati commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Purpose of the change

https://issues.apache.org/jira/browse/FLINK-40278

Define, document, and test HTTP sink retry semantics for batch submission mode. The retry behaviour itself was implemented in FLINK-40277 (#54); this PR makes it explicit and locks it in with tests. Sink runtime code is unchanged.

In batch submission mode, each HTTP request carries several records and gets one response status:

  • A retryable response retries all original entries of that HTTP request.
  • A successful or ignored response completes all entries of that HTTP request.
  • A fatal response, or a retryable response after the last retry, fails all entries of that HTTP request.

Partial per-record retry is out of scope; it would need a response processor that can parse target-specific per-record results.

Brief change log

  • Add a "Retries and handling errors (Sink)" section to the Table and DataStream docs (en + zh), linked from each table of contents. It states that retries are disabled by default (http.sink.max-retries = 0), which failures are retried, that with the legacy http.sink.error.code properties no status code is retried (only requests without a response), and that one flush split into several HTTP requests is handled per request.
  • Add JavaNetSinkHttpClientTest cases that run through the real PerRequestSubmitter and BatchRequestSubmitter:
    • per-request mode retries only the failed entry;
    • batch mode retries all entries of a retryable batch and then succeeds;
    • batch mode returns all entries as retryable once retries are exhausted;
    • batch mode fails all entries of a fatal batch without retrying;
    • batch mode completes all entries of an ignored batch;
    • a flush split by http.sink.request.batch.size retries only the records of the failed HTTP request;
    • retried records are regrouped into new HTTP requests (two failed POST requests separated by a PUT are resent as one request).
  • The tests assert that retries receive the same HttpSinkRequestEntry instances (identity, not equality), so retries reuse the original entries rather than reconstructed request bytes. The split-flush tests record the entries of every HTTP request through the post-request callback.
  • Test clients are closed in an @AfterEach so a failed assertion does not leak executors.

Verifying this change

mvn -pl flink-connector-http -DskipITs -Dskip.archunit=true -Dtest=JavaNetSinkHttpClientTest,HttpSinkClientWithRetryTest,BatchRequestSubmitterTest,HttpSinkWriterTest test (39 tests, all pass)

mvn -pl flink-connector-http spotless:check checkstyle:check

Mutation check: making BatchRequestSubmitter wrap each entry in a new HttpSinkRequestEntry fails the four batch identity tests, so the identity assertions are effective.

Impact and documentation

  • Dependencies: no
  • Public API: no
  • Runtime per-record code paths: no
  • Documentation: sink retry and batch failure semantics documented on the Table and DataStream pages (en + zh).

Was generative AI tooling used to co-author this PR?
  • Yes (please specify the tool below)

Generated-by: Cursor (auto mode) for the initial change; Claude Code (Claude Fable 5.1) for the review follow-ups

@bharathgunapati
bharathgunapati force-pushed the FLINK-40278-sink-batch-retry-semantics branch from 0d77851 to cee73a9 Compare October 8, 2026 16:07
@bharathgunapati bharathgunapati changed the title [FLINK-40278][connector-http] Define HTTP sink batch retry semantics [FLINK-40278][connector-http] Document HTTP sink batch retry semantics Oct 8, 2026
@bharathgunapati bharathgunapati changed the title [FLINK-40278][connector-http] Document HTTP sink batch retry semantics [FLINK-40278][connector-http] Document that HTTP sink retries resend the whole batch request Oct 8, 2026
@bharathgunapati
bharathgunapati force-pushed the FLINK-40278-sink-batch-retry-semantics branch 2 times, most recently from 6a97e7f to 6fe47be Compare October 8, 2026 16:16
@bharathgunapati bharathgunapati changed the title [FLINK-40278][connector-http] Document that HTTP sink retries resend the whole batch request [FLINK-40278][connector-http] Document and test that HTTP sink retries resend the whole batch Oct 8, 2026
@bharathgunapati
bharathgunapati force-pushed the FLINK-40278-sink-batch-retry-semantics branch from 6fe47be to 28595cc Compare October 8, 2026 16:24
@davidradl

Copy link
Copy Markdown
Contributor

in general - if we retry the whole batch is it possible for some of them to be accepted? So retrying would produce duplicates - can we know which event(s) needs to be retried?

response that is not successful or ignored is not retried and fails the job. A request that still fails with a retryable
error after the last retry also fails the job.

In batch submission mode, each HTTP request carries several records and gets one response status, which applies to every

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we get more granular information?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not today: the sink only reads the status code. Per-record results need a parser for the endpoint's response body, out of scope for this ticket. A partly applied batch is resent in full.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirming what Martijn said: the sink classifies a response from its status code only, so it cannot tell which records of a batch the server applied. Per-record retry needs a response-body parser for the target, which is out of scope here. The docs state the limitation and recommend an idempotent endpoint for that case.

fails all of them. The sink does not retry individual records inside a batch. Use an idempotent endpoint if retried
records must not be applied twice. When one flush is split into several HTTP requests (because of
`http.sink.request.batch.size` or different HTTP methods), each response applies only to the records in its own request.
Retried records are batched again, so they can be grouped into different HTTP requests than in the first attempt.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have you manually tested this?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked locally with http.sink.request.batch.size 2 and three records: only the failed request's records are resent. Please add a test, nothing in the PR covers this yet.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@davidradl yes, both with unit tests and end to end.

@MartijnVisser added two tests in JavaNetSinkHttpClientTest that go through the real BatchRequestSubmitter:

  • batchModeRetriesOnlyTheFailedRequestWhenFlushIsSplit: batch size 2, three records. The HTTP requests are [1,2] -> 500, [3] -> 200, then only [1,2] is resent.
  • batchModeRegroupsRetriedEntriesIntoNewHttpRequests: POST, PUT, POST. The two failed POST records come back as one request on retry.

Both record the entries of every HTTP request through the post-request callback and compare by identity.

The same scenarios also run end to end in a Flink mini-cluster against an embedded HTTP server in bharathgunapati/flink-http-sink-examples (DataStreamHttpSinkIT, commit 699e9ee). Server log:

POST /split-batch -> 500 body=[{"id":1},{"id":2}]
POST /split-batch -> 200 body=[{"id":3}]
POST /split-batch -> 200 body=[{"id":1},{"id":2}]

POST /regroup -> 500 body=[{"id":1}]
PUT  /regroup -> 200 body=[{"id":2,"method":"PUT"}]
POST /regroup -> 500 body=[{"id":3}]
POST /regroup -> 200 body=[{"id":1},{"id":3}]

@MartijnVisser MartijnVisser left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR. The new batch tests fail when BatchRequestSubmitter rebuilds the entries, so the identity checks work. Can you add the AI disclosure from the PR template (FLINK-40758)?

Retries are disabled by default: `http.sink.max-retries` defaults to `0`, so the first failed request fails the job.

When `http.sink.max-retries` is greater than `0`, the sink retries requests that fail before a response is received (for
example, connection errors) and responses whose status code is listed in `http.sink.retry-codes`. The delay between

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the legacy http.sink.error.code properties, no status code is retried, only failures without a response. I think that needs a sentence here, on all four pages.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Added a paragraph on all four pages: with the legacy http.sink.error.code / http.sink.error.code.exclude properties no status code is retried, a code in the error list fails the job, every other code is success, and only requests that fail before a response is retried. That matches HttpSinkResponseClassifier, which returns only SUCCESS or FATAL_FAILURE on the legacy path.

@bharathgunapati
bharathgunapati force-pushed the FLINK-40278-sink-batch-retry-semantics branch from 28595cc to 8d59f21 Compare October 25, 2026 17:11
bharathgunapati added a commit to bharathgunapati/flink-http-sink-examples that referenced this pull request Oct 25, 2026
Cover the batch retry semantics documented in apache/flink-connector-http#57
end to end: a flush split by http.sink.request.batch.size retries only the
records of the failed HTTP request, and retried records are regrouped into
new HTTP requests.

- MockHttpServer: fail the first request whose body contains a marker and
  answer 200 otherwise, so a failure follows a record rather than a request
  position (requests of one flush are sent concurrently). Drop dead code.
- DataStreamHttpSinkIT: two new scenarios; records tagged with
  "method":"PUT" are sent with PUT so one test can mix methods.
- DataStreamHttpSinkExample: runnable /split-batch-retry demo.
- CI and README: build the connector from FLINK-40278-sink-batch-retry-semantics.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@bharathgunapati

Copy link
Copy Markdown
Contributor Author

Thanks for the reviews. Addressed in the latest push:

  • Legacy error-code behaviour documented on all four pages.
  • Two new tests for a flush split into several HTTP requests (batch size and method change), see the thread above for the end-to-end log.
  • Test clients are now closed in an @AfterEach.
  • AI disclosure added to the description per the new template.

@davidradl on the duplicates question: yes, if the server applied part of a batch before answering 500, the retry resends the whole batch. The sink cannot know which records were applied from a status code alone, so the docs ask for an idempotent endpoint when that matters.

The CI run for the new head is waiting for workflow approval. Could one of you approve it?

@MartijnVisser MartijnVisser left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, the split-flush tests and the legacy error-code paragraph cover what I asked. Please replace the Co-Authored-By trailer with the Generated-by: line from the PR description, as the contribution guide asks.

…s resend the whole batch

Generated-by: Cursor (auto mode) for the initial change; Claude Code (Claude Fable 5.1) for the review follow-ups
@bharathgunapati
bharathgunapati force-pushed the FLINK-40278-sink-batch-retry-semantics branch from 8d59f21 to b4e74c7 Compare October 26, 2026 01:59
@bharathgunapati

Copy link
Copy Markdown
Contributor Author

Done. The commit now ends with the Generated-by line from the description instead of the Co-Authored-By trailer (head b4e74c7, no content change). I applied the same to #58, which had the identical trailer.

@MartijnVisser MartijnVisser left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM % CI passing

@MartijnVisser
MartijnVisser merged commit 70e18b8 into apache:main Oct 26, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants