Skip to main content

AppendStreamBuilder

Struct AppendStreamBuilder 

Source
pub struct AppendStreamBuilder { /* private fields */ }
Expand description

Configures a bounded, concurrent table append stream.

Retries temporary rejections by default. Replaying unknown outcomes requires Self::retry_unknown because an unacknowledged attempt may have committed.

§Telemetry

For a log or telemetry sink that should keep running after batch failures, use AppendFailurePolicy::Continue and keep the circuit breaker enabled. Start with one concurrent batch per stream when producers run on multiple application replicas; tune the total concurrency across all producers.

use scopedb_client::AppendFailurePolicy;

let stream = client.table("events").append_stream()
    .failure_policy(AppendFailurePolicy::Continue)
    .max_concurrent_batches(1)
    .build()?;

if let Err(error) = stream.try_send(&serde_json::json!({"name": "request.completed"})) {
    eprintln!("telemetry row not admitted: {error}");
}
let report = stream.shutdown().await?;
println!("Write results: {report:?}");

Continue mode is best effort. Monitor reports and AppendStream::stats for failed, unknown, and locally dropped rows. Send failure diagnostics to a separate sink. AppendStream::try_send never waits for capacity; AppendStream::send provides backpressure instead.

Implementations§

Source§

impl AppendStreamBuilder

Source

pub fn failure_policy(self, failure_policy: AppendFailurePolicy) -> Self

Chooses whether to stop or continue after a batch fails. Defaults to Stop.

Examples found in repository?
examples/telemetry.rs (line 51)
43async fn main() -> Result<(), Box<dyn std::error::Error>> {
44    let client = common::client()?;
45    let table = common::write_table(&client)?;
46
47    // Continue mode is explicitly best effort: failed or ambiguous batches are
48    // accounted for, released, and do not stop later telemetry batches.
49    let telemetry = table
50        .append_stream()
51        .failure_policy(AppendFailurePolicy::Continue)
52        .circuit_breaker(Some(AppendCircuitBreakerOptions::default()))
53        .target_batch_bytes(1024 * 1024)
54        .max_batch_rows(10_000)
55        .flush_interval(Duration::from_secs(1))
56        .max_buffered_bytes(32 * 1024 * 1024)
57        // This limit applies to each stream; include every application replica.
58        .max_concurrent_batches(1)
59        .attempt_timeout(Duration::from_secs(10))
60        .on_batch_failure(|event| {
61            // A synchronous observer; use a separate diagnostics sink.
62            eprintln!("telemetry append failure: {event:?}");
63        })
64        .build()?;
65
66    let mut dropped_locally = 0_u64;
67    for request in 1..=100 {
68        if !track(&telemetry, request) {
69            dropped_locally += 1;
70        }
71    }
72    println!("locally dropped: {dropped_locally}");
73
74    // In a service, stop and join producer tasks before this settlement barrier.
75    let report = telemetry.shutdown().await?;
76    if report.outcome != AppendDeliveryOutcome::Ok {
77        eprintln!("telemetry loss or ambiguity: {report:?}");
78    }
79    println!("final stream stats: {:?}", telemetry.stats());
80
81    Ok(())
82}
Source

pub fn target_batch_bytes(self, target_batch_bytes: usize) -> Self

Sets the target NDJSON payload size (default 4 MiB, maximum 8 MiB). One row may exceed this target up to 8 MiB.

