Skip to main content

servers/http/
prom_store.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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::{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_SIGNAL_TYPE,
39    SEMANTIC_SOURCE, SEMANTIC_SOURCE_VERSION, SIGNAL_TYPE_METRIC, SOURCE_PROMETHEUS,
40};
41
42use crate::error::{self, InternalSnafu, PipelineSnafu, Result};
43use crate::http::extractor::PipelineInfo;
44use crate::http::header::{
45    CONTENT_TYPE_PROTOBUF_STR, GREPTIME_DB_HEADER_METRICS, write_cost_header_map,
46};
47use crate::pending_rows_batcher::PendingRowsBatcher;
48use crate::prom_remote_write::decode::PromSeriesProcessor;
49use crate::prom_remote_write::decode_remote_write_request;
50use crate::prom_remote_write::v2::{decode_remote_write_v2_request, into_write_requests};
51use crate::prom_remote_write::validation::PromValidationMode;
52use crate::prom_store::snappy_decompress;
53use crate::query_handler::{PipelineHandlerRef, PromStoreProtocolHandlerRef, PromStoreResponse};
54
55pub const PHYSICAL_TABLE_PARAM: &str = "physical_table";
56pub const DEFAULT_ENCODING: &str = "snappy";
57pub const VM_ENCODING: &str = "zstd";
58pub const VM_PROTO_VERSION: &str = "1";
59const REMOTE_WRITE_V1_VERSION: &str = "1.0";
60const REMOTE_WRITE_V2_VERSION: &str = "2.0";
61const REMOTE_WRITE_V1_PROTO: &str = "prometheus.WriteRequest";
62const REMOTE_WRITE_V2_PROTO: &str = "io.prometheus.write.v2.Request";
63const CONTENT_TYPE_PROTO_PARAM: &str = "proto";
64const REMOTE_WRITE_V2_SAMPLES_WRITTEN_HEADER: &str = "x-prometheus-remote-write-samples-written";
65const REMOTE_WRITE_V2_HISTOGRAMS_WRITTEN_HEADER: &str =
66    "x-prometheus-remote-write-histograms-written";
67const REMOTE_WRITE_V2_EXEMPLARS_WRITTEN_HEADER: &str =
68    "x-prometheus-remote-write-exemplars-written";
69
70#[derive(Clone)]
71pub struct PromStoreState {
72    pub prom_store_handler: PromStoreProtocolHandlerRef,
73    pub pipeline_handler: Option<PipelineHandlerRef>,
74    pub prom_store_with_metric_engine: bool,
75    pub prom_validation_mode: PromValidationMode,
76    pub experimental_enable_prometheus_native_histogram: bool,
77    pub pending_rows_batcher: Option<Arc<PendingRowsBatcher>>,
78}
79
80#[derive(Debug, Serialize, Deserialize)]
81pub struct RemoteWriteQuery {
82    pub db: Option<String>,
83    /// Specify which physical table to use for storing metrics.
84    /// This only works on remote write requests.
85    pub physical_table: Option<String>,
86    /// For VictoriaMetrics modified remote write protocol
87    pub get_vm_proto_version: Option<String>,
88}
89
90impl Default for RemoteWriteQuery {
91    fn default() -> RemoteWriteQuery {
92        Self {
93            db: Some(DEFAULT_SCHEMA_NAME.to_string()),
94            physical_table: Some(GREPTIME_PHYSICAL_TABLE.to_string()),
95            get_vm_proto_version: None,
96        }
97    }
98}
99
100#[axum_macros::debug_handler]
101#[tracing::instrument(
102    skip_all,
103    fields(protocol = "prometheus", request_type = "remote_write")
104)]
105pub async fn remote_write(
106    State(state): State<PromStoreState>,
107    Query(params): Query<RemoteWriteQuery>,
108    Extension(query_ctx): Extension<QueryContext>,
109    content_type: Option<TypedHeader<headers::ContentType>>,
110    pipeline_info: PipelineInfo,
111    content_encoding: TypedHeader<headers::ContentEncoding>,
112    body: Bytes,
113) -> Result<axum::response::Response> {
114    let is_zstd = content_encoding.contains(VM_ENCODING);
115
116    match remote_write_proto(content_type) {
117        RemoteWriteProto::V1 => {
118            remote_write_v1(state, params, query_ctx, pipeline_info, is_zstd, body).await
119        }
120        RemoteWriteProto::V2 => {
121            if let Some(response) = unsupported_remote_write_v2_encoding_response(&content_encoding)
122            {
123                return Ok(response);
124            }
125            remote_write_v2(state, params, query_ctx, pipeline_info, is_zstd, body).await
126        }
127        RemoteWriteProto::Unsupported(content_type) => Ok((
128            StatusCode::UNSUPPORTED_MEDIA_TYPE,
129            format!("unsupported prometheus remote write content type: {content_type}"),
130        )
131            .into_response()),
132    }
133}
134
135async fn remote_write_v1(
136    state: PromStoreState,
137    params: RemoteWriteQuery,
138    query_ctx: QueryContext,
139    pipeline_info: PipelineInfo,
140    is_zstd: bool,
141    body: Bytes,
142) -> Result<axum::response::Response> {
143    let PromStoreState {
144        prom_store_handler,
145        pipeline_handler,
146        prom_store_with_metric_engine,
147        prom_validation_mode,
148        experimental_enable_prometheus_native_histogram: _,
149        pending_rows_batcher,
150    } = state;
151
152    if let Some(response) = vm_proto_version_response(&params) {
153        return Ok(response);
154    }
155
156    let (db, query_ctx, _timer) =
157        prepare_remote_write_context(&params, query_ctx, REMOTE_WRITE_V1_VERSION);
158
159    let mut processor = PromSeriesProcessor::default_processor();
160
161    if let Some(pipeline_name) = pipeline_info.pipeline_name {
162        let pipeline_def = PipelineDefinition::from_name(
163            &pipeline_name,
164            to_pipeline_version(pipeline_info.pipeline_version.as_deref())
165                .context(PipelineSnafu)?,
166            None,
167        )
168        .context(PipelineSnafu)?;
169        let pipeline_handler = pipeline_handler.context(InternalSnafu {
170            err_msg: "pipeline handler is not set".to_string(),
171        })?;
172
173        processor.set_pipeline(pipeline_handler, query_ctx.clone(), pipeline_def);
174    }
175
176    let mut req = decode_remote_write_request(is_zstd, body, prom_validation_mode, &mut processor)?;
177
178    let req = if processor.use_pipeline {
179        processor.exec_pipeline().await?
180    } else {
181        req.as_insert_requests()
182    };
183    let batches = into_prom_write_batches(req, query_ctx);
184
185    let outcome = match write_prometheus_rows_with_progress(
186        prom_store_handler,
187        pending_rows_batcher,
188        prom_store_with_metric_engine,
189        batches,
190    )
191    .await
192    {
193        Ok(outcome) => outcome,
194        Err(error) => {
195            record_remote_write_samples(&db, error.rows_written);
196            return Err(error.error);
197        }
198    };
199    record_remote_write_samples(&db, outcome.rows_written);
200
201    Ok((
202        StatusCode::NO_CONTENT,
203        write_cost_header_map(outcome.write_cost),
204    )
205        .into_response())
206}
207
208async fn remote_write_v2(
209    state: PromStoreState,
210    params: RemoteWriteQuery,
211    query_ctx: QueryContext,
212    pipeline_info: PipelineInfo,
213    is_zstd: bool,
214    body: Bytes,
215) -> Result<axum::response::Response> {
216    let PromStoreState {
217        prom_store_handler,
218        pipeline_handler: _,
219        prom_store_with_metric_engine,
220        prom_validation_mode: _,
221        experimental_enable_prometheus_native_histogram,
222        pending_rows_batcher,
223    } = state;
224
225    if let Some(response) = vm_proto_version_response(&params) {
226        return Ok(response);
227    }
228
229    // Pipeline processing is not supported for remote write v2 yet. Ignore the
230    // optional pipeline parameter and ingest samples directly.
231    let _ = pipeline_info;
232
233    let (db, query_ctx, _timer) =
234        prepare_remote_write_context(&params, query_ctx, REMOTE_WRITE_V2_VERSION);
235
236    let request = match decode_remote_write_v2_request(is_zstd, body) {
237        Ok(request) => request,
238        Err(error) => return Ok(remote_write_v2_error_response(error, 0, 0, 0)),
239    };
240    if !experimental_enable_prometheus_native_histogram && request_has_native_histograms(&request) {
241        return Ok(remote_write_v2_error_response(
242            error::InvalidPromRemoteRequestSnafu {
243                msg: "prometheus remote write v2 native histogram ingestion is experimental; set http.experimental_enable_prometheus_native_histogram = true to enable it"
244                    .to_string(),
245            }
246            .build(),
247            0,
248            0,
249            0,
250        ));
251    }
252    let req = match into_write_requests(request) {
253        Ok(req) => req,
254        Err(error) => return Ok(remote_write_v2_error_response(error, 0, 0, 0)),
255    };
256    let sample_count = req.sample_count;
257    let histogram_count = req.histogram_count;
258    let sample_batches = into_prom_write_batches(req.samples, query_ctx.clone());
259    let histogram_batches = into_prom_write_batches(req.histograms, query_ctx);
260    let outcome = match write_prometheus_v2_rows_with_progress(
261        prom_store_handler,
262        pending_rows_batcher,
263        prom_store_with_metric_engine,
264        sample_batches,
265        histogram_batches,
266    )
267    .await
268    {
269        Ok(outcome) => outcome,
270        Err(error) => {
271            record_remote_write_samples(&db, error.samples_written);
272            record_remote_write_histograms(&db, error.histograms_written);
273            return Ok(remote_write_v2_error_response(
274                error.error,
275                error.samples_written,
276                error.histograms_written,
277                0,
278            ));
279        }
280    };
281    debug_assert_eq!(outcome.samples_written, sample_count);
282    debug_assert_eq!(outcome.histograms_written, histogram_count);
283    record_remote_write_samples(&db, outcome.samples_written);
284    record_remote_write_histograms(&db, outcome.histograms_written);
285
286    let mut headers = write_cost_header_map(outcome.write_cost);
287    append_remote_write_v2_written_headers(
288        &mut headers,
289        outcome.samples_written,
290        outcome.histograms_written,
291        0,
292    );
293
294    Ok((StatusCode::NO_CONTENT, headers).into_response())
295}
296
297fn request_has_native_histograms(
298    request: &api::greptime_proto::io::prometheus::write::v2::Request,
299) -> bool {
300    request
301        .timeseries
302        .iter()
303        .any(|series| !series.histograms.is_empty())
304}
305
306fn vm_proto_version_response(params: &RemoteWriteQuery) -> Option<axum::response::Response> {
307    params
308        .get_vm_proto_version
309        .as_ref()
310        .map(|_| VM_PROTO_VERSION.into_response())
311}
312
313fn prepare_remote_write_context(
314    params: &RemoteWriteQuery,
315    mut query_ctx: QueryContext,
316    remote_write_version: &str,
317) -> (String, Arc<QueryContext>, HistogramTimer) {
318    let db = params.db.clone().unwrap_or_default();
319    query_ctx.set_channel(Channel::Prometheus);
320    let physical_table = params
321        .physical_table
322        .clone()
323        .unwrap_or_else(|| GREPTIME_PHYSICAL_TABLE.to_string());
324    query_ctx.set_extension(PHYSICAL_TABLE_PARAM, physical_table);
325    // Stamp the Prometheus metric identity here, before `as_req_iter` splits into the
326    // batched and direct write paths, so both inherit it (the batched path bypasses
327    // `PromStoreProtocolHandler::write`). Prometheus remote-write metadata is weak
328    // here, so the type is inferred from naming.
329    query_ctx.set_extension(SEMANTIC_SIGNAL_TYPE, SIGNAL_TYPE_METRIC);
330    query_ctx.set_extension(SEMANTIC_SOURCE, SOURCE_PROMETHEUS);
331    query_ctx.set_extension(SEMANTIC_SOURCE_VERSION, remote_write_version);
332    query_ctx.set_extension(SEMANTIC_METRIC_METADATA_QUALITY, METADATA_QUALITY_INFERRED);
333    let query_ctx = Arc::new(query_ctx);
334    let timer = crate::metrics::METRIC_HTTP_PROM_STORE_WRITE_ELAPSED
335        .with_label_values(&[db.as_str()])
336        .start_timer();
337
338    (db, query_ctx, timer)
339}
340
341struct PromWriteOutcome {
342    write_cost: usize,
343    rows_written: u64,
344}
345
346struct PromWriteError {
347    error: error::Error,
348    rows_written: u64,
349}
350
351struct PromWriteV2Outcome {
352    write_cost: usize,
353    samples_written: u64,
354    histograms_written: u64,
355}
356
357struct PromWriteV2Error {
358    error: error::Error,
359    samples_written: u64,
360    histograms_written: u64,
361}
362
363type PromWriteBatch = (QueryContextRef, RowInsertRequests);
364
365#[async_trait]
366trait PromWriteBatcher: Send + Sync {
367    async fn submit(&self, requests: RowInsertRequests, ctx: QueryContextRef) -> Result<u64>;
368}
369
370#[async_trait]
371impl PromWriteBatcher for PendingRowsBatcher {
372    async fn submit(&self, requests: RowInsertRequests, ctx: QueryContextRef) -> Result<u64> {
373        PendingRowsBatcher::submit(self, requests, ctx).await
374    }
375}
376
377fn into_prom_write_batches(req: ContextReq, query_ctx: QueryContextRef) -> Vec<PromWriteBatch> {
378    req.as_req_iter(query_ctx).collect()
379}
380
381async fn preflight_prometheus_rows(
382    prom_store_handler: &PromStoreProtocolHandlerRef,
383    batches: &mut [PromWriteBatch],
384) -> Result<()> {
385    for (ctx, reqs) in batches {
386        prom_store_handler.pre_write(reqs, ctx.clone()).await?;
387        // Detach from context clones retained by pre-write hooks so the checked
388        // schema cannot change before this prepared batch is written.
389        *ctx = Arc::new(ctx.fork());
390    }
391    Ok(())
392}
393
394/// Writes preflighted PRW batches and keeps the number of persisted rows on error.
395///
396/// The v2 handler uses that partial progress to return Prometheus' written
397/// sample/histogram headers even when a later table write fails.
398async fn write_prometheus_rows_with_progress(
399    prom_store_handler: PromStoreProtocolHandlerRef,
400    pending_rows_batcher: Option<Arc<PendingRowsBatcher>>,
401    prom_store_with_metric_engine: bool,
402    mut batches: Vec<PromWriteBatch>,
403) -> std::result::Result<PromWriteOutcome, PromWriteError> {
404    if prom_store_with_metric_engine && let Some(batcher) = pending_rows_batcher {
405        preflight_prometheus_rows(&prom_store_handler, &mut batches)
406            .await
407            .map_err(|error| PromWriteError {
408                error,
409                rows_written: 0,
410            })?;
411        let mut rows_written = 0;
412        for (temp_ctx, reqs) in batches {
413            let rows = batcher
414                .submit(reqs, temp_ctx)
415                .await
416                .map_err(|error| PromWriteError {
417                    error,
418                    rows_written,
419                })?;
420            rows_written += rows;
421        }
422        return Ok(PromWriteOutcome {
423            write_cost: 0,
424            rows_written,
425        });
426    }
427
428    let row_counts = batches
429        .iter()
430        .map(|(_, request)| prom_write_row_count(request))
431        .collect::<Vec<_>>();
432    let batch_count = batches.len();
433    let outputs = prom_store_handler
434        .write_all(batches, prom_store_with_metric_engine)
435        .await
436        .map_err(|error| PromWriteError {
437            error,
438            rows_written: 0,
439        })?;
440    let output_count = outputs.len();
441    let mut write_cost = 0;
442    let mut rows_written = 0;
443    for (output, rows) in outputs.into_iter().zip(row_counts) {
444        let output = output.map_err(|error| PromWriteError {
445            error,
446            rows_written,
447        })?;
448        write_cost += output.meta.cost;
449        rows_written += rows;
450    }
451    if output_count != batch_count {
452        return Err(PromWriteError {
453            error: incomplete_prom_write_error(),
454            rows_written,
455        });
456    }
457
458    Ok(PromWriteOutcome {
459        write_cost,
460        rows_written,
461    })
462}
463
464async fn write_prometheus_v2_rows_with_progress(
465    prom_store_handler: PromStoreProtocolHandlerRef,
466    pending_rows_batcher: Option<Arc<PendingRowsBatcher>>,
467    prom_store_with_metric_engine: bool,
468    sample_batches: Vec<PromWriteBatch>,
469    histogram_batches: Vec<PromWriteBatch>,
470) -> std::result::Result<PromWriteV2Outcome, PromWriteV2Error> {
471    if histogram_batches.is_empty() {
472        return write_prometheus_rows_with_progress(
473            prom_store_handler,
474            pending_rows_batcher,
475            prom_store_with_metric_engine,
476            sample_batches,
477        )
478        .await
479        .map(|outcome| PromWriteV2Outcome {
480            write_cost: outcome.write_cost,
481            samples_written: outcome.rows_written,
482            histograms_written: 0,
483        })
484        .map_err(|error| PromWriteV2Error {
485            error: error.error,
486            samples_written: error.rows_written,
487            histograms_written: 0,
488        });
489    }
490
491    if prom_store_with_metric_engine && let Some(batcher) = pending_rows_batcher {
492        return write_batched_prometheus_v2_rows_with_progress(
493            prom_store_handler,
494            batcher.as_ref(),
495            prom_store_with_metric_engine,
496            sample_batches,
497            histogram_batches,
498        )
499        .await;
500    }
501
502    let sample_batch_count = sample_batches.len();
503    let mut batches = sample_batches;
504    batches.extend(histogram_batches);
505    let row_counts = batches
506        .iter()
507        .map(|(_, request)| prom_write_row_count(request))
508        .collect::<Vec<_>>();
509    let batch_count = batches.len();
510    let outputs = prom_store_handler
511        .write_all(batches, prom_store_with_metric_engine)
512        .await
513        .map_err(|error| PromWriteV2Error {
514            error,
515            samples_written: 0,
516            histograms_written: 0,
517        })?;
518
519    let mut write_cost = 0;
520    let mut samples_written = 0;
521    let mut histograms_written = 0;
522    let mut output_count = 0;
523    for (index, (output, rows)) in outputs.into_iter().zip(row_counts).enumerate() {
524        let output = output.map_err(|error| PromWriteV2Error {
525            error,
526            samples_written,
527            histograms_written,
528        })?;
529        write_cost += output.meta.cost;
530        if index < sample_batch_count {
531            samples_written += rows;
532        } else {
533            histograms_written += rows;
534        }
535        output_count += 1;
536    }
537    if output_count != batch_count {
538        return Err(PromWriteV2Error {
539            error: incomplete_prom_write_error(),
540            samples_written,
541            histograms_written,
542        });
543    }
544
545    Ok(PromWriteV2Outcome {
546        write_cost,
547        samples_written,
548        histograms_written,
549    })
550}
551
552async fn write_batched_prometheus_v2_rows_with_progress<B: PromWriteBatcher + ?Sized>(
553    prom_store_handler: PromStoreProtocolHandlerRef,
554    batcher: &B,
555    prom_store_with_metric_engine: bool,
556    sample_batches: Vec<PromWriteBatch>,
557    histogram_batches: Vec<PromWriteBatch>,
558) -> std::result::Result<PromWriteV2Outcome, PromWriteV2Error> {
559    let sample_batch_count = sample_batches.len();
560    let mut batches = sample_batches;
561    batches.extend(histogram_batches);
562    preflight_prometheus_rows(&prom_store_handler, &mut batches)
563        .await
564        .map_err(|error| PromWriteV2Error {
565            error,
566            samples_written: 0,
567            histograms_written: 0,
568        })?;
569
570    let mut samples_written = 0;
571    let mut histograms_written = 0;
572    let mut write_cost = 0;
573    let mut batches = batches.into_iter();
574    for (ctx, requests) in batches.by_ref().take(sample_batch_count) {
575        let rows = batcher
576            .submit(requests, ctx)
577            .await
578            .map_err(|error| PromWriteV2Error {
579                error,
580                samples_written,
581                histograms_written,
582            })?;
583        samples_written += rows;
584    }
585    for (ctx, requests) in batches {
586        let rows = prom_write_row_count(&requests);
587        let output = prom_store_handler
588            .write_prepared(requests, ctx, prom_store_with_metric_engine)
589            .await
590            .map_err(|error| PromWriteV2Error {
591                error,
592                samples_written,
593                histograms_written,
594            })?;
595        write_cost += output.meta.cost;
596        histograms_written += rows;
597    }
598
599    Ok(PromWriteV2Outcome {
600        write_cost,
601        samples_written,
602        histograms_written,
603    })
604}
605
606fn prom_write_row_count(request: &RowInsertRequests) -> u64 {
607    request
608        .inserts
609        .iter()
610        .filter_map(|insert| insert.rows.as_ref().map(|rows| rows.rows.len() as u64))
611        .sum()
612}
613
614fn incomplete_prom_write_error() -> error::Error {
615    InternalSnafu {
616        err_msg: "prometheus write handler returned before processing every batch".to_string(),
617    }
618    .build()
619}
620
621fn record_remote_write_samples(db: &str, rows: u64) {
622    if rows == 0 {
623        return;
624    }
625    crate::metrics::PROM_STORE_REMOTE_WRITE_SAMPLES
626        .with_label_values(&[db])
627        .inc_by(rows);
628}
629
630fn record_remote_write_histograms(db: &str, rows: u64) {
631    if rows == 0 {
632        return;
633    }
634    crate::metrics::PROM_STORE_REMOTE_WRITE_HISTOGRAMS
635        .with_label_values(&[db])
636        .inc_by(rows);
637}
638
639fn remote_write_v2_error_response(
640    error: error::Error,
641    samples: u64,
642    histograms: u64,
643    exemplars: u64,
644) -> axum::response::Response {
645    let mut response = error.into_response();
646    append_remote_write_v2_written_headers(response.headers_mut(), samples, histograms, exemplars);
647    response
648}
649
650fn append_remote_write_v2_written_headers(
651    headers: &mut HeaderMap,
652    samples: u64,
653    histograms: u64,
654    exemplars: u64,
655) {
656    headers.insert(
657        REMOTE_WRITE_V2_SAMPLES_WRITTEN_HEADER,
658        HeaderValue::from_str(&samples.to_string()).expect("u64 header value is valid"),
659    );
660    headers.insert(
661        REMOTE_WRITE_V2_HISTOGRAMS_WRITTEN_HEADER,
662        HeaderValue::from_str(&histograms.to_string()).expect("u64 header value is valid"),
663    );
664    headers.insert(
665        REMOTE_WRITE_V2_EXEMPLARS_WRITTEN_HEADER,
666        HeaderValue::from_str(&exemplars.to_string()).expect("u64 header value is valid"),
667    );
668}
669
670enum RemoteWriteProto {
671    V1,
672    V2,
673    Unsupported(mime::Mime),
674}
675
676// ref: https://github.com/prometheus/client_golang/blob/74560058a7af7a695db8196c8e84a0754032c6af/exp/api/remote/remote_api.go#L544
677fn remote_write_proto(content_type: Option<TypedHeader<headers::ContentType>>) -> RemoteWriteProto {
678    let Some(TypedHeader(content_type)) = content_type else {
679        return RemoteWriteProto::V1;
680    };
681
682    let mime_type: mime::Mime = content_type.into();
683    if !mime_type
684        .essence_str()
685        .eq_ignore_ascii_case(CONTENT_TYPE_PROTOBUF_STR)
686    {
687        return RemoteWriteProto::Unsupported(mime_type);
688    }
689
690    for (name, value) in mime_type.params() {
691        if !name.as_str().eq_ignore_ascii_case(CONTENT_TYPE_PROTO_PARAM) {
692            continue;
693        }
694
695        return match value.as_str() {
696            REMOTE_WRITE_V1_PROTO => RemoteWriteProto::V1,
697            REMOTE_WRITE_V2_PROTO => RemoteWriteProto::V2,
698            _ => RemoteWriteProto::Unsupported(mime_type.clone()),
699        };
700    }
701
702    RemoteWriteProto::V1
703}
704
705fn unsupported_remote_write_v2_encoding_response(
706    content_encoding: &headers::ContentEncoding,
707) -> Option<axum::response::Response> {
708    if content_encoding.contains(DEFAULT_ENCODING) || content_encoding.contains(VM_ENCODING) {
709        return None;
710    }
711
712    Some((
713        StatusCode::UNSUPPORTED_MEDIA_TYPE,
714        format!(
715            "unsupported prometheus remote write content encoding: only {DEFAULT_ENCODING} and {VM_ENCODING} are supported"
716        ),
717    )
718        .into_response())
719}
720
721impl IntoResponse for PromStoreResponse {
722    fn into_response(self) -> axum::response::Response {
723        let mut header_map = HeaderMap::new();
724        header_map.insert(&header::CONTENT_TYPE, self.content_type);
725        header_map.insert(&header::CONTENT_ENCODING, self.content_encoding);
726
727        let metrics = if self.resp_metrics.is_empty() {
728            None
729        } else {
730            serde_json::to_string(&self.resp_metrics).ok()
731        };
732        if let Some(m) = metrics.and_then(|m| HeaderValue::from_str(&m).ok()) {
733            header_map.insert(&GREPTIME_DB_HEADER_METRICS, m);
734        }
735
736        (header_map, self.body).into_response()
737    }
738}
739
740#[axum_macros::debug_handler]
741#[tracing::instrument(
742    skip_all,
743    fields(protocol = "prometheus", request_type = "remote_read")
744)]
745pub async fn remote_read(
746    State(state): State<PromStoreState>,
747    Query(params): Query<RemoteWriteQuery>,
748    Extension(mut query_ctx): Extension<QueryContext>,
749    body: Bytes,
750) -> Result<PromStoreResponse> {
751    let db = params.db.clone().unwrap_or_default();
752    query_ctx.set_channel(Channel::Prometheus);
753
754    let request = decode_remote_read_request(body).await?;
755
756    let query_ctx = Arc::new(query_ctx);
757    let _timer = crate::metrics::METRIC_HTTP_PROM_STORE_READ_ELAPSED
758        .with_label_values(&[db.as_str()])
759        .start_timer();
760
761    state.prom_store_handler.read(request, query_ctx).await
762}
763
764async fn decode_remote_read_request(body: Bytes) -> Result<ReadRequest> {
765    let buf = snappy_decompress(&body[..])?;
766
767    ReadRequest::decode(&buf[..]).context(error::DecodePromRemoteRequestSnafu)
768}
769
770#[cfg(test)]
771mod tests {
772    use std::sync::Mutex;
773
774    use api::prom_store::remote::ReadRequest;
775    use api::v1::{Row, RowInsertRequest, Rows};
776    use async_trait::async_trait;
777    use common_query::Output;
778    use pipeline::GreptimePipelineParams;
779    use session::context::{QueryContext, QueryContextRef};
780
781    use super::*;
782    use crate::prom_remote_write::validation::PromValidationMode;
783    use crate::prom_store::Metrics;
784    use crate::query_handler::PromStoreProtocolHandler;
785
786    #[test]
787    fn test_remote_write_proto() {
788        assert!(matches!(
789            remote_write_proto(content_type(
790                "application/x-protobuf;proto=io.prometheus.write.v2.Request"
791            )),
792            RemoteWriteProto::V2
793        ));
794        assert!(matches!(
795            remote_write_proto(content_type(
796                "application/x-protobuf; proto=\"io.prometheus.write.v2.Request\""
797            )),
798            RemoteWriteProto::V2
799        ));
800        assert!(matches!(
801            remote_write_proto(content_type(
802                "APPLICATION/X-PROTOBUF;proto=io.prometheus.write.v2.Request"
803            )),
804            RemoteWriteProto::V2
805        ));
806        assert!(matches!(
807            remote_write_proto(content_type("application/x-protobuf")),
808            RemoteWriteProto::V1
809        ));
810        assert!(matches!(
811            remote_write_proto(content_type(
812                "application/x-protobuf;proto=prometheus.WriteRequest"
813            )),
814            RemoteWriteProto::V1
815        ));
816        assert!(matches!(
817            remote_write_proto(content_type(
818                "application/x-protobuf;proto=unknown.WriteRequest"
819            )),
820            RemoteWriteProto::Unsupported(_)
821        ));
822        assert!(matches!(
823            remote_write_proto(content_type(
824                "application/json;proto=io.prometheus.write.v2.Request"
825            )),
826            RemoteWriteProto::Unsupported(_)
827        ));
828        assert!(matches!(remote_write_proto(None), RemoteWriteProto::V1));
829    }
830
831    fn content_type(value: &str) -> Option<TypedHeader<headers::ContentType>> {
832        Some(TypedHeader(std::str::FromStr::from_str(value).unwrap()))
833    }
834
835    #[test]
836    fn test_prepare_remote_write_context_stamps_semantics() {
837        let (_, query_ctx, _timer) = prepare_remote_write_context(
838            &RemoteWriteQuery::default(),
839            QueryContext::with("greptime", "public"),
840            REMOTE_WRITE_V2_VERSION,
841        );
842
843        assert_eq!(
844            query_ctx.extension(SEMANTIC_SIGNAL_TYPE),
845            Some(SIGNAL_TYPE_METRIC)
846        );
847        assert_eq!(
848            query_ctx.extension(SEMANTIC_SOURCE),
849            Some(SOURCE_PROMETHEUS)
850        );
851        assert_eq!(
852            query_ctx.extension(SEMANTIC_SOURCE_VERSION),
853            Some(REMOTE_WRITE_V2_VERSION)
854        );
855        assert_eq!(
856            query_ctx.extension(SEMANTIC_METRIC_METADATA_QUALITY),
857            Some(METADATA_QUALITY_INFERRED)
858        );
859    }
860
861    #[tokio::test]
862    async fn test_mixed_v2_preflights_all_then_batches_only_samples() {
863        let events = Arc::new(Mutex::new(Vec::new()));
864        let handler: PromStoreProtocolHandlerRef = Arc::new(RecordingPromStoreHandler {
865            events: events.clone(),
866        });
867        let batcher = RecordingPromWriteBatcher {
868            events: events.clone(),
869        };
870
871        let Ok(outcome) = write_batched_prometheus_v2_rows_with_progress(
872            handler,
873            &batcher,
874            true,
875            vec![test_prom_write_batch("sample")],
876            vec![test_prom_write_batch("histogram")],
877        )
878        .await
879        else {
880            panic!("mixed remote write should succeed")
881        };
882
883        assert_eq!(1, outcome.samples_written);
884        assert_eq!(1, outcome.histograms_written);
885        assert_eq!(
886            vec![
887                "pre:sample".to_string(),
888                "pre:histogram".to_string(),
889                "batch:sample".to_string(),
890                "direct:histogram".to_string(),
891            ],
892            *events.lock().unwrap()
893        );
894    }
895
896    fn test_prom_write_batch(table_name: &str) -> PromWriteBatch {
897        (
898            Arc::new(QueryContext::with("greptime", "public")),
899            RowInsertRequests {
900                inserts: vec![RowInsertRequest {
901                    table_name: table_name.to_string(),
902                    rows: Some(Rows {
903                        schema: Vec::new(),
904                        rows: vec![Row { values: Vec::new() }],
905                    }),
906                }],
907            },
908        )
909    }
910
911    fn record_write_event(events: &Mutex<Vec<String>>, phase: &str, request: &RowInsertRequests) {
912        events.lock().unwrap().push(format!(
913            "{phase}:{}",
914            request.inserts.first().unwrap().table_name
915        ));
916    }
917
918    struct RecordingPromWriteBatcher {
919        events: Arc<Mutex<Vec<String>>>,
920    }
921
922    #[async_trait]
923    impl PromWriteBatcher for RecordingPromWriteBatcher {
924        async fn submit(&self, requests: RowInsertRequests, _ctx: QueryContextRef) -> Result<u64> {
925            record_write_event(&self.events, "batch", &requests);
926            Ok(prom_write_row_count(&requests))
927        }
928    }
929
930    struct RecordingPromStoreHandler {
931        events: Arc<Mutex<Vec<String>>>,
932    }
933
934    #[async_trait]
935    impl PromStoreProtocolHandler for RecordingPromStoreHandler {
936        async fn pre_write(
937            &self,
938            request: &RowInsertRequests,
939            _ctx: QueryContextRef,
940        ) -> Result<()> {
941            record_write_event(&self.events, "pre", request);
942            Ok(())
943        }
944
945        async fn write_prepared(
946            &self,
947            request: RowInsertRequests,
948            _ctx: QueryContextRef,
949            _with_metric_engine: bool,
950        ) -> Result<Output> {
951            record_write_event(&self.events, "direct", &request);
952            Ok(Output::new_with_affected_rows(0))
953        }
954
955        async fn write(
956            &self,
957            _request: RowInsertRequests,
958            _ctx: QueryContextRef,
959            _with_metric_engine: bool,
960        ) -> Result<Output> {
961            unreachable!("mixed v2 writes use preflighted execution")
962        }
963
964        async fn write_all(
965            &self,
966            _requests: Vec<(QueryContextRef, RowInsertRequests)>,
967            _with_metric_engine: bool,
968        ) -> Result<Vec<Result<Output>>> {
969            unreachable!("mixed v2 writes preserve sample and histogram routing")
970        }
971
972        async fn read(
973            &self,
974            _request: ReadRequest,
975            _ctx: QueryContextRef,
976        ) -> Result<PromStoreResponse> {
977            unimplemented!()
978        }
979
980        async fn ingest_metrics(&self, _metrics: Metrics) -> Result<()> {
981            unimplemented!()
982        }
983    }
984
985    #[tokio::test]
986    async fn test_remote_write_v2_ignores_pipeline() {
987        let request = api::greptime_proto::io::prometheus::write::v2::Request {
988            symbols: vec![String::new()],
989            timeseries: Vec::new(),
990        };
991        let body =
992            Bytes::from(crate::prom_store::snappy_compress(&request.encode_to_vec()).unwrap());
993
994        let response = remote_write_v2(
995            test_state(),
996            RemoteWriteQuery::default(),
997            QueryContext::with("greptime", "public"),
998            pipeline_info(Some("pipeline")),
999            false,
1000            body,
1001        )
1002        .await
1003        .unwrap();
1004
1005        assert_eq!(response.status(), StatusCode::NO_CONTENT);
1006        assert_eq!(
1007            Some("0"),
1008            response
1009                .headers()
1010                .get(REMOTE_WRITE_V2_SAMPLES_WRITTEN_HEADER)
1011                .map(|x| x.to_str().unwrap())
1012        );
1013    }
1014
1015    fn test_state() -> PromStoreState {
1016        PromStoreState {
1017            prom_store_handler: Arc::new(NoopPromStoreHandler),
1018            pipeline_handler: None,
1019            prom_store_with_metric_engine: false,
1020            prom_validation_mode: PromValidationMode::Strict,
1021            experimental_enable_prometheus_native_histogram: false,
1022            pending_rows_batcher: None,
1023        }
1024    }
1025
1026    fn pipeline_info(pipeline_name: Option<&str>) -> PipelineInfo {
1027        PipelineInfo {
1028            pipeline_name: pipeline_name.map(ToString::to_string),
1029            pipeline_version: None,
1030            pipeline_params: GreptimePipelineParams::default(),
1031        }
1032    }
1033
1034    struct NoopPromStoreHandler;
1035
1036    #[async_trait]
1037    impl PromStoreProtocolHandler for NoopPromStoreHandler {
1038        async fn pre_write(
1039            &self,
1040            _request: &RowInsertRequests,
1041            _ctx: QueryContextRef,
1042        ) -> Result<()> {
1043            Ok(())
1044        }
1045
1046        async fn write_prepared(
1047            &self,
1048            _request: RowInsertRequests,
1049            _ctx: QueryContextRef,
1050            _with_metric_engine: bool,
1051        ) -> Result<Output> {
1052            unreachable!("empty remote write v2 request should not write")
1053        }
1054
1055        async fn write(
1056            &self,
1057            _request: RowInsertRequests,
1058            _ctx: QueryContextRef,
1059            _with_metric_engine: bool,
1060        ) -> Result<Output> {
1061            unreachable!("empty remote write v2 request should not write")
1062        }
1063
1064        async fn write_all(
1065            &self,
1066            requests: Vec<(QueryContextRef, RowInsertRequests)>,
1067            _with_metric_engine: bool,
1068        ) -> Result<Vec<Result<Output>>> {
1069            assert!(requests.is_empty());
1070            Ok(Vec::new())
1071        }
1072
1073        async fn read(
1074            &self,
1075            _request: ReadRequest,
1076            _ctx: QueryContextRef,
1077        ) -> Result<PromStoreResponse> {
1078            unimplemented!()
1079        }
1080
1081        async fn ingest_metrics(&self, _metrics: Metrics) -> Result<()> {
1082            unimplemented!()
1083        }
1084    }
1085}