From 142f1d4b7c9bf632cd05b4e225e86739204274e1 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 18 Sep 2026 18:04:04 +0000 Subject: [PATCH] feat(serverless): expose CollectionsService response time Sync serverless collections proto with the public-api `time` field and surface it on create/delete/get/list results. --- examples/serverless.rs | 2 +- proto/serverless_collections.proto | 8 ++++++++ src/serverless/client.rs | 29 +++++++++++++++++---------- src/serverless/grpc.rs | 16 +++++++++++++-- src/serverless/mod.rs | 10 +++++----- src/serverless/models.rs | 32 ++++++++++++++++++++++++++++-- 6 files changed, 76 insertions(+), 21 deletions(-) diff --git a/examples/serverless.rs b/examples/serverless.rs index 6621317..c89a314 100644 --- a/examples/serverless.rs +++ b/examples/serverless.rs @@ -42,7 +42,7 @@ async fn main() -> Result<(), QdrantError> { .payload_index("color", KeywordIndex::new()), ) .await?; - println!("create_collection: {result}"); + println!("create_collection: {result:?}"); println!( "collections: {:?}", diff --git a/proto/serverless_collections.proto b/proto/serverless_collections.proto index a864b8d..3e09d11 100644 --- a/proto/serverless_collections.proto +++ b/proto/serverless_collections.proto @@ -224,6 +224,8 @@ message CreateCollectionResponse { string collection_name = 1; // Outcome, e.g. "created", "already exists". string result = 2; + // Time spent to process + double time = 3; } // Names the collection to delete. @@ -238,6 +240,8 @@ message DeleteCollectionResponse { bool deleted = 1; // Number of storage objects removed. uint32 objects_deleted = 2; + // Time spent to process + double time = 3; } // Names the collection to fetch. @@ -255,6 +259,8 @@ message GetCollectionResponse { // Available points as of the last applied write (eventually consistent); // absent until the updater has written stats for the collection. optional uint64 point_count = 3; + // Time spent to process + double time = 4; } // Lists the caller's collections. The tenant travels in metadata. @@ -283,4 +289,6 @@ message ListCollectionsResponse { // Opaque token to pass as `offset_token` to retrieve the next page. Absent // when there are no more results. optional string next_offset_token = 2; + // Time spent to process + double time = 3; } diff --git a/src/serverless/client.rs b/src/serverless/client.rs index ca5bcf2..c3bc9e4 100644 --- a/src/serverless/client.rs +++ b/src/serverless/client.rs @@ -31,7 +31,8 @@ use crate::serverless::grpc::{ CreateCollectionRequest, DeleteCollectionRequest, GetCollectionRequest, ListCollectionsRequest, }; use crate::serverless::models::{ - CollectionConfig, CollectionInfo, CollectionSummary, CollectionsList, ListCollections, + CollectionConfig, CollectionInfo, CollectionSummary, CollectionsList, CreateCollectionResult, + DeleteCollectionResult, ListCollections, }; use crate::Qdrant; @@ -217,39 +218,43 @@ impl QdrantServerless { /// At least one dense or sparse vector is required. Unlike the regular client, /// no storage internals (quantization, WAL, segment number, ...) can be /// configured: the serverless manager decides those. - /// - /// Returns the outcome string from the service (e.g. `"created"`). pub async fn create_collection( &self, collection_name: impl Into, config: CollectionConfig, - ) -> QdrantResult { + ) -> QdrantResult { let request = CreateCollectionRequest { collection_name: collection_name.into(), config: Some(collection_config_to_grpc(&config)), }; let request = &request; self.with_collections_client(|mut api| async move { - let response = api.create_collection(request.clone()).await?; - Ok(response.into_inner().result) + let response = api.create_collection(request.clone()).await?.into_inner(); + Ok(CreateCollectionResult { + collection_name: response.collection_name, + result: response.result, + time: response.time, + }) }) .await } /// Deletes a collection and all of its data. - /// - /// Returns `true` if the collection existed and was deleted, `false` otherwise. pub async fn delete_collection( &self, collection_name: impl Into, - ) -> QdrantResult { + ) -> QdrantResult { let request = DeleteCollectionRequest { collection_name: collection_name.into(), }; let request = &request; self.with_collections_client(|mut api| async move { - let response = api.delete_collection(request.clone()).await?; - Ok(response.into_inner().deleted) + let response = api.delete_collection(request.clone()).await?.into_inner(); + Ok(DeleteCollectionResult { + deleted: response.deleted, + objects_deleted: response.objects_deleted, + time: response.time, + }) }) .await } @@ -279,6 +284,7 @@ impl QdrantServerless { .map(collection_config_from_grpc) .transpose()?, point_count: response.point_count, + time: response.time, }) } @@ -334,6 +340,7 @@ impl QdrantServerless { }) .collect(), next_offset_token: response.next_offset_token, + time: response.time, }) }) .await diff --git a/src/serverless/grpc.rs b/src/serverless/grpc.rs index ef58d04..8a4e066 100644 --- a/src/serverless/grpc.rs +++ b/src/serverless/grpc.rs @@ -213,7 +213,7 @@ pub struct CreateCollectionRequest { pub config: ::core::option::Option, } /// Result of a create. -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateCollectionResponse { /// Tenant-facing name of the collection. #[prost(string, tag = "1")] @@ -221,6 +221,9 @@ pub struct CreateCollectionResponse { /// Outcome, e.g. "created", "already exists". #[prost(string, tag = "2")] pub result: ::prost::alloc::string::String, + /// Time spent to process + #[prost(double, tag = "3")] + pub time: f64, } /// Names the collection to delete. #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] @@ -230,7 +233,7 @@ pub struct DeleteCollectionRequest { pub collection_name: ::prost::alloc::string::String, } /// Result of a delete. -#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct DeleteCollectionResponse { /// Whether the collection existed and was deleted. #[prost(bool, tag = "1")] @@ -238,6 +241,9 @@ pub struct DeleteCollectionResponse { /// Number of storage objects removed. #[prost(uint32, tag = "2")] pub objects_deleted: u32, + /// Time spent to process + #[prost(double, tag = "3")] + pub time: f64, } /// Names the collection to fetch. #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] @@ -259,6 +265,9 @@ pub struct GetCollectionResponse { /// absent until the updater has written stats for the collection. #[prost(uint64, optional, tag = "3")] pub point_count: ::core::option::Option, + /// Time spent to process + #[prost(double, tag = "4")] + pub time: f64, } /// Lists the caller's collections. The tenant travels in metadata. #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] @@ -293,6 +302,9 @@ pub struct ListCollectionsResponse { /// when there are no more results. #[prost(string, optional, tag = "2")] pub next_offset_token: ::core::option::Option<::prost::alloc::string::String>, + /// Time spent to process + #[prost(double, tag = "3")] + pub time: f64, } /// Distance metric used to compare dense vectors. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] diff --git a/src/serverless/mod.rs b/src/serverless/mod.rs index 4814d9d..c0acc5b 100644 --- a/src/serverless/mod.rs +++ b/src/serverless/mod.rs @@ -38,9 +38,9 @@ pub mod models; pub use client::{QdrantServerless, QdrantServerlessBuilder, DEFAULT_SERVERLESS_GRPC_PORT}; pub use models::{ - BoolIndex, CollectionConfig, CollectionInfo, CollectionSummary, CollectionsList, DatetimeIndex, - DenseVectorConfig, Distance, FloatIndex, GeoIndex, IntegerIndex, KeywordIndex, - KeywordPrefixParams, ListCollections, ListCollectionsBuilder, PayloadIndex, PrecisionTier, - SnowballParams, SparseVectorConfig, StemmingAlgorithm, StopwordsSet, TextIndex, Tokenizer, - UuidIndex, + BoolIndex, CollectionConfig, CollectionInfo, CollectionSummary, CollectionsList, + CreateCollectionResult, DatetimeIndex, DeleteCollectionResult, DenseVectorConfig, Distance, + FloatIndex, GeoIndex, IntegerIndex, KeywordIndex, KeywordPrefixParams, ListCollections, + ListCollectionsBuilder, PayloadIndex, PrecisionTier, SnowballParams, SparseVectorConfig, + StemmingAlgorithm, StopwordsSet, TextIndex, Tokenizer, UuidIndex, }; diff --git a/src/serverless/models.rs b/src/serverless/models.rs index c4844d6..fd53bc0 100644 --- a/src/serverless/models.rs +++ b/src/serverless/models.rs @@ -422,16 +422,42 @@ impl CollectionConfig { } } +/// Result of [`super::QdrantServerless::create_collection`]. +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct CreateCollectionResult { + /// Tenant-facing name of the collection. + pub collection_name: String, + /// Outcome, e.g. `"created"`. + pub result: String, + /// Time spent to process the request, in seconds. + pub time: f64, +} + +/// Result of [`super::QdrantServerless::delete_collection`]. +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct DeleteCollectionResult { + /// Whether the collection existed and was deleted. + pub deleted: bool, + /// Number of storage objects removed. + pub objects_deleted: u32, + /// Time spent to process the request, in seconds. + pub time: f64, +} + /// A collection's configuration and stats, as returned by [`super::QdrantServerless::get_collection`]. /// /// `point_count` is eventually consistent and absent until stats have been /// written for the collection. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct CollectionInfo { pub exists: bool, pub config: Option, pub point_count: Option, + /// Time spent to process the request, in seconds. + pub time: f64, } /// Request for [`super::QdrantServerless::list_collections`]. @@ -504,10 +530,12 @@ pub struct CollectionSummary { } /// A page of collections returned by [`super::QdrantServerless::list_collections`]. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct CollectionsList { pub collections: Vec, /// Opaque token to pass as `offset_token` for the next page. Absent when done. pub next_offset_token: Option, + /// Time spent to process the request, in seconds. + pub time: f64, }