Examples found in repository?
examples/telemetry.rs (line 53)
43async fn main() -> Result<(), Box<dyn std::error::Error>> {
44    let client = common::client()?;
45    let table = common::write_table(&client)?;
46
47    // Continue mode is explicitly best effort: failed or ambiguous batches are
48    // accounted for, released, and do not stop later telemetry batches.
49    let telemetry = table
50        .append_stream()
51        .failure_policy(AppendFailurePolicy::Continue)
52        .circuit_breaker(Some(AppendCircuitBreakerOptions::default()))
53        .target_batch_bytes(1024 * 1024)
54        .max_batch_rows(10_000)
55        .flush_interval(Duration::from_secs(1))
56        .max_buffered_bytes(32 * 1024 * 1024)
57        // This limit applies to each stream; include every application replica.
58        .max_concurrent_batches(1)
59        .attempt_timeout(Duration::from_secs(10))
60        .on_batch_failure(|event| {
61            // A synchronous observer; use a separate diagnostics sink.
62            eprintln!("telemetry append failure: {event:?}");
63        })
64        .build()?;
65
66    let mut dropped_locally = 0_u64;
67    for request in 1..=100 {
68        if !track(&telemetry, request) {
69            dropped_locally += 1;
70        }
71    }
72    println!("locally dropped: {dropped_locally}");
73
74    // In a service, stop and join producer tasks before this settlement barrier.
75    let report = telemetry.shutdown().await?;
76    if report.outcome != AppendDeliveryOutcome::Ok {
77        eprintln!("telemetry loss or ambiguity: {report:?}");
78    }
79    println!("final stream stats: {:?}", telemetry.stats());
80
81    Ok(())
82}
More examples
Hide additional examples
examples/bulk_append.rs (line 31)
22async fn main() -> Result<(), Box<dyn std::error::Error>> {
23    let client = common::client()?;
24    let table = common::write_table(&client)?;
25
26    // The default Stop policy gives strict barriers. Local memory and request
27    // concurrency are bounded independently; concurrent batches have no
28    // defined commit order.
29    let stream = table
30        .append_stream()
31        .target_batch_bytes(32 * 1024)
32        .max_batch_rows(10_000)
33        .flush_interval(Duration::from_secs(1))
34        .max_buffered_bytes(1024 * 1024)
35        .max_concurrent_batches(4)
36        .attempt_timeout(Duration::from_secs(30))
37        .build()?;
38
39    let rows = (1..=10_000).map(|id| {
40        serde_json::json!({
41            "id": id,
42            "occurred_at": jiff::Timestamp::now(),
43            "name": format!("example-{id}"),
44        })
45    });
46
47    let admission_error = match stream.send_all(rows).await {
48        Ok(result) => {
49            println!("accepted locally: {} rows", result.accepted_rows);
50            None
51        }
52        Err(error) => {
53            // There is no stream-wide abort or rollback. Settle the already
54            // accepted prefix even if row production or admission stopped.
55            eprintln!("row admission stopped; settling the accepted prefix: {error}");
56            Some(error)
57        }
58    };
59
60    match stream.shutdown().await {
61        Ok(report) => {
62            println!("committed remotely: {} rows", report.committed_rows);
63            println!("final stream stats: {:?}", stream.stats());
64        }
65        Err(error) => {
66            if matches!(
67                error.append_details().map(|details| details.status),
68                Some(AppendState::Unknown)
69            ) {
70                eprintln!("commit outcome is unknown; reconcile before replaying");
71            } else {
72                eprintln!("bulk append failed: {error}");
73            }
74            eprintln!("final stream stats: {:?}", stream.stats());
75            return Err(error.into());
76        }
77    }
78
79    if let Some(error) = admission_error {
80        return Err(error.into());
81    }
82    Ok(())
83}
Source

pub fn max_batch_rows(self, max_batch_rows: usize) -> Self

Sets the maximum rows in one HTTP batch, up to the 200,000-row protocol limit.

Examples found in repository?
examples/telemetry.rs (line 54)
43async fn main() -> Result<(), Box<dyn std::error::Error>> {
44    let client = common::client()?;
45    let table = common::write_table(&client)?;
46
47    // Continue mode is explicitly best effort: failed or ambiguous batches are
48    // accounted for, released, and do not stop later telemetry batches.
49    let telemetry = table
50        .append_stream()
51        .failure_policy(AppendFailurePolicy::Continue)
52        .circuit_breaker(Some(AppendCircuitBreakerOptions::default()))
53        .target_batch_bytes(1024 * 1024)
54        .max_batch_rows(10_000)
55        .flush_interval(Duration::from_secs(1))
56        .max_buffered_bytes(32 * 1024 * 1024)
57        // This limit applies to each stream; include every application replica.
58        .max_concurrent_batches(1)
59        .attempt_timeout(Duration::from_secs(10))
60        .on_batch_failure(|event| {
61            // A synchronous observer; use a separate diagnostics sink.
62            eprintln!("telemetry append failure: {event:?}");
63        })
64        .build()?;
65
66    let mut dropped_locally = 0_u64;
67    for request in 1..=100 {
68        if !track(&telemetry, request) {
69            dropped_locally += 1;
70        }
71    }
72    println!("locally dropped: {dropped_locally}");
73
74    // In a service, stop and join producer tasks before this settlement barrier.
75    let report = telemetry.shutdown().await?;
76    if report.outcome != AppendDeliveryOutcome::Ok {
77        eprintln!("telemetry loss or ambiguity: {report:?}");
78    }
79    println!("final stream stats: {:?}", telemetry.stats());
80
81    Ok(())
82}
More examples
Hide additional examples
examples/bulk_append.rs (line 32)
22async fn main() -> Result<(), Box<dyn std::error::Error>> {
23    let client = common::client()?;
24    let table = common::write_table(&client)?;
25
26    // The default Stop policy gives strict barriers. Local memory and request
27    // concurrency are bounded independently; concurrent batches have no
28    // defined commit order.
29    let stream = table
30        .append_stream()
31        .target_batch_bytes(32 * 1024)
32        .max_batch_rows(10_000)
33        .flush_interval(Duration::from_secs(1))
34        .max_buffered_bytes(1024 * 1024)
35        .max_concurrent_batches(4)
36        .attempt_timeout(Duration::from_secs(30))
37        .build()?;
38
39    let rows = (1..=10_000).map(|id| {
40        serde_json::json!({
41            "id": id,
42            "occurred_at": jiff::Timestamp::now(),
43            "name": format!("example-{id}"),
44        })
45    });
46
47    let admission_error = match stream.send_all(rows).await {
48        Ok(result) => {
49            println!("accepted locally: {} rows", result.accepted_rows);
50            None
51        }
52        Err(error) => {
53            // There is no stream-wide abort or rollback. Settle the already
54            // accepted prefix even if row production or admission stopped.
55            eprintln!("row admission stopped; settling the accepted prefix: {error}");
56            Some(error)
57        }
58    };
59
60    match stream.shutdown().await {
61        Ok(report) => {
62            println!("committed remotely: {} rows", report.committed_rows);
63            println!("final stream stats: {:?}", stream.stats());
64        }
65        Err(error) => {
66            if matches!(
67                error.append_details().map(|details| details.status),
68                Some(AppendState::Unknown)
69            ) {
70                eprintln!("commit outcome is unknown; reconcile before replaying");
71            } else {
72                eprintln!("bulk append failed: {error}");
73            }
74            eprintln!("final stream stats: {:?}", stream.stats());
75            return Err(error.into());
76        }
77    }
78
79    if let Some(error) = admission_error {
80        return Err(error.into());
81    }
82    Ok(())
83}
Source

