Skip to content

fix(sink): require sink decoupling in the file sink impl itself - #26681

Draft
yuhao-su wants to merge 1 commit into
mainfrom
yuhao/fix-is-file-sink-gcs-snowflake
Draft

fix(sink): require sink decoupling in the file sink impl itself#26681
yuhao-su wants to merge 1 commit into
mainfrom
yuhao/fix-is-file-sink-gcs-snowflake

Conversation

@yuhao-su

@yuhao-su yuhao-su commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

I hereby agree to the terms of the RisingWave Labs, Inc. Contributor License Agreement.

What's changed and what's your intention?

The rule "a file sink must run decoupled" was expressed as a connector-name whitelist in the frontend catalog — SinkDesc::is_file_sink() listing fs / azblob / s3 / webhdfs — and checked in StreamSink::create. for_all_sinks! registers six FileSink<_> backends over the same OpenDAL writer, so gcs and snowflake were missing from that list.

The rule is load-bearing. OpenDalSinkWriter commits a file only once its batching strategy is met (10s rollover / 10240 rows) and truncates the log store only after a commit. On the non-decoupled in-memory log store, an untruncated checkpoint barrier parks BoundedInMemLogStoreReader in AwaitingTruncate (next_item() returns pending() forever), so the sink never gets another chance to commit and the checkpoint never completes. Verified locally against a FileSink<_> sink created with set sink_decouple = false: FLUSH hangs indefinitely, unrelated DDL on the same cluster stops making progress, and nothing is ever written to the target; with decoupling enabled the same sink flushes in 0.1s and lands its files after the rollover.

Rather than adding the two names to the list, this moves the rule to where the property lives: is_sink_decouple on impl<S: OpendalSinkBackend> Sink for FileSink<S>. One generic impl covers all six backends and any backend added later, so the list can't drift out of sync again. The whitelist and its frontend special case are deleted. This mirrors the turbopuffer sink, which has the same requirement and already handles it this way.

The frontend already dispatches SinkType::is_sink_decouple(...) through match_sink_name_str! a few lines above the special case, so no new machinery is needed.

Behavior change

The error is now a sink config error rather than ErrorCode::NotSupported with a HINT, so the remedy moves into the message text:

ERROR:  Failed to run the query

Caused by these errors (recent errors listed first):
  1: Sink error
  2: config error
  3: File sink can only be created with sink_decouple enabled. Please run `set sink_decouple = true` first.

Verification

  • New unit test test_requires_sink_decouple in opendal_sink.rs (Default/Enable accepted, Disable rejected) over fs, gcs, s3 and snowflake.
  • e2e_test/sink/append_only_sink.slt extended from the s3 case to s3 + gcs + snowflake; passes locally. The check happens at planning time, before the connector's validate(), so no cloud credentials are involved.
  • Checked that a decoupled fs sink is unaffected: FLUSH returns immediately and a non-empty parquet file lands after the rollover.
  • ./risedev c clean.

Checklist

  • I have written necessary rustdoc comments.
  • I have added necessary unit tests and integration tests.
  • I have added test labels as necessary.
  • My PR changes performance-critical code, so I will run (micro) benchmarks and present the results.

Documentation

  • My PR needs documentation updates.

gcs and snowflake file sinks can no longer be created with sink_decouple = false (they previously appeared to succeed and then stalled the streaming job). The error message for all file sinks changes as shown above.

The check runs at plan time only, and the resulting log store type is persisted in the sink's plan, so this does not affect sinks that already exist: they keep the log store they were created with and are not re-validated on upgrade or recovery. A gcs or snowflake sink created earlier with sink_decouple = false therefore keeps its in-memory log store and its stalling behavior — it has to be dropped and recreated with decoupling enabled.

🤖 Generated with Claude Code

@yuhao-su yuhao-su added type/fix Type: Bug fix. Only for pull requests. ci/run-e2e-sink-tests labels Aug 15, 2026
@yuhao-su
yuhao-su requested review from hzxa21, wenym1 and zwang28 August 15, 2026 21:51
@yuhao-su
yuhao-su marked this pull request as draft August 16, 2026 03:04
The "file sinks must be decoupled" rule was a connector-name whitelist in
the frontend catalog (`SinkDesc::is_file_sink`) listing fs/azblob/s3/webhdfs.
It missed gcs and snowflake, although all six are `FileSink<_>` over the same
OpenDAL writer: that writer commits a file only once its batching strategy is
met and truncates the log store only then, so with `sink_decouple = false` a
half-full batch leaves the checkpoint barrier untruncated and the whole
streaming graph stalls. A gcs or snowflake sink created that way did exactly
that.

Move the rule to `is_sink_decouple` on `impl<S: OpendalSinkBackend> Sink for
FileSink<S>`, where one generic impl covers every backend and any future one,
and drop the whitelist plus its frontend special case. This is what the
turbopuffer sink, which has the same requirement, already does.

The error is now a sink config error instead of `NotSupported` with a HINT, so
the remedy moves into the message text.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@yuhao-su
yuhao-su force-pushed the yuhao/fix-is-file-sink-gcs-snowflake branch from 66f4ca5 to b47b799 Compare August 16, 2026 03:20
@yuhao-su yuhao-su changed the title fix(sink): treat gcs and snowflake sinks as file sinks fix(sink): require sink decoupling in the file sink impl itself Aug 16, 2026
@yuhao-su yuhao-su added the user-facing-changes Contains changes that are visible to users label Aug 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/run-e2e-sink-tests type/fix Type: Bug fix. Only for pull requests. user-facing-changes Contains changes that are visible to users

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant