1use std::sync::Arc;
16
17use api::prom_store::remote::ReadRequest;
18use api::v1::RowInsertRequests;
19use async_trait::async_trait;
20use axum::Extension;
21use axum::body::Bytes;
22use axum::extract::{Query, State};
23use axum::http::{Extensions, HeaderMap, HeaderValue, StatusCode, header};
24use axum::response::IntoResponse;
25use axum_extra::TypedHeader;
26use common_catalog::consts::DEFAULT_SCHEMA_NAME;
27use common_query::prelude::GREPTIME_PHYSICAL_TABLE;
28use common_telemetry::tracing;
29use mime_guess::mime;
30use pipeline::util::to_pipeline_version;
31use pipeline::{ContextReq, PipelineDefinition};
32use prometheus::HistogramTimer;
33use prost::Message;
34use serde::{Deserialize, Serialize};
35use session::context::{Channel, QueryContext, QueryContextRef};
36use snafu::prelude::*;
37use table::requests::{
38 METADATA_QUALITY_INFERRED, SEMANTIC_METRIC_METADATA_QUALITY, SEMANTIC_PER_TABLE_INDEX_KEY,
39 SEMANTIC_SIGNAL_TYPE, SEMANTIC_SOURCE, SEMANTIC_SOURCE_VERSION, SIGNAL_TYPE_METRIC,
40 SOURCE_PROMETHEUS,
41};
42
43use crate::batcher::logical_table::LogicalTablePendingRowsBatcher;
44use crate::error::{self, InternalSnafu, PipelineSnafu, Result};
45use crate::http::extractor::PipelineInfo;
46use crate::http::header::{
47 CONTENT_TYPE_PROTOBUF_STR, GREPTIME_DB_HEADER_METRICS, write_cost_header_map,
48};
49use crate::http::memory_limit::BodyMemoryAccounting;
50use crate::prom_remote_write::decode::PromSeriesProcessor;
51use crate::prom_remote_write::v2::decode_remote_write_v2;
52use crate::prom_remote_write::validation::PromValidationMode;
53use crate::prom_remote_write::{
54 REMOTE_WRITE_V1_VERSION, REMOTE_WRITE_V2_VERSION, decode_remote_write_request,
55};
56use crate::prom_store::{MAX_DECOMPRESSED_REQUEST_SIZE, snappy_decompress_limited};
57use crate::query_handler::{PipelineHandlerRef, PromStoreProtocolHandlerRef, PromStoreResponse};
58use crate::request_memory_limiter::ServerMemoryLimiter;
59
60pub const PHYSICAL_TABLE_PARAM: &str = "physical_table";
61pub const DEFAULT_ENCODING: &str = "snappy";
62pub const VM_ENCODING: &str = "zstd";
63pub const VM_PROTO_VERSION: &str = "1";
64const REMOTE_WRITE_V1_PROTO: &str = "prometheus.WriteRequest";
65const REMOTE_WRITE_V2_PROTO: &str = "io.prometheus.write.v2.Request";
66const CONTENT_TYPE_PROTO_PARAM: &str = "proto";
67const REMOTE_WRITE_V2_SAMPLES_WRITTEN_HEADER: &str = "x-prometheus-remote-write-samples-written";
68const REMOTE_WRITE_V2_HISTOGRAMS_WRITTEN_HEADER: &str =
69 "x-prometheus-remote-write-histograms-written";
70const REMOTE_WRITE_V2_EXEMPLARS_WRITTEN_HEADER: &str =
71 "x-prometheus-remote-write-exemplars-written";
72
73#[derive(Clone)]
74pub struct PromStoreState {
75 pub prom_store_handler: PromStoreProtocolHandlerRef,
76 pub pipeline_handler: Option<PipelineHandlerRef>,
77 pub prom_store_with_metric_engine: bool,
78 pub prom_validation_mode: PromValidationMode,
79 pub pending_rows_batcher: Option<Arc<LogicalTablePendingRowsBatcher>>,
80 pub memory_limiter: ServerMemoryLimiter,
83}
84
85#[derive(Debug, Serialize, Deserialize)]
86pub struct RemoteWriteQuery {
87 pub db: Option<String>,
88 pub physical_table: Option<String>,
91 pub get_vm_proto_version: Option<String>,
93}
94
95impl Default for RemoteWriteQuery {
96 fn default() -> RemoteWriteQuery {
97 Self {
98 db: Some(DEFAULT_SCHEMA_NAME.to_string()),
99 physical_table: Some(GREPTIME_PHYSICAL_TABLE.to_string()),
100 get_vm_proto_version: None,
101 }
102 }
103}
104
105#[axum_macros::debug_handler]
106#[tracing::instrument(
107 skip_all,
108 fields(protocol = "prometheus", request_type = "remote_write")
109)]
110pub async fn remote_write(
111 State(state): State<PromStoreState>,
112 Query(params): Query<RemoteWriteQuery>,
113 (Extension(query_ctx), extensions): (Extension<QueryContext>, Extensions),
114 content_type: Option<TypedHeader<headers::ContentType>>,
115 pipeline_info: PipelineInfo,
116 content_encoding: TypedHeader<headers::ContentEncoding>,
117 body: Bytes,
118) -> Result<axum::response::Response> {
119 let is_zstd = content_encoding.contains(VM_ENCODING);
120
121 match remote_write_proto(content_type) {
122 RemoteWriteProto::V1 => {
123 remote_write_v1(state, params, query_ctx, pipeline_info, is_zstd, body).await
124 }
125 RemoteWriteProto::V2 => {
126 if let Some(response) = unsupported_remote_write_v2_encoding_response(&content_encoding)
127 {
128 return Ok(response);
129 }
130 let body_reserved_bytes = extensions
132 .get::<BodyMemoryAccounting>()
133 .map_or(0, BodyMemoryAccounting::reserved_bytes);
134 remote_write_v2(
135 state,
136 params,
137 query_ctx,
138 pipeline_info,
139 is_zstd,
140 body,
141 body_reserved_bytes,
142 )
143 .await
144 }
145 RemoteWriteProto::Unsupported(content_type) => Ok((
146 StatusCode::UNSUPPORTED_MEDIA_TYPE,
147 format!("unsupported prometheus remote write content type: {content_type}"),
148 )
149 .into_response()),
150 }
151}
152
153async fn remote_write_v1(
154 state: PromStoreState,
155 params: RemoteWriteQuery,
156 query_ctx: QueryContext,
157 pipeline_info: PipelineInfo,
158 is_zstd: bool,
159 body: Bytes,
160) -> Result<axum::response::Response> {
161 let PromStoreState {
162 prom_store_handler,
163 pipeline_handler,
164 prom_store_with_metric_engine,
165 prom_validation_mode,
166 pending_rows_batcher,
167 memory_limiter,
168 } = state;
169
170 if let Some(response) = vm_proto_version_response(¶ms) {
171 return Ok(response);
172 }
173
174 let (db, query_ctx, _timer) =
175 prepare_remote_write_context(¶ms, query_ctx, REMOTE_WRITE_V1_VERSION);
176 let query_ctx = Arc::new(query_ctx);
177
178 let mut processor = PromSeriesProcessor::default_processor();
179
180 if let Some(pipeline_name) = pipeline_info.pipeline_name {
181 let pipeline_def = PipelineDefinition::from_name(
182 &pipeline_name,
183 to_pipeline_version(pipeline_info.pipeline_version.as_deref())
184 .context(PipelineSnafu)?,
185 None,
186 )
187 .context(PipelineSnafu)?;
188 let pipeline_handler = pipeline_handler.context(InternalSnafu {
189 err_msg: "pipeline handler is not set".to_string(),
190 })?;
191
192 processor.set_pipeline(pipeline_handler, query_ctx.clone(), pipeline_def);
193 }
194
195 let (mut decoded, decode_guards) = decode_remote_write_request(
198 is_zstd,
199 body,
200 prom_validation_mode,
201 &mut processor,
202 &memory_limiter,
203 )
204 .await?;
205
206 let req = if processor.use_pipeline {
212 drop(decoded);
213 processor.exec_pipeline().await?
214 } else {
215 let req = decoded.as_insert_requests();
216 drop(decoded);
217 req
218 };
219 drop(decode_guards);
220 let batches = into_prom_write_batches(req, query_ctx);
221
222 let outcome = match write_prometheus_rows_with_progress(
223 prom_store_handler,
224 pending_rows_batcher,
225 prom_store_with_metric_engine,
226 batches,
227 )
228 .await
229 {
230 Ok(outcome) => outcome,
231 Err(error) => {
232 record_remote_write_samples(&db, REMOTE_WRITE_V1_VERSION, error.rows_written);
233 return Err(error.error);
234 }
235 };
236 record_remote_write_samples(&db, REMOTE_WRITE_V1_VERSION, outcome.rows_written);
237
238 Ok((
239 StatusCode::NO_CONTENT,
240 write_cost_header_map(outcome.write_cost),
241 )
242 .into_response())
243}
244
245async fn remote_write_v2(
246 state: PromStoreState,
247 params: RemoteWriteQuery,
248 query_ctx: QueryContext,
249 pipeline_info: PipelineInfo,
250 is_zstd: bool,
251 body: Bytes,
252 body_reserved_bytes: u64,
253) -> Result<axum::response::Response> {
254 let PromStoreState {
255 prom_store_handler,
256 pipeline_handler: _,
257 prom_store_with_metric_engine,
258 prom_validation_mode: _,
259 pending_rows_batcher,
260 memory_limiter,
261 } = state;
262
263 if let Some(response) = vm_proto_version_response(¶ms) {
264 return Ok(response);
265 }
266
267 let _ = pipeline_info;
270
271 let (db, mut query_ctx, _timer) =
272 prepare_remote_write_context(¶ms, query_ctx, REMOTE_WRITE_V2_VERSION);
273
274 let req =
275 match decode_remote_write_v2(is_zstd, body, &memory_limiter, body_reserved_bytes).await {
276 Ok(req) => req,
277 Err(error) => return Ok(remote_write_v2_error_response(error, 0, 0, 0)),
278 };
279 if let Some(index) = req.semantic_index.encode(&query_ctx.current_schema()) {
282 query_ctx.set_extension(SEMANTIC_PER_TABLE_INDEX_KEY, index);
283 }
284 let query_ctx = Arc::new(query_ctx);
285 let (sample_count, histogram_count) = (req.sample_count, req.histogram_count);
286 let mut sample_batches = into_prom_write_batches(req.samples, query_ctx.clone());
287 let mut histogram_batches = into_prom_write_batches(req.histograms, query_ctx);
288 for (batches, histogram) in [(&mut sample_batches, false), (&mut histogram_batches, true)] {
289 let counter = if histogram {
290 crate::metrics::PROM_STORE_REMOTE_WRITE_HISTOGRAMS
291 .with_label_values(&[&db, REMOTE_WRITE_V2_VERSION])
292 } else {
293 crate::metrics::PROM_STORE_REMOTE_WRITE_SAMPLES
294 .with_label_values(&[&db, REMOTE_WRITE_V2_VERSION])
295 };
296 for (ctx, _) in batches {
297 let counter = counter.clone();
298 let progress =
299 session::write_progress::WriteProgress::with_reservation(req.memory_guard.clone())
300 .with_observer(move |rows| counter.inc_by(rows));
301 let mut fork = ctx.fork();
302 fork.set_write_progress(Arc::new(progress));
303 *ctx = Arc::new(fork);
304 }
305 }
306 let outcome = match write_prometheus_v2_rows_with_progress(
307 prom_store_handler,
308 pending_rows_batcher,
309 prom_store_with_metric_engine,
310 sample_batches,
311 histogram_batches,
312 )
313 .await
314 {
315 Ok(outcome) => outcome,
316 Err(error) => {
317 return Ok(remote_write_v2_error_response(
318 error.error,
319 error.samples_written,
320 error.histograms_written,
321 0,
322 ));
323 }
324 };
325 debug_assert!(outcome.samples_written <= sample_count);
326 debug_assert!(outcome.histograms_written <= histogram_count);
327 let mut headers = write_cost_header_map(outcome.write_cost);
328 append_remote_write_v2_written_headers(
329 &mut headers,
330 outcome.samples_written,
331 outcome.histograms_written,
332 0,
333 );
334
335 let status = if outcome.accepted {
336 StatusCode::ACCEPTED
337 } else {
338 StatusCode::NO_CONTENT
339 };
340 Ok((status, headers).into_response())
341}
342
343fn vm_proto_version_response(params: &RemoteWriteQuery) -> Option<axum::response::Response> {
344 params
345 .get_vm_proto_version
346 .as_ref()
347 .map(|_| VM_PROTO_VERSION.into_response())
348}
349
350fn prepare_remote_write_context(
354 params: &RemoteWriteQuery,
355 mut query_ctx: QueryContext,
356 remote_write_version: &str,
357) -> (String, QueryContext, HistogramTimer) {
358 let db = params.db.clone().unwrap_or_default();
359 query_ctx.set_channel(Channel::Prometheus);
360 let physical_table = params
361 .physical_table
362 .clone()
363 .unwrap_or_else(|| GREPTIME_PHYSICAL_TABLE.to_string());
364 query_ctx.set_extension(PHYSICAL_TABLE_PARAM, physical_table);
365 query_ctx.set_extension(SEMANTIC_SIGNAL_TYPE, SIGNAL_TYPE_METRIC);
371 query_ctx.set_extension(SEMANTIC_SOURCE, SOURCE_PROMETHEUS);
372 query_ctx.set_extension(SEMANTIC_SOURCE_VERSION, remote_write_version);
373 query_ctx.set_extension(SEMANTIC_METRIC_METADATA_QUALITY, METADATA_QUALITY_INFERRED);
374 let timer = crate::metrics::METRIC_HTTP_PROM_STORE_WRITE_ELAPSED
375 .with_label_values(&[db.as_str(), remote_write_version])
376 .start_timer();
377
378 (db, query_ctx, timer)
379}
380
381struct PromWriteOutcome {
382 write_cost: usize,
383 rows_written: u64,
384}
385
386struct PromWriteError {
387 error: error::Error,
388 rows_written: u64,
389}
390
391struct PromWriteV2Outcome {
392 accepted: bool,
393 write_cost: usize,
394 samples_written: u64,
395 histograms_written: u64,
396}
397
398struct PromWriteV2Error {
399 error: error::Error,
400 samples_written: u64,
401 histograms_written: u64,
402}
403
404type PromWriteBatch = (QueryContextRef, RowInsertRequests);
405
406#[async_trait]
407trait PromWriteBatcher: Send + Sync {
408 async fn submit(&self, requests: RowInsertRequests, ctx: QueryContextRef) -> Result<u64>;
409}
410
411#[async_trait]
412impl PromWriteBatcher for LogicalTablePendingRowsBatcher {
413 async fn submit(&self, requests: RowInsertRequests, ctx: QueryContextRef) -> Result<u64> {
414 LogicalTablePendingRowsBatcher::submit(self, requests, ctx).await
415 }
416}
417
418fn into_prom_write_batches(req: ContextReq, query_ctx: QueryContextRef) -> Vec<PromWriteBatch> {
419 req.as_req_iter(query_ctx).collect()
420}
421
422async fn preflight_prometheus_rows(
423 prom_store_handler: &PromStoreProtocolHandlerRef,
424 batches: &mut [PromWriteBatch],
425) -> Result<()> {
426 for (ctx, reqs) in batches.iter_mut() {
427 prom_store_handler.pre_write(reqs, ctx.clone()).await?;
428 *ctx = Arc::new(ctx.fork());
431 }
432 operator::insert::admit_row_insert_batches(batches)
433 .await
434 .map_err(common_error::ext::BoxedError::new)
435 .context(error::ExecuteGrpcQuerySnafu)?;
436 Ok(())
437}
438
439async fn write_prometheus_rows_with_progress(
444 prom_store_handler: PromStoreProtocolHandlerRef,
445 pending_rows_batcher: Option<Arc<LogicalTablePendingRowsBatcher>>,
446 prom_store_with_metric_engine: bool,
447 mut batches: Vec<PromWriteBatch>,
448) -> std::result::Result<PromWriteOutcome, PromWriteError> {
449 if prom_store_with_metric_engine && let Some(batcher) = pending_rows_batcher {
450 preflight_prometheus_rows(&prom_store_handler, &mut batches)
456 .await
457 .map_err(|error| PromWriteError {
458 error,
459 rows_written: 0,
460 })?;
461 if batcher.accepts_bulk_destinations(batches.iter()).await {
466 let mut rows_written = 0;
467 for (temp_ctx, reqs) in batches {
468 let rows =
469 batcher
470 .submit(reqs, temp_ctx)
471 .await
472 .map_err(|error| PromWriteError {
473 error,
474 rows_written,
475 })?;
476 rows_written += rows;
477 }
478 return Ok(PromWriteOutcome {
479 write_cost: 0,
480 rows_written,
481 });
482 }
483 return write_prepared_prometheus_rows_with_progress(
484 prom_store_handler,
485 batches,
486 prom_store_with_metric_engine,
487 )
488 .await;
489 }
490
491 let row_counts = batches
492 .iter()
493 .map(|(_, request)| prom_write_row_count(request))
494 .collect::<Vec<_>>();
495 let batch_count = batches.len();
496 let outputs = prom_store_handler
497 .write_all(batches, prom_store_with_metric_engine)
498 .await
499 .map_err(|error| PromWriteError {
500 error,
501 rows_written: 0,
502 })?;
503 let output_count = outputs.len();
504 let mut write_cost = 0;
505 let mut rows_written = 0;
506 for (output, rows) in outputs.into_iter().zip(row_counts) {
507 let output = output.map_err(|error| PromWriteError {
508 error,
509 rows_written,
510 })?;
511 write_cost += output.meta.cost;
512 rows_written += rows;
513 }
514 if output_count != batch_count {
515 return Err(PromWriteError {
516 error: incomplete_prom_write_error(),
517 rows_written,
518 });
519 }
520
521 Ok(PromWriteOutcome {
522 write_cost,
523 rows_written,
524 })
525}
526
527async fn write_prepared_prometheus_rows_with_progress(
530 prom_store_handler: PromStoreProtocolHandlerRef,
531 batches: Vec<PromWriteBatch>,
532 prom_store_with_metric_engine: bool,
533) -> std::result::Result<PromWriteOutcome, PromWriteError> {
534 let mut write_cost = 0;
535 let mut rows_written = 0;
536 for (ctx, request) in batches {
537 let rows = prom_write_row_count(&request);
538 let output = prom_store_handler
539 .write_prepared(request, ctx, prom_store_with_metric_engine)
540 .await
541 .map_err(|error| PromWriteError {
542 error,
543 rows_written,
544 })?;
545 write_cost += output.meta.cost;
546 rows_written += rows;
547 }
548 Ok(PromWriteOutcome {
549 write_cost,
550 rows_written,
551 })
552}
553
554async fn write_prometheus_v2_rows_with_progress(
555 prom_store_handler: PromStoreProtocolHandlerRef,
556 pending_rows_batcher: Option<Arc<LogicalTablePendingRowsBatcher>>,
557 prom_store_with_metric_engine: bool,
558 sample_batches: Vec<PromWriteBatch>,
559 histogram_batches: Vec<PromWriteBatch>,
560) -> std::result::Result<PromWriteV2Outcome, PromWriteV2Error> {
561 let sample_batch_count = sample_batches.len();
562 let mut batches = sample_batches;
563 batches.extend(histogram_batches);
564 preflight_prometheus_rows(&prom_store_handler, &mut batches)
565 .await
566 .map_err(|error| PromWriteV2Error {
567 error,
568 samples_written: 0,
569 histograms_written: 0,
570 })?;
571 let batcher = if prom_store_with_metric_engine
572 && let Some(batcher) = pending_rows_batcher.as_ref()
573 && batcher.accepts_bulk_destinations(batches.iter()).await
574 {
575 Some(batcher.as_ref() as &dyn PromWriteBatcher)
576 } else {
577 None
578 };
579 write_batched_prometheus_v2_rows_with_progress(
580 prom_store_handler,
581 batcher,
582 prom_store_with_metric_engine,
583 sample_batch_count,
584 batches,
585 )
586 .await
587}
588
589async fn write_batched_prometheus_v2_rows_with_progress(
590 prom_store_handler: PromStoreProtocolHandlerRef,
591 batcher: Option<&dyn PromWriteBatcher>,
592 prom_store_with_metric_engine: bool,
593 sample_batch_count: usize,
594 mut batches: Vec<PromWriteBatch>,
595) -> std::result::Result<PromWriteV2Outcome, PromWriteV2Error> {
596 let progress = batches
597 .iter_mut()
598 .map(|(ctx, _)| {
599 if ctx.write_progress().is_none() {
600 let mut fork = ctx.fork();
601 fork.set_write_progress(
602 Arc::new(session::write_progress::WriteProgress::default()),
603 );
604 *ctx = Arc::new(fork);
605 }
606 ctx.write_progress().cloned().unwrap_or_default()
607 })
608 .collect::<Vec<_>>();
609 let counts = || {
610 let samples = progress[..sample_batch_count]
611 .iter()
612 .map(|p| p.written())
613 .sum();
614 let histograms = progress[sample_batch_count..]
615 .iter()
616 .map(|p| p.written())
617 .sum();
618 (samples, histograms)
619 };
620 let mut write_cost = 0;
621 let mut samples_accepted = 0;
622 let mut histograms_accepted = 0;
623 for (index, (ctx, requests)) in batches.into_iter().enumerate() {
624 let result = if index < sample_batch_count
625 && let Some(batcher) = batcher
626 {
627 batcher
628 .submit(requests, ctx)
629 .await
630 .map(|rows| (rows, 0, false))
631 } else {
632 prom_store_handler
633 .write_prepared(requests, ctx, prom_store_with_metric_engine)
634 .await
635 .map(|output| {
636 let pending = !output.meta.write_completions.is_empty();
637 let (rows, cost) = output.extract_rows_and_cost();
638 (rows as u64, cost, pending)
639 })
640 };
641 match result {
642 Ok((rows, cost, pending)) => {
643 write_cost += cost;
644 if index < sample_batch_count {
645 samples_accepted += rows;
646 } else {
647 histograms_accepted += rows;
648 }
649 if pending {
650 progress[index].mark_accepted();
651 }
652 if !progress[index].accepted() {
653 progress[index].confirm(rows);
654 }
655 }
656 Err(error) => {
657 for progress in &progress {
658 progress.wait().await;
659 }
660 let (samples_written, histograms_written) = counts();
661 return Err(PromWriteV2Error {
662 error,
663 samples_written,
664 histograms_written,
665 });
666 }
667 }
668 }
669 Ok(PromWriteV2Outcome {
670 write_cost,
671 samples_written: samples_accepted,
673 histograms_written: histograms_accepted,
674 accepted: progress.iter().any(|p| p.accepted()),
675 })
676}
677
678fn prom_write_row_count(request: &RowInsertRequests) -> u64 {
679 request
680 .inserts
681 .iter()
682 .filter_map(|insert| insert.rows.as_ref().map(|rows| rows.rows.len() as u64))
683 .sum()
684}
685
686fn incomplete_prom_write_error() -> error::Error {
687 InternalSnafu {
688 err_msg: "prometheus write handler returned before processing every batch".to_string(),
689 }
690 .build()
691}
692
693fn record_remote_write_samples(db: &str, version: &str, rows: u64) {
694 if rows == 0 {
695 return;
696 }
697 crate::metrics::PROM_STORE_REMOTE_WRITE_SAMPLES
698 .with_label_values(&[db, version])
699 .inc_by(rows);
700}
701
702fn remote_write_v2_error_response(
703 error: error::Error,
704 samples: u64,
705 histograms: u64,
706 exemplars: u64,
707) -> axum::response::Response {
708 let mut response = error.into_response();
709 append_remote_write_v2_written_headers(response.headers_mut(), samples, histograms, exemplars);
710 response
711}
712
713fn append_remote_write_v2_written_headers(
714 headers: &mut HeaderMap,
715 samples: u64,
716 histograms: u64,
717 exemplars: u64,
718) {
719 headers.insert(
720 REMOTE_WRITE_V2_SAMPLES_WRITTEN_HEADER,
721 HeaderValue::from_str(&samples.to_string()).expect("u64 header value is valid"),
722 );
723 headers.insert(
724 REMOTE_WRITE_V2_HISTOGRAMS_WRITTEN_HEADER,
725 HeaderValue::from_str(&histograms.to_string()).expect("u64 header value is valid"),
726 );
727 headers.insert(
728 REMOTE_WRITE_V2_EXEMPLARS_WRITTEN_HEADER,
729 HeaderValue::from_str(&exemplars.to_string()).expect("u64 header value is valid"),
730 );
731}
732
733enum RemoteWriteProto {
734 V1,
735 V2,
736 Unsupported(mime::Mime),
737}
738
739fn remote_write_proto(content_type: Option<TypedHeader<headers::ContentType>>) -> RemoteWriteProto {
741 let Some(TypedHeader(content_type)) = content_type else {
742 return RemoteWriteProto::V1;
743 };
744
745 let mime_type: mime::Mime = content_type.into();
746 if !mime_type
747 .essence_str()
748 .eq_ignore_ascii_case(CONTENT_TYPE_PROTOBUF_STR)
749 {
750 return RemoteWriteProto::Unsupported(mime_type);
751 }
752
753 for (name, value) in mime_type.params() {
754 if !name.as_str().eq_ignore_ascii_case(CONTENT_TYPE_PROTO_PARAM) {
755 continue;
756 }
757
758 return match value.as_str() {
759 REMOTE_WRITE_V1_PROTO => RemoteWriteProto::V1,
760 REMOTE_WRITE_V2_PROTO => RemoteWriteProto::V2,
761 _ => RemoteWriteProto::Unsupported(mime_type.clone()),
762 };
763 }
764
765 RemoteWriteProto::V1
766}
767
768fn unsupported_remote_write_v2_encoding_response(
769 content_encoding: &headers::ContentEncoding,
770) -> Option<axum::response::Response> {
771 if content_encoding.contains(DEFAULT_ENCODING) || content_encoding.contains(VM_ENCODING) {
772 return None;
773 }
774
775 Some((
776 StatusCode::UNSUPPORTED_MEDIA_TYPE,
777 format!(
778 "unsupported prometheus remote write content encoding: only {DEFAULT_ENCODING} and {VM_ENCODING} are supported"
779 ),
780 )
781 .into_response())
782}
783
784impl IntoResponse for PromStoreResponse {
785 fn into_response(self) -> axum::response::Response {
786 let mut header_map = HeaderMap::new();
787 header_map.insert(&header::CONTENT_TYPE, self.content_type);
788 header_map.insert(&header::CONTENT_ENCODING, self.content_encoding);
789
790 let metrics = if self.resp_metrics.is_empty() {
791 None
792 } else {
793 serde_json::to_string(&self.resp_metrics).ok()
794 };
795 if let Some(m) = metrics.and_then(|m| HeaderValue::from_str(&m).ok()) {
796 header_map.insert(&GREPTIME_DB_HEADER_METRICS, m);
797 }
798
799 (header_map, self.body).into_response()
800 }
801}
802
803#[axum_macros::debug_handler]
804#[tracing::instrument(
805 skip_all,
806 fields(protocol = "prometheus", request_type = "remote_read")
807)]
808pub async fn remote_read(
809 State(state): State<PromStoreState>,
810 Query(params): Query<RemoteWriteQuery>,
811 Extension(mut query_ctx): Extension<QueryContext>,
812 body: Bytes,
813) -> Result<PromStoreResponse> {
814 let db = params.db.clone().unwrap_or_default();
815 query_ctx.set_channel(Channel::Prometheus);
816
817 let request = decode_remote_read_request(body, &state.memory_limiter).await?;
818
819 let query_ctx = Arc::new(query_ctx);
820 let _timer = crate::metrics::METRIC_HTTP_PROM_STORE_READ_ELAPSED
821 .with_label_values(&[db.as_str()])
822 .start_timer();
823
824 state.prom_store_handler.read(request, query_ctx).await
825}
826
827async fn decode_remote_read_request(
828 body: Bytes,
829 limiter: &ServerMemoryLimiter,
830) -> Result<ReadRequest> {
831 let buf =
834 snappy_decompress_limited(&body[..], MAX_DECOMPRESSED_REQUEST_SIZE, limiter, 0).await?;
835
836 ReadRequest::decode(&buf[..]).context(error::DecodePromRemoteRequestSnafu)
837}
838
839#[cfg(test)]
840mod tests {
841 use std::sync::Mutex;
842 use std::time::Duration;
843
844 use api::prom_store::remote::ReadRequest;
845 use api::v1::{Row, RowInsertRequest, Rows};
846 use async_trait::async_trait;
847 use axum::body::Body;
848 use axum::http::Request;
849 use axum::{Router, middleware, routing};
850 use common_memory_manager::OnExhaustedPolicy;
851 use common_query::Output;
852 use pipeline::GreptimePipelineParams;
853 use session::context::{QueryContext, QueryContextRef};
854 use tower::ServiceExt;
855
856 use super::*;
857 use crate::http::memory_limit::memory_limit_middleware;
858 use crate::prom_remote_write::validation::PromValidationMode;
859 use crate::query_handler::PromStoreProtocolHandler;
860
861 #[test]
862 fn test_remote_write_proto() {
863 assert!(matches!(
864 remote_write_proto(content_type(
865 "application/x-protobuf;proto=io.prometheus.write.v2.Request"
866 )),
867 RemoteWriteProto::V2
868 ));
869 assert!(matches!(
870 remote_write_proto(content_type(
871 "application/x-protobuf; proto=\"io.prometheus.write.v2.Request\""
872 )),
873 RemoteWriteProto::V2
874 ));
875 assert!(matches!(
876 remote_write_proto(content_type(
877 "APPLICATION/X-PROTOBUF;proto=io.prometheus.write.v2.Request"
878 )),
879 RemoteWriteProto::V2
880 ));
881 assert!(matches!(
882 remote_write_proto(content_type("application/x-protobuf")),
883 RemoteWriteProto::V1
884 ));
885 assert!(matches!(
886 remote_write_proto(content_type(
887 "application/x-protobuf;proto=prometheus.WriteRequest"
888 )),
889 RemoteWriteProto::V1
890 ));
891 assert!(matches!(
892 remote_write_proto(content_type(
893 "application/x-protobuf;proto=unknown.WriteRequest"
894 )),
895 RemoteWriteProto::Unsupported(_)
896 ));
897 assert!(matches!(
898 remote_write_proto(content_type(
899 "application/json;proto=io.prometheus.write.v2.Request"
900 )),
901 RemoteWriteProto::Unsupported(_)
902 ));
903 assert!(matches!(remote_write_proto(None), RemoteWriteProto::V1));
904 }
905
906 fn content_type(value: &str) -> Option<TypedHeader<headers::ContentType>> {
907 Some(TypedHeader(std::str::FromStr::from_str(value).unwrap()))
908 }
909
910 #[test]
911 fn test_prepare_remote_write_context_stamps_semantics() {
912 let (_, query_ctx, _timer) = prepare_remote_write_context(
913 &RemoteWriteQuery::default(),
914 QueryContext::with("greptime", "public"),
915 REMOTE_WRITE_V2_VERSION,
916 );
917
918 assert_eq!(
919 query_ctx.extension(SEMANTIC_SIGNAL_TYPE),
920 Some(SIGNAL_TYPE_METRIC)
921 );
922 assert_eq!(
923 query_ctx.extension(SEMANTIC_SOURCE),
924 Some(SOURCE_PROMETHEUS)
925 );
926 assert_eq!(
927 query_ctx.extension(SEMANTIC_SOURCE_VERSION),
928 Some(REMOTE_WRITE_V2_VERSION)
929 );
930 assert_eq!(
931 query_ctx.extension(SEMANTIC_METRIC_METADATA_QUALITY),
932 Some(METADATA_QUALITY_INFERRED)
933 );
934 }
935
936 #[tokio::test]
937 async fn test_mixed_v2_preflights_all_then_batches_only_samples() {
938 let events = Arc::new(Mutex::new(Vec::new()));
939 let handler: PromStoreProtocolHandlerRef = Arc::new(RecordingPromStoreHandler {
940 events: events.clone(),
941 });
942 let batcher = RecordingPromWriteBatcher {
943 events: events.clone(),
944 };
945
946 let mut batches = vec![
947 test_prom_write_batch("sample"),
948 test_prom_write_batch("histogram"),
949 ];
950 preflight_prometheus_rows(&handler, &mut batches)
953 .await
954 .unwrap();
955 let Ok(outcome) = write_batched_prometheus_v2_rows_with_progress(
956 handler,
957 Some(&batcher),
958 true,
959 1,
960 batches,
961 )
962 .await
963 else {
964 panic!("mixed remote write should succeed")
965 };
966
967 assert_eq!(1, outcome.samples_written);
968 assert_eq!(1, outcome.histograms_written);
969 assert_eq!(
970 vec![
971 "pre:sample".to_string(),
972 "pre:histogram".to_string(),
973 "batch:sample".to_string(),
974 "direct:histogram".to_string(),
975 ],
976 *events.lock().unwrap()
977 );
978 }
979
980 fn test_prom_write_batch(table_name: &str) -> PromWriteBatch {
981 (
982 Arc::new(QueryContext::with("greptime", "public")),
983 RowInsertRequests {
984 inserts: vec![RowInsertRequest {
985 table_name: table_name.to_string(),
986 rows: Some(Rows {
987 schema: Vec::new(),
988 rows: vec![Row { values: Vec::new() }],
989 }),
990 }],
991 },
992 )
993 }
994
995 #[tokio::test]
996 async fn test_async_v2_counts_acceptance_without_confirming_storage() {
997 let handler: PromStoreProtocolHandlerRef = Arc::new(RecordingPromStoreHandler {
998 events: Default::default(),
999 });
1000 let mut batches = vec![
1001 test_prom_write_batch("queued"),
1002 test_prom_write_batch("queued"),
1003 ];
1004 let mut progress = Vec::new();
1005 for (ctx, _) in &mut batches {
1006 let tracker = Arc::new(session::write_progress::WriteProgress::default());
1007 Arc::get_mut(ctx)
1008 .unwrap()
1009 .set_write_progress(tracker.clone());
1010 progress.push(tracker);
1011 }
1012 preflight_prometheus_rows(&handler, &mut batches)
1013 .await
1014 .unwrap();
1015 let Ok(outcome) =
1016 write_batched_prometheus_v2_rows_with_progress(handler, None, true, 1, batches).await
1017 else {
1018 panic!("queued writes should be accepted")
1019 };
1020 assert!(outcome.accepted);
1021 assert_eq!(outcome.samples_written, 1);
1022 assert_eq!(outcome.histograms_written, 1);
1023 assert!(progress.iter().all(|tracker| tracker.written() == 0));
1024 }
1025
1026 fn record_write_event(events: &Mutex<Vec<String>>, phase: &str, request: &RowInsertRequests) {
1027 events.lock().unwrap().push(format!(
1028 "{phase}:{}",
1029 request.inserts.first().unwrap().table_name
1030 ));
1031 }
1032
1033 #[tokio::test]
1034 async fn test_failed_request_waits_for_earlier_queued_writes() {
1035 struct DelayedBatcher(
1036 Mutex<
1037 Option<(
1038 Arc<session::write_progress::WriteProgress>,
1039 session::write_progress::PendingWrite,
1040 )>,
1041 >,
1042 );
1043 #[async_trait]
1044 impl PromWriteBatcher for DelayedBatcher {
1045 async fn submit(
1046 &self,
1047 request: RowInsertRequests,
1048 ctx: QueryContextRef,
1049 ) -> Result<u64> {
1050 let progress = ctx.write_progress().unwrap().clone();
1051 let pending = progress.start();
1052 progress.mark_accepted();
1053 *self.0.lock().unwrap() = Some((progress, pending));
1054 Ok(prom_write_row_count(&request))
1055 }
1056 }
1057 let handler: PromStoreProtocolHandlerRef = Arc::new(RecordingPromStoreHandler {
1058 events: Default::default(),
1059 });
1060 let batcher = DelayedBatcher(Mutex::new(None));
1061 let mut batches = vec![
1062 test_prom_write_batch("queued"),
1063 test_prom_write_batch("partial"),
1064 ];
1065 preflight_prometheus_rows(&handler, &mut batches)
1066 .await
1067 .unwrap();
1068 let write = write_batched_prometheus_v2_rows_with_progress(
1069 handler,
1070 Some(&batcher),
1071 true,
1072 1,
1073 batches,
1074 );
1075 tokio::pin!(write);
1076 assert!(futures::poll!(write.as_mut()).is_pending());
1077 let (progress, pending) = batcher.0.lock().unwrap().take().unwrap();
1078 progress.add_written(1);
1079 drop(pending);
1080 let Err(error) = write.await else {
1081 panic!("the second batch must fail")
1082 };
1083 assert_eq!(error.samples_written, 1);
1084 assert_eq!(error.histograms_written, 1);
1085 }
1086
1087 struct RecordingPromWriteBatcher {
1088 events: Arc<Mutex<Vec<String>>>,
1089 }
1090
1091 #[async_trait]
1092 impl PromWriteBatcher for RecordingPromWriteBatcher {
1093 async fn submit(&self, requests: RowInsertRequests, ctx: QueryContextRef) -> Result<u64> {
1094 assert_eq!(ctx.write_rows_to_admit("greptime", "public", 1), 0);
1095 record_write_event(&self.events, "batch", &requests);
1096 Ok(prom_write_row_count(&requests))
1097 }
1098 }
1099
1100 struct RecordingPromStoreHandler {
1101 events: Arc<Mutex<Vec<String>>>,
1102 }
1103
1104 #[async_trait]
1105 impl PromStoreProtocolHandler for RecordingPromStoreHandler {
1106 async fn pre_write(
1107 &self,
1108 request: &RowInsertRequests,
1109 _ctx: QueryContextRef,
1110 ) -> Result<()> {
1111 record_write_event(&self.events, "pre", request);
1112 Ok(())
1113 }
1114
1115 async fn write_prepared(
1116 &self,
1117 request: RowInsertRequests,
1118 ctx: QueryContextRef,
1119 _with_metric_engine: bool,
1120 ) -> Result<Output> {
1121 assert_eq!(ctx.write_rows_to_admit("greptime", "public", 1), 0);
1122 record_write_event(&self.events, "direct", &request);
1123 match request.inserts[0].table_name.as_str() {
1124 "partial" => {
1125 ctx.write_progress().unwrap().add_written(1);
1126 return Err(InternalSnafu {
1127 err_msg: "one region failed",
1128 }
1129 .build());
1130 }
1131 "queued" => ctx.write_progress().unwrap().mark_accepted(),
1132 _ => {}
1133 }
1134 Ok(Output::new_with_affected_rows(
1135 prom_write_row_count(&request) as usize,
1136 ))
1137 }
1138
1139 async fn write(
1140 &self,
1141 _request: RowInsertRequests,
1142 _ctx: QueryContextRef,
1143 _with_metric_engine: bool,
1144 ) -> Result<Output> {
1145 unreachable!("mixed v2 writes use preflighted execution")
1146 }
1147
1148 async fn write_all(
1149 &self,
1150 _requests: Vec<(QueryContextRef, RowInsertRequests)>,
1151 _with_metric_engine: bool,
1152 ) -> Result<Vec<Result<Output>>> {
1153 unreachable!("mixed v2 writes preserve sample and histogram routing")
1154 }
1155
1156 async fn read(
1157 &self,
1158 _request: ReadRequest,
1159 _ctx: QueryContextRef,
1160 ) -> Result<PromStoreResponse> {
1161 unimplemented!()
1162 }
1163 }
1164
1165 #[tokio::test]
1166 async fn test_remote_write_v2_reports_confirmed_partial_and_queued_writes() {
1167 for (name, expected_status, expected_count) in [
1168 ("confirmed", StatusCode::NO_CONTENT, "2"),
1169 ("partial", StatusCode::INTERNAL_SERVER_ERROR, "1"),
1170 ("queued", StatusCode::ACCEPTED, "2"),
1171 ] {
1172 let request = crate::prom_remote_write::v2::test_util::request_with_labels_and_samples(
1173 vec![("__name__", name)],
1174 vec![api::greptime_proto::io::prometheus::write::v2::Sample::default(); 2],
1175 );
1176 let body =
1177 Bytes::from(crate::prom_store::snappy_compress(&request.encode_to_vec()).unwrap());
1178 let mut state = test_state();
1179 state.prom_store_handler = Arc::new(RecordingPromStoreHandler {
1180 events: Default::default(),
1181 });
1182 let response = remote_write_v2(
1183 state,
1184 RemoteWriteQuery::default(),
1185 QueryContext::with("greptime", "public"),
1186 pipeline_info(None),
1187 false,
1188 body,
1189 0,
1190 )
1191 .await
1192 .unwrap();
1193 assert_eq!(response.status(), expected_status);
1194 assert_eq!(
1195 response.headers()[REMOTE_WRITE_V2_SAMPLES_WRITTEN_HEADER],
1196 expected_count
1197 );
1198 assert_eq!(
1199 response.headers()[REMOTE_WRITE_V2_HISTOGRAMS_WRITTEN_HEADER],
1200 "0"
1201 );
1202 assert_eq!(
1203 response.headers()[REMOTE_WRITE_V2_EXEMPLARS_WRITTEN_HEADER],
1204 "0"
1205 );
1206 }
1207 }
1208
1209 fn memory_limited_prom_write_app(limiter: ServerMemoryLimiter) -> Router {
1210 let mut state = test_state();
1211 state.memory_limiter = limiter.clone();
1212 Router::new()
1213 .route("/write", routing::post(remote_write))
1214 .with_state(state)
1215 .layer(Extension(QueryContext::with("greptime", "public")))
1216 .layer(middleware::from_fn_with_state(
1217 limiter,
1218 memory_limit_middleware,
1219 ))
1220 }
1221
1222 #[tokio::test(start_paused = true)]
1223 async fn test_remote_write_v2_accounts_for_body_when_rejecting_or_waiting() {
1224 let wire = api::greptime_proto::io::prometheus::write::v2::Request {
1225 symbols: vec![
1226 String::new(),
1227 format!("{:?}", (0..2048).collect::<Vec<_>>()),
1228 ],
1229 timeseries: Vec::new(),
1230 }
1231 .encode_to_vec();
1232 let decoded_bytes = wire.len().div_ceil(1024) * 1024;
1233
1234 for encoding in [DEFAULT_ENCODING, VM_ENCODING] {
1235 let body = Bytes::from(if encoding == DEFAULT_ENCODING {
1236 crate::prom_store::snappy_compress(&wire).unwrap()
1237 } else {
1238 zstd::stream::encode_all(wire.as_slice(), 3).unwrap()
1239 });
1240 for chunked in [false, true] {
1241 let chunk_size = if chunked { 513 } else { body.len() };
1243 let frames = body
1244 .chunks(chunk_size)
1245 .map(Bytes::copy_from_slice)
1246 .collect::<Vec<_>>();
1247 let body_reserved: usize =
1248 frames.iter().map(|f| f.len().div_ceil(1024) * 1024).sum();
1249 if chunked {
1250 assert!(body_reserved > body.len().div_ceil(1024) * 1024);
1251 }
1252 for decoder_quota in [decoded_bytes - 1024, decoded_bytes, decoded_bytes + 1024] {
1254 let limit = (body_reserved + decoder_quota) as u64;
1255 let limiter = ServerMemoryLimiter::new(limit, OnExhaustedPolicy::default());
1256 let fits = decoder_quota > decoded_bytes;
1257 let occupied = fits.then(|| limiter.try_acquire(1024).unwrap());
1258 let app = memory_limited_prom_write_app(limiter.clone());
1259 let mut request = Request::builder()
1260 .uri("/write")
1261 .method("POST")
1262 .header(
1263 header::CONTENT_TYPE,
1264 "application/x-protobuf;proto=io.prometheus.write.v2.Request",
1265 )
1266 .header(header::CONTENT_ENCODING, encoding);
1267 let request_body = if chunked {
1268 Body::from_stream(futures::stream::iter(
1269 frames.clone().into_iter().map(Ok::<_, std::io::Error>),
1270 ))
1271 } else {
1272 request = request.header(header::CONTENT_LENGTH, body.len().to_string());
1273 Body::from(body.clone())
1274 };
1275 let started = tokio::time::Instant::now();
1276 let response = app.oneshot(request.body(request_body).unwrap());
1277 tokio::pin!(response);
1278 if let Some(occupied) = occupied {
1279 assert!(futures::poll!(response.as_mut()).is_pending());
1280 assert_eq!(limiter.used_bytes(), body_reserved as u64 + 1024);
1282 drop(occupied);
1283 }
1284 let response = response.await.unwrap();
1285 assert_eq!(
1286 response.status(),
1287 if fits {
1288 StatusCode::NO_CONTENT
1289 } else {
1290 StatusCode::TOO_MANY_REQUESTS
1291 },
1292 "encoding={encoding}, chunked={chunked}, decoder_quota={decoder_quota}"
1293 );
1294 assert_eq!(started.elapsed(), Duration::ZERO);
1295 assert_eq!(
1296 response.headers()[REMOTE_WRITE_V2_SAMPLES_WRITTEN_HEADER],
1297 "0"
1298 );
1299 assert_eq!(limiter.used_bytes(), 0);
1300 }
1301 }
1302 }
1303 }
1304
1305 #[tokio::test]
1306 async fn test_remote_write_v2_ignores_pipeline() {
1307 let request = api::greptime_proto::io::prometheus::write::v2::Request {
1308 symbols: vec![String::new()],
1309 timeseries: Vec::new(),
1310 };
1311 let body =
1312 Bytes::from(crate::prom_store::snappy_compress(&request.encode_to_vec()).unwrap());
1313
1314 let response = remote_write_v2(
1315 test_state(),
1316 RemoteWriteQuery::default(),
1317 QueryContext::with("greptime", "public"),
1318 pipeline_info(Some("pipeline")),
1319 false,
1320 body,
1321 0,
1322 )
1323 .await
1324 .unwrap();
1325
1326 assert_eq!(response.status(), StatusCode::NO_CONTENT);
1327 assert_eq!(
1328 Some("0"),
1329 response
1330 .headers()
1331 .get(REMOTE_WRITE_V2_SAMPLES_WRITTEN_HEADER)
1332 .map(|x| x.to_str().unwrap())
1333 );
1334 }
1335
1336 fn test_state() -> PromStoreState {
1337 PromStoreState {
1338 prom_store_handler: Arc::new(NoopPromStoreHandler),
1339 pipeline_handler: None,
1340 prom_store_with_metric_engine: false,
1341 prom_validation_mode: PromValidationMode::Strict,
1342 pending_rows_batcher: None,
1343 memory_limiter: ServerMemoryLimiter::default(),
1344 }
1345 }
1346
1347 fn pipeline_info(pipeline_name: Option<&str>) -> PipelineInfo {
1348 PipelineInfo {
1349 pipeline_name: pipeline_name.map(ToString::to_string),
1350 pipeline_version: None,
1351 pipeline_params: GreptimePipelineParams::default(),
1352 }
1353 }
1354
1355 struct NoopPromStoreHandler;
1356
1357 #[async_trait]
1358 impl PromStoreProtocolHandler for NoopPromStoreHandler {
1359 async fn pre_write(
1360 &self,
1361 _request: &RowInsertRequests,
1362 _ctx: QueryContextRef,
1363 ) -> Result<()> {
1364 Ok(())
1365 }
1366
1367 async fn write_prepared(
1368 &self,
1369 _request: RowInsertRequests,
1370 _ctx: QueryContextRef,
1371 _with_metric_engine: bool,
1372 ) -> Result<Output> {
1373 unreachable!("empty remote write v2 request should not write")
1374 }
1375
1376 async fn write(
1377 &self,
1378 _request: RowInsertRequests,
1379 _ctx: QueryContextRef,
1380 _with_metric_engine: bool,
1381 ) -> Result<Output> {
1382 unreachable!("empty remote write v2 request should not write")
1383 }
1384
1385 async fn write_all(
1386 &self,
1387 requests: Vec<(QueryContextRef, RowInsertRequests)>,
1388 _with_metric_engine: bool,
1389 ) -> Result<Vec<Result<Output>>> {
1390 assert!(requests.is_empty());
1391 Ok(Vec::new())
1392 }
1393
1394 async fn read(
1395 &self,
1396 _request: ReadRequest,
1397 _ctx: QueryContextRef,
1398 ) -> Result<PromStoreResponse> {
1399 unimplemented!()
1400 }
1401 }
1402}