pub fn flush_interval(self, flush_interval: Duration) -> Self

Sets the maximum delay between buffering the first row and dispatching its batch.

Examples found in repository?
examples/telemetry.rs (line 55)
43async fn main() -> Result<(), Box<dyn std::error::Error>> {
44    let client = common::client()?;
45    let table = common::write_table(&client)?;
46
47    // Continue mode is explicitly best effort: failed or ambiguous batches are
48    // accounted for, released, and do not stop later telemetry batches.
49    let telemetry = table
50        .append_stream()
51        .failure_policy(AppendFailurePolicy::Continue)
52        .circuit_breaker(Some(AppendCircuitBreakerOptions::default()))
53        .target_batch_bytes(1024 * 1024)
54        .max_batch_rows(10_000)
55        .flush_interval(Duration::from_secs(1))
56        .max_buffered_bytes(32 * 1024 * 1024)
57        // This limit applies to each stream; include every application replica.
58        .max_concurrent_batches(1)
59        .attempt_timeout(Duration::from_secs(10))
60        .on_batch_failure(|event| {
61            // A synchronous observer; use a separate diagnostics sink.
62            eprintln!("telemetry append failure: {event:?}");
63        })
64        .build()?;
65
66    let mut dropped_locally = 0_u64;
67    for request in 1..=100 {
68        if !track(&telemetry, request) {
69            dropped_locally += 1;
70        }
71    }
72    println!("locally dropped: {dropped_locally}");
73
74    // In a service, stop and join producer tasks before this settlement barrier.
75    let report = telemetry.shutdown().await?;
76    if report.outcome != AppendDeliveryOutcome::Ok {
77        eprintln!("telemetry loss or ambiguity: {report:?}");
78    }
79    println!("final stream stats: {:?}", telemetry.stats());
80
81    Ok(())
82}
More examples
Hide additional examples
examples/bulk_append.rs (line 33)
22async fn main() -> Result<(), Box<dyn std::error::Error>> {
23    let client = common::client()?;
24    let table = common::write_table(&client)?;
25
26    // The default Stop policy gives strict barriers. Local memory and request
27    // concurrency are bounded independently; concurrent batches have no
28    // defined commit order.
29    let stream = table
30        .append_stream()
31        .target_batch_bytes(32 * 1024)
32        .max_batch_rows(10_000)
33        .flush_interval(Duration::from_secs(1))
34        .max_buffered_bytes(1024 * 1024)
35        .max_concurrent_batches(4)
36        .attempt_timeout(Duration::from_secs(30))
37        .build()?;
38
39    let rows = (1..=10_000).map(|id| {
40        serde_json::json!({
41            "id": id,
42            "occurred_at": jiff::Timestamp::now(),
43            "name": format!("example-{id}"),
44        })
45    });
46
47    let admission_error = match stream.send_all(rows).await {
48        Ok(result) => {
49            println!("accepted locally: {} rows", result.accepted_rows);
50            None
51        }
52        Err(error) => {
53            // There is no stream-wide abort or rollback. Settle the already
54            // accepted prefix even if row production or admission stopped.
55            eprintln!("row admission stopped; settling the accepted prefix: {error}");
56            Some(error)
57        }
58    };
59
60    match stream.shutdown().await {
61        Ok(report) => {
62            println!("committed remotely: {} rows", report.committed_rows);
63            println!("final stream stats: {:?}", stream.stats());
64        }
65        Err(error) => {
66            if matches!(
67                error.append_details().map(|details| details.status),
68                Some(AppendState::Unknown)
69            ) {
70                eprintln!("commit outcome is unknown; reconcile before replaying");
71            } else {
72                eprintln!("bulk append failed: {error}");
73            }
74            eprintln!("final stream stats: {:?}", stream.stats());
75            return Err(error.into());
76        }
77    }
78
79    if let Some(error) = admission_error {
80        return Err(error.into());
81    }
82    Ok(())
83}
Source

