Skip to main content

AppendStream

Struct AppendStream 

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

A cloneable, bounded producer handle for asynchronous table appends. Dropping every handle drains admitted rows in the background while the Tokio runtime remains alive. Use shutdown() to observe settlement before exiting.

Implementations§

Source§

impl AppendStream

Source

pub async fn send<T>(&self, row: &T) -> Result<(), Error>
where T: Serialize + ?Sized,

Serializes and admits one row, waiting for bounded local capacity.

Success means local admission only. Use Self::flush or Self::shutdown to wait for remote settlement.

Examples found in repository?
examples/import_ndjson.rs (line 51)
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}
Source

pub fn try_send<T>(&self, row: &T) -> Result<(), AppendTrySendError>
where T: Serialize + ?Sized,

Attempts synchronous local admission without waiting for capacity. Returns an error if the circuit is open or local capacity is full.

Examples found in repository?
examples/telemetry.rs (lines 26-31)
25fn track(stream: &AppendStream, request: u64) -> bool {
26    match stream.try_send(&serde_json::json!({
27        "event_id": Uuid::now_v7(),
28        "occurred_at": jiff::Timestamp::now(),
29        "name": "request.completed",
30        "attributes": {"request": request, "status": 200},
31    })) {
32        // This means local admission only, not a remote commit.
33        Ok(()) => true,
34        Err(error) => {
35            // Route this diagnostic to another sink, never back into `stream`.
36            eprintln!("telemetry row dropped locally: {error}");
37            false
38        }
39    }
40}
Source

pub async fn send_all<I>(&self, rows: I) -> Result<AppendAdmissionResult, Error>
where I: IntoIterator, I::Item: Serialize,

Admits an iterator of rows sequentially with bounded memory.

If admission fails partway, the error includes the accepted count. Call Self::shutdown to settle those rows; Self::stats retains cumulative counts.

Examples found in repository?
examples/append_stream.rs (lines 24-35)
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/bulk_append.rs (line 47)
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 async fn flush(&self) -> Result<AppendDeliveryReport, Error>

Dispatches rows admitted before this barrier and waits for their outcomes.

The report covers activity since the previous barrier. On a Stop-mode failure, partial counts remain available in Self::stats.

Committed rows have at least one successful acknowledgement; earlier unknown attempts may also have committed. A failed barrier may include successful batches, so replaying its entire input can duplicate rows.

Once enqueued, dropping this future does not cancel remote settlement. Keep the future alive to receive its interval report; the latest completed report also remains observable through Self::stats.

Source

pub async fn shutdown(&self) -> Result<AppendDeliveryReport, Error>

Permanently closes the stream after settling every admitted row.

This method is idempotent across cloned producer handles. Once polled, dropping its future stops waiting but settlement continues in the worker. Call shutdown again to observe completion. Failure reports remain in stats().

Examples found in repository?
examples/append_stream.rs (line 42)
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 57)
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 75)
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 60)
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 stats(&self) -> AppendStreamStats

Returns a synchronous snapshot suitable for metrics and shutdown diagnostics.

Examples found in repository?
examples/import_ndjson.rs (line 58)
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}
More examples
Hide additional examples
examples/telemetry.rs (line 79)
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 63)
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}

Trait Implementations§

Source§

impl Clone for AppendStream

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more

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> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. 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> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. 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