influxdb3_client/write.rs
1use std::collections::HashMap;
2
3use crate::{error::Error, point::Point, precision::Precision};
4
5/// Options controlling a single write operation.
6///
7/// Defaults live in [`crate::ClientConfig::write_options`]; individual writes
8/// override values via the [`crate::Client::write`] builder.
9#[derive(Debug, Clone)]
10pub struct WriteOptions {
11 /// Timestamp precision for this write. Defaults to `Nanosecond`.
12 pub precision: Precision,
13
14 /// Tags merged into every point before serialisation.
15 /// Point-level tags take precedence on collision.
16 pub default_tags: HashMap<String, String>,
17
18 /// If `Some(n)`, compress the body with gzip when it exceeds `n` bytes.
19 /// `Some(0)` always compresses; `None` never compresses. Defaults to
20 /// `Some(1024)`.
21 ///
22 /// Compression trades CPU for bandwidth. The default suits remote/cloud
23 /// targets where bandwidth dominates. For high-throughput ingest over a
24 /// fast LAN (flight-test, IIoT), gzip CPU can become the bottleneck. Set
25 /// `gzip_threshold(None)` to disable it, or raise the threshold so only
26 /// large batches are compressed.
27 pub gzip_threshold: Option<usize>,
28
29 /// When `true`, skip WAL synchronisation (faster, lower durability).
30 pub no_sync: bool,
31
32 /// When `true`, a batch is accepted even if some lines are invalid.
33 pub accept_partial: bool,
34
35 /// When `true`, use the V2 (`/api/v2/write`) endpoint instead of V3.
36 pub use_v2_api: bool,
37
38 /// Optional tag ordering for deterministic line-protocol output.
39 pub tag_order: Vec<String>,
40
41 /// Maximum number of points per HTTP request when calling `write`.
42 /// Larger inputs are streamed as multiple sequential or pipelined requests.
43 /// Defaults to `5_000`.
44 ///
45 /// This is a point count, not a byte size. The effective ceiling is the
46 /// server's maximum request size (configurable on InfluxDB, 10 MB by
47 /// default); if you raise `batch_size` into a `413`, raise that limit too.
48 pub batch_size: usize,
49
50 /// Maximum number of concurrent in-flight HTTP requests when writing
51 /// multiple batches. Defaults to `4`. Set to `1` for strict ordering.
52 pub max_inflight: usize,
53}
54
55/// Default maximum number of points per write request.
56pub const DEFAULT_BATCH_SIZE: usize = 5_000;
57
58/// Default maximum number of concurrent in-flight HTTP write requests.
59pub const DEFAULT_MAX_INFLIGHT: usize = 4;
60
61impl Default for WriteOptions {
62 fn default() -> Self {
63 WriteOptions {
64 precision: Precision::Nanosecond,
65 default_tags: HashMap::new(),
66 gzip_threshold: Some(1024),
67 no_sync: false,
68 accept_partial: true,
69 use_v2_api: true,
70 tag_order: Vec::new(),
71 batch_size: DEFAULT_BATCH_SIZE,
72 max_inflight: DEFAULT_MAX_INFLIGHT,
73 }
74 }
75}
76
77impl WriteOptions {
78 pub(crate) fn validate(&self) -> Result<(), Error> {
79 if self.use_v2_api && self.no_sync {
80 return Err(Error::Config(
81 "invalid write options: no_sync requires use_v2_api=false".into(),
82 ));
83 }
84 Ok(())
85 }
86}
87
88/// A type that can be lazily serialised to InfluxDB line protocol for writing.
89///
90/// Pass anything that implements this trait to [`crate::Client::write`].
91///
92/// | Type | Use case |
93/// |---|---|
94/// | `&str` / `String` | Pre-formatted line protocol (low-level escape hatch) |
95/// | `Vec<Point>` | Point builder API (pass ownership; clone a slice with `.to_vec()` if you must keep it) |
96/// | [`crate::write_dataframe::DataFrameWrite`] | polars DataFrame (`polars` feature) |
97///
98/// Implementations return an iterator that yields **one batch per HTTP
99/// request**. The iterator is consumed lazily, so only one batch buffer lives
100/// in memory at a time even for million-point writes.
101pub trait WriteInput {
102 /// Lazily produce line-protocol batches, one per HTTP request.
103 ///
104 /// Implementations should respect `opts.batch_size`. Errors per batch are
105 /// returned in the iterator so partially-valid inputs can still send what
106 /// they can.
107 fn into_lp_batches(
108 self,
109 opts: &WriteOptions,
110 ) -> Box<dyn Iterator<Item = crate::Result<Vec<u8>>> + Send>;
111}
112
113impl WriteInput for &str {
114 fn into_lp_batches(
115 self,
116 _opts: &WriteOptions,
117 ) -> Box<dyn Iterator<Item = crate::Result<Vec<u8>>> + Send> {
118 if self.is_empty() {
119 Box::new(std::iter::empty())
120 } else {
121 Box::new(std::iter::once(Ok(self.as_bytes().to_vec())))
122 }
123 }
124}
125
126impl WriteInput for String {
127 fn into_lp_batches(
128 self,
129 _opts: &WriteOptions,
130 ) -> Box<dyn Iterator<Item = crate::Result<Vec<u8>>> + Send> {
131 if self.is_empty() {
132 Box::new(std::iter::empty())
133 } else {
134 Box::new(std::iter::once(Ok(self.into_bytes())))
135 }
136 }
137}
138
139/// Lazy iterator that serialises chunks of points into LP buffers on demand.
140pub(crate) struct PointBatchIter {
141 points: Vec<Point>,
142 idx: usize,
143 batch_size: usize,
144 precision: Precision,
145 default_tags: HashMap<String, String>,
146 tag_order: Vec<String>,
147}
148
149impl Iterator for PointBatchIter {
150 type Item = crate::Result<Vec<u8>>;
151
152 fn next(&mut self) -> Option<Self::Item> {
153 if self.idx >= self.points.len() {
154 return None;
155 }
156 let end = (self.idx + self.batch_size).min(self.points.len());
157 // Pre-size at roughly 64 bytes per point.
158 let mut buf = Vec::with_capacity((end - self.idx) * 64);
159 let tag_order = if self.tag_order.is_empty() {
160 None
161 } else {
162 Some(self.tag_order.as_slice())
163 };
164 // One scratch buffer reused for every point in the batch.
165 let mut key_scratch = Vec::new();
166 for point in &self.points[self.idx..end] {
167 if let Err(e) = point.write_line_protocol(
168 &mut buf,
169 self.precision,
170 &self.default_tags,
171 tag_order,
172 &mut key_scratch,
173 ) {
174 self.idx = self.points.len(); // stop iteration after error
175 return Some(Err(e));
176 }
177 buf.push(b'\n');
178 }
179 // Drop the trailing newline.
180 if buf.last() == Some(&b'\n') {
181 buf.pop();
182 }
183 self.idx = end;
184 Some(Ok(buf))
185 }
186}
187
188impl WriteInput for Vec<Point> {
189 fn into_lp_batches(
190 self,
191 opts: &WriteOptions,
192 ) -> Box<dyn Iterator<Item = crate::Result<Vec<u8>>> + Send> {
193 Box::new(PointBatchIter {
194 points: self,
195 idx: 0,
196 batch_size: opts.batch_size.max(1),
197 precision: opts.precision,
198 default_tags: opts.default_tags.clone(),
199 tag_order: opts.tag_order.clone(),
200 })
201 }
202}