pub fn channel_capacity(self, channel_capacity: usize) -> Self

Source

pub fn max_buffered_bytes(self, max_buffered_bytes: usize) -> Self

Sets the maximum bytes admitted locally but not yet settled remotely.

Examples found in repository?
examples/telemetry.rs (line 56)
43async fn main() -> Result<(), Box<dyn std::error::Error>> {
44    let client = common::client()?;
45    let table = common::write_table(&client)?;
46
47    // Continue mode is explicitly best effort: failed or ambiguous batches are
48    // accounted for, released, and do not stop later telemetry batches.
49    let telemetry = table
50        .append_stream()
51        .failure_policy(AppendFailurePolicy::Continue)
52        .circuit_breaker(Some(AppendCircuitBreakerOptions::default()))
53        .target_batch_bytes(1024 * 1024)
54        .max_batch_rows(10_000)
55        .flush_interval(Duration::from_secs(1))
56        .max_buffered_bytes(32 * 1024 * 1024)
57        // This limit applies to each stream; include every application replica.
58        .max_concurrent_batches(1)
59        .attempt_timeout(Duration::from_secs(10))
60        .on_batch_failure(|event| {
61            // A synchronous observer; use a separate diagnostics sink.
62            eprintln!("telemetry append failure: {event:?}");
63        })
64        .build()?;
65
66    let mut dropped_locally = 0_u64;
67    for request in 1..=100 {
68        if !track(&telemetry, request) {
69            dropped_locally += 1;
70        }
71    }
72    println!("locally dropped: {dropped_locally}");
73
74    // In a service, stop and join producer tasks before this settlement barrier.
75    let report = telemetry.shutdown().await?;
76    if report.outcome != AppendDeliveryOutcome::Ok {
77        eprintln!("telemetry loss or ambiguity: {report:?}");
78    }
79    println!("final stream stats: {:?}", telemetry.stats());
80
81    Ok(())
82}
More examples
Hide additional examples
examples/bulk_append.rs (line 34)
22async fn main() -> Result<(), Box<dyn std::error::Error>> {
23    let client = common::client()?;
24    let table = common::write_table(&client)?;
25
26    // The default Stop policy gives strict barriers. Local memory and request
27    // concurrency are bounded independently; concurrent batches have no
28    // defined commit order.
29    let stream = table
30        .append_stream()
31        .target_batch_bytes(32 * 1024)
32        .max_batch_rows(10_000)
33        .flush_interval(Duration::from_secs(1))
34        .max_buffered_bytes(1024 * 1024)
35        .max_concurrent_batches(4)
36        .attempt_timeout(Duration::from_secs(30))
37        .build()?;
38
39    let rows = (1..=10_000).map(|id| {
40        serde_json::json!({
41            "id": id,
42            "occurred_at": jiff::Timestamp::now(),
43            "name": format!("example-{id}"),
44        })
45    });
46
47    let admission_error = match stream.send_all(rows).await {
48        Ok(result) => {
49            println!("accepted locally: {} rows", result.accepted_rows);
50            None
51        }
52        Err(error) => {
53            // There is no stream-wide abort or rollback. Settle the already
54            // accepted prefix even if row production or admission stopped.
55            eprintln!("row admission stopped; settling the accepted prefix: {error}");
56            Some(error)
57        }
58    };
59
60    match stream.shutdown().await {
61        Ok(report) => {
62            println!("committed remotely: {} rows", report.committed_rows);
63            println!("final stream stats: {:?}", stream.stats());
64        }
65        Err(error) => {
66            if matches!(
67                error.append_details().map(|details| details.status),
68                Some(AppendState::Unknown)
69            ) {
70                eprintln!("commit outcome is unknown; reconcile before replaying");
71            } else {
72                eprintln!("bulk append failed: {error}");
73            }
74            eprintln!("final stream stats: {:?}", stream.stats());
75            return Err(error.into());
76        }
77    }
78
79    if let Some(error) = admission_error {
80        return Err(error.into());
81    }
82    Ok(())
83}
Source

