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
impl AppendStream
Sourcepub async fn send<T>(&self, row: &T) -> Result<(), Error>
pub async fn send<T>(&self, row: &T) -> Result<(), Error>
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?
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}Sourcepub fn try_send<T>(&self, row: &T) -> Result<(), AppendTrySendError>
pub fn try_send<T>(&self, row: &T) -> Result<(), AppendTrySendError>
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?
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}Sourcepub async fn send_all<I>(&self, rows: I) -> Result<AppendAdmissionResult, Error>
pub async fn send_all<I>(&self, rows: I) -> Result<AppendAdmissionResult, Error>
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?
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
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}Sourcepub async fn flush(&self) -> Result<AppendDeliveryReport, Error>
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.
Sourcepub async fn shutdown(&self) -> Result<AppendDeliveryReport, Error>
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?
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
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}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}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}Sourcepub fn stats(&self) -> AppendStreamStats
pub fn stats(&self) -> AppendStreamStats
Returns a synchronous snapshot suitable for metrics and shutdown diagnostics.
Examples found in repository?
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
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}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}