pub fn max_concurrent_batches(self, max_concurrent_batches: usize) -> Self

Sets the maximum number of append batches sent concurrently per stream (default 4).

Examples found in repository?
examples/telemetry.rs (line 58)
43async fn main() -> Result<(), Box<dyn std::error::Error>> {
44    let client = common::client()?;
45    let table = common::write_table(&client)?;
46
47    // Continue mode is explicitly best effort: failed or ambiguous batches are
48    // accounted for, released, and do not stop later telemetry batches.
49    let telemetry = table
50        .append_stream()
51        .failure_policy(AppendFailurePolicy::Continue)
52        .circuit_breaker(Some(AppendCircuitBreakerOptions::default()))
53        .target_batch_bytes(1024 * 1024)
54        .max_batch_rows(10_000)
55        .flush_interval(Duration::from_secs(1))
56        .max_buffered_bytes(32 * 1024 * 1024)
57        // This limit applies to each stream; include every application replica.
58        .max_concurrent_batches(1)
59        .attempt_timeout(Duration::from_secs(10))
60        .on_batch_failure(|event| {
61            // A synchronous observer; use a separate diagnostics sink.
62            eprintln!("telemetry append failure: {event:?}");
63        })
64        .build()?;
65
66    let mut dropped_locally = 0_u64;
67    for request in 1..=100 {
68        if !track(&telemetry, request) {
69            dropped_locally += 1;
70        }
71    }
72    println!("locally dropped: {dropped_locally}");
73
74    // In a service, stop and join producer tasks before this settlement barrier.
75    let report = telemetry.shutdown().await?;
76    if report.outcome != AppendDeliveryOutcome::Ok {
77        eprintln!("telemetry loss or ambiguity: {report:?}");
78    }
79    println!("final stream stats: {:?}", telemetry.stats());
80
81    Ok(())
82}
More examples
Hide additional examples
examples/bulk_append.rs (line 35)
22async fn main() -> Result<(), Box<dyn std::error::Error>> {
23    let client = common::client()?;
24    let table = common::write_table(&client)?;
25
26    // The default Stop policy gives strict barriers. Local memory and request
27    // concurrency are bounded independently; concurrent batches have no
28    // defined commit order.
29    let stream = table
30        .append_stream()
31        .target_batch_bytes(32 * 1024)
32        .max_batch_rows(10_000)
33        .flush_interval(Duration::from_secs(1))
34        .max_buffered_bytes(1024 * 1024)
35        .max_concurrent_batches(4)
36        .attempt_timeout(Duration::from_secs(30))
37        .build()?;
38
39    let rows = (1..=10_000).map(|id| {
40        serde_json::json!({
41            "id": id,
42            "occurred_at": jiff::Timestamp::now(),
43            "name": format!("example-{id}"),
44        })
45    });
46
47    let admission_error = match stream.send_all(rows).await {
48        Ok(result) => {
49            println!("accepted locally: {} rows", result.accepted_rows);
50            None
51        }
52        Err(error) => {
53            // There is no stream-wide abort or rollback. Settle the already
54            // accepted prefix even if row production or admission stopped.
55            eprintln!("row admission stopped; settling the accepted prefix: {error}");
56            Some(error)
57        }
58    };
59
60    match stream.shutdown().await {
61        Ok(report) => {
62            println!("committed remotely: {} rows", report.committed_rows);
63            println!("final stream stats: {:?}", stream.stats());
64        }
65        Err(error) => {
66            if matches!(
67                error.append_details().map(|details| details.status),
68                Some(AppendState::Unknown)
69            ) {
70                eprintln!("commit outcome is unknown; reconcile before replaying");
71            } else {
72                eprintln!("bulk append failed: {error}");
73            }
74            eprintln!("final stream stats: {:?}", stream.stats());
75            return Err(error.into());
76        }
77    }
78
79    if let Some(error) = admission_error {
80        return Err(error.into());
81    }
82    Ok(())
83}
Source

pub fn max_retries(self, max_retries: usize) -> Self

Source

pub fn retry_unknown(self, enabled: bool) -> Self

Opts into replaying transient unknown outcomes. This can duplicate rows. Client timeouts are never replayed automatically, even when enabled.

Source

pub fn initial_backoff(self, initial_backoff: Duration) -> Self

Source

pub fn max_backoff(self, max_backoff: Duration) -> Self

Source

pub fn attempt_timeout(self, attempt_timeout: Duration) -> Self

Sets a timeout for each HTTP attempt, overriding the client’s write timeout. A timeout has an unknown commit outcome. By default the client uses 30 seconds. The duration must be finite and nonzero; this deadline cannot be disabled.

Examples found in repository?
examples/telemetry.rs (line 59)
43async fn main() -> Result<(), Box<dyn std::error::Error>> {
44    let client = common::client()?;
45    let table = common::write_table(&client)?;
46
47    // Continue mode is explicitly best effort: failed or ambiguous batches are
48    // accounted for, released, and do not stop later telemetry batches.
49    let telemetry = table
50        .append_stream()
51        .failure_policy(AppendFailurePolicy::Continue)
52        .circuit_breaker(Some(AppendCircuitBreakerOptions::default()))
53        .target_batch_bytes(1024 * 1024)
54        .max_batch_rows(10_000)
55        .flush_interval(Duration::from_secs(1))
56        .max_buffered_bytes(32 * 1024 * 1024)
57        // This limit applies to each stream; include every application replica.
58        .max_concurrent_batches(1)
59        .attempt_timeout(Duration::from_secs(10))
60        .on_batch_failure(|event| {
61            // A synchronous observer; use a separate diagnostics sink.
62            eprintln!("telemetry append failure: {event:?}");
63        })
64        .build()?;
65
66    let mut dropped_locally = 0_u64;
67    for request in 1..=100 {
68        if !track(&telemetry, request) {
69            dropped_locally += 1;
70        }
71    }
72    println!("locally dropped: {dropped_locally}");
73
74    // In a service, stop and join producer tasks before this settlement barrier.
75    let report = telemetry.shutdown().await?;
76    if report.outcome != AppendDeliveryOutcome::Ok {
77        eprintln!("telemetry loss or ambiguity: {report:?}");
78    }
79    println!("final stream stats: {:?}", telemetry.stats());
80
81    Ok(())
82}
More examples
Hide additional examples
examples/bulk_append.rs (line 36)
22async fn main() -> Result<(), Box<dyn std::error::Error>> {
23    let client = common::client()?;
24    let table = common::write_table(&client)?;
25
26    // The default Stop policy gives strict barriers. Local memory and request
27    // concurrency are bounded independently; concurrent batches have no
28    // defined commit order.
29    let stream = table
30        .append_stream()
31        .target_batch_bytes(32 * 1024)
32        .max_batch_rows(10_000)
33        .flush_interval(Duration::from_secs(1))
34        .max_buffered_bytes(1024 * 1024)
35        .max_concurrent_batches(4)
36        .attempt_timeout(Duration::from_secs(30))
37        .build()?;
38
39    let rows = (1..=10_000).map(|id| {
40        serde_json::json!({
41            "id": id,
42            "occurred_at": jiff::Timestamp::now(),
43            "name": format!("example-{id}"),
44        })
45    });
46
47    let admission_error = match stream.send_all(rows).await {
48        Ok(result) => {
49            println!("accepted locally: {} rows", result.accepted_rows);
50            None
51        }
52        Err(error) => {
53            // There is no stream-wide abort or rollback. Settle the already
54            // accepted prefix even if row production or admission stopped.
55            eprintln!("row admission stopped; settling the accepted prefix: {error}");
56            Some(error)
57        }
58    };
59
60    match stream.shutdown().await {
61        Ok(report) => {
62            println!("committed remotely: {} rows", report.committed_rows);
63            println!("final stream stats: {:?}", stream.stats());
64        }
65        Err(error) => {
66            if matches!(
67                error.append_details().map(|details| details.status),
68                Some(AppendState::Unknown)
69            ) {
70                eprintln!("commit outcome is unknown; reconcile before replaying");
71            } else {
72                eprintln!("bulk append failed: {error}");
73            }
74            eprintln!("final stream stats: {:?}", stream.stats());
75            return Err(error.into());
76        }
77    }
78
79    if let Some(error) = admission_error {
80        return Err(error.into());
81    }
82    Ok(())
83}
Source

pub fn circuit_breaker( self, circuit_breaker: Option<AppendCircuitBreakerOptions>, ) -> Self

Configures the continue-mode availability circuit, or disables it with None. Enabled by default: five consecutive availability failures pause dispatch for 30 seconds before probing recovery.

Examples found in repository?
examples/telemetry.rs (line 52)
43async fn main() -> Result<(), Box<dyn std::error::Error>> {
44    let client = common::client()?;
45    let table = common::write_table(&client)?;
46
47    // Continue mode is explicitly best effort: failed or ambiguous batches are
48    // accounted for, released, and do not stop later telemetry batches.
49    let telemetry = table
50        .append_stream()
51        .failure_policy(AppendFailurePolicy::Continue)
52        .circuit_breaker(Some(AppendCircuitBreakerOptions::default()))
53        .target_batch_bytes(1024 * 1024)
54        .max_batch_rows(10_000)
55        .flush_interval(Duration::from_secs(1))
56        .max_buffered_bytes(32 * 1024 * 1024)
57        // This limit applies to each stream; include every application replica.
58        .max_concurrent_batches(1)
59        .attempt_timeout(Duration::from_secs(10))
60        .on_batch_failure(|event| {
61            // A synchronous observer; use a separate diagnostics sink.
62            eprintln!("telemetry append failure: {event:?}");
63        })
64        .build()?;
65
66    let mut dropped_locally = 0_u64;
67    for request in 1..=100 {
68        if !track(&telemetry, request) {
69            dropped_locally += 1;
70        }
71    }
72    println!("locally dropped: {dropped_locally}");
73
74    // In a service, stop and join producer tasks before this settlement barrier.
75    let report = telemetry.shutdown().await?;
76    if report.outcome != AppendDeliveryOutcome::Ok {
77        eprintln!("telemetry loss or ambiguity: {report:?}");
78    }
79    println!("final stream stats: {:?}", telemetry.stats());
80
81    Ok(())
82}
Source

pub fn on_batch_failure<F>(self, listener: F) -> Self
where F: Fn(&AppendBatchFailureEvent) + Send + Sync + 'static,

Observes remote batch failures. Listener panics are isolated from the worker.

Examples found in repository?
examples/telemetry.rs (lines 60-63)
43async fn main() -> Result<(), Box<dyn std::error::Error>> {
44    let client = common::client()?;
45    let table = common::write_table(&client)?;
46
47    // Continue mode is explicitly best effort: failed or ambiguous batches are
48    // accounted for, released, and do not stop later telemetry batches.
49    let telemetry = table
50        .append_stream()
51        .failure_policy(AppendFailurePolicy::Continue)
52        .circuit_breaker(Some(AppendCircuitBreakerOptions::default()))
53        .target_batch_bytes(1024 * 1024)
54        .max_batch_rows(10_000)
55        .flush_interval(Duration::from_secs(1))
56        .max_buffered_bytes(32 * 1024 * 1024)
57        // This limit applies to each stream; include every application replica.
58        .max_concurrent_batches(1)
59        .attempt_timeout(Duration::from_secs(10))
60        .on_batch_failure(|event| {
61            // A synchronous observer; use a separate diagnostics sink.
62            eprintln!("telemetry append failure: {event:?}");
63        })
64        .build()?;
65
66    let mut dropped_locally = 0_u64;
67    for request in 1..=100 {
68        if !track(&telemetry, request) {
69            dropped_locally += 1;
70        }
71    }
72    println!("locally dropped: {dropped_locally}");
73
74    // In a service, stop and join producer tasks before this settlement barrier.
75    let report = telemetry.shutdown().await?;
76    if report.outcome != AppendDeliveryOutcome::Ok {
77        eprintln!("telemetry loss or ambiguity: {report:?}");
78    }
79    println!("final stream stats: {:?}", telemetry.stats());
80
81    Ok(())
82}
Source

pub fn build(self) -> Result<AppendStream, Error>

Examples found in repository?
examples/append_stream.rs (line 21)
18async fn main() -> Result<(), Box<dyn std::error::Error>> {
19    let client = common::client()?;
20    let table = common::write_table(&client)?;
21    let stream = table.append_stream().build()?;
22
23    let admitted = stream
24        .send_all([
25            serde_json::json!({
26                "id": 1,
27                "occurred_at": jiff::Timestamp::now(),
28                "name": "first",
29            }),
30            serde_json::json!({
31                "id": 2,
32                "occurred_at": jiff::Timestamp::now(),
33                "name": "second",
34            }),
35        ])
36        .await?;
37    println!("accepted locally: {} rows", admitted.accepted_rows);
38
39    // send_all() confirms only local admission. With the default strict policy,
40    // successful shutdown confirms at least one commit acknowledgement per row.
41    // Retrying an unknown outcome can insert duplicates.
42    let report = stream.shutdown().await?;
43    println!("committed remotely: {} rows", report.committed_rows);
44
45    Ok(())
46}
More examples
Hide additional examples
examples/import_ndjson.rs (line 29)
23async fn main() -> Result<(), Box<dyn std::error::Error>> {
24    let path = std::env::args()
25        .nth(1)
26        .ok_or("usage: import_ndjson <events.ndjson>")?;
27    let mut input = BufReader::new(File::open(path)?);
28    let client = common::client()?;
29    let stream = common::write_table(&client)?.append_stream().build()?;
30    let admitted: Result<(), Box<dyn std::error::Error>> = async {
31        let mut line_number = 0;
32        loop {
33            // Bound a single input record as well as the stream's queued bytes.
34            let mut line = String::new();
35            let bytes = input
36                .by_ref()
37                .take(8 * 1024 * 1024 + 1)
38                .read_line(&mut line)?;
39            if bytes == 0 {
40                break;
41            }
42            line_number += 1;
43            if bytes > 8 * 1024 * 1024 {
44                return Err(format!("line {line_number} exceeds 8 MiB").into());
45            }
46            if line.trim().is_empty() {
47                continue;
48            }
49            let row: serde_json::Value = serde_json::from_str(&line)
50                .map_err(|error| format!("line {line_number}: {error}"))?;
51            stream.send(&row).await?;
52        }
53        Ok(())
54    }
55    .await;
56    // Settle the accepted prefix even if parsing or admission failed midway.
57    let settled = stream.shutdown().await;
58    println!("delivery: {:?}", stream.stats());
59    settled?;
60    admitted?;
61    Ok(())
62}
examples/telemetry.rs (line 64)
43async fn main() -> Result<(), Box<dyn std::error::Error>> {
44    let client = common::client()?;
45    let table = common::write_table(&client)?;
46
47    // Continue mode is explicitly best effort: failed or ambiguous batches are
48    // accounted for, released, and do not stop later telemetry batches.
49    let telemetry = table
50        .append_stream()
51        .failure_policy(AppendFailurePolicy::Continue)
52        .circuit_breaker(Some(AppendCircuitBreakerOptions::default()))
53        .target_batch_bytes(1024 * 1024)
54        .max_batch_rows(10_000)
55        .flush_interval(Duration::from_secs(1))
56        .max_buffered_bytes(32 * 1024 * 1024)
57        // This limit applies to each stream; include every application replica.
58        .max_concurrent_batches(1)
59        .attempt_timeout(Duration::from_secs(10))
60        .on_batch_failure(|event| {
61            // A synchronous observer; use a separate diagnostics sink.
62            eprintln!("telemetry append failure: {event:?}");
63        })
64        .build()?;
65
66    let mut dropped_locally = 0_u64;
67    for request in 1..=100 {
68        if !track(&telemetry, request) {
69            dropped_locally += 1;
70        }
71    }
72    println!("locally dropped: {dropped_locally}");
73
74    // In a service, stop and join producer tasks before this settlement barrier.
75    let report = telemetry.shutdown().await?;
76    if report.outcome != AppendDeliveryOutcome::Ok {
77        eprintln!("telemetry loss or ambiguity: {report:?}");
78    }
79    println!("final stream stats: {:?}", telemetry.stats());
80
81    Ok(())
82}
examples/bulk_append.rs (line 37)
22async fn main() -> Result<(), Box<dyn std::error::Error>> {
23    let client = common::client()?;
24    let table = common::write_table(&client)?;
25
26    // The default Stop policy gives strict barriers. Local memory and request
27    // concurrency are bounded independently; concurrent batches have no
28    // defined commit order.
29    let stream = table
30        .append_stream()
31        .target_batch_bytes(32 * 1024)
32        .max_batch_rows(10_000)
33        .flush_interval(Duration::from_secs(1))
34        .max_buffered_bytes(1024 * 1024)
35        .max_concurrent_batches(4)
36        .attempt_timeout(Duration::from_secs(30))
37        .build()?;
38
39    let rows = (1..=10_000).map(|id| {
40        serde_json::json!({
41            "id": id,
42            "occurred_at": jiff::Timestamp::now(),
43            "name": format!("example-{id}"),
44        })
45    });
46
47    let admission_error = match stream.send_all(rows).await {
48        Ok(result) => {
49            println!("accepted locally: {} rows", result.accepted_rows);
50            None
51        }
52        Err(error) => {
53            // There is no stream-wide abort or rollback. Settle the already
54            // accepted prefix even if row production or admission stopped.
55            eprintln!("row admission stopped; settling the accepted prefix: {error}");
56            Some(error)
57        }
58    };
59
60    match stream.shutdown().await {
61        Ok(report) => {
62            println!("committed remotely: {} rows", report.committed_rows);
63            println!("final stream stats: {:?}", stream.stats());
64        }
65        Err(error) => {
66            if matches!(
67                error.append_details().map(|details| details.status),
68                Some(AppendState::Unknown)
69            ) {
70                eprintln!("commit outcome is unknown; reconcile before replaying");
71            } else {
72                eprintln!("bulk append failed: {error}");
73            }
74            eprintln!("final stream stats: {:?}", stream.stats());
75            return Err(error.into());
76        }
77    }
78
79    if let Some(error) = admission_error {
80        return Err(error.into());
81    }
82    Ok(())
83}

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more