-
Notifications
You must be signed in to change notification settings - Fork 3
Set up data plane #20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really solid work here! I left some comments for consideration, specifically around dealing with the Index host and how we're generating code.
I think we can consider a lot of this as follow up PRs / work and keep this PR focused on getting the data plane established like you've done so far. Another pass by @haruska and @ssmith-pc would be appreciated if possible. 🙏
pinecone_sdk/src/pinecone/data.rs
Outdated
let index_host = self.describe_index(name).await?.host; | ||
// prepend with "https://" if not already present | ||
let index_host = if index_host.starts_with("https://") { | ||
index_host | ||
} else { | ||
format!("https://{}", index_host) | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe leaving off the schema and applying the :443
port is how we can specify https, right? At least, we're doing that in a couple other clients rather than using a schema for gRPC. We even strip out the https
if it's present in a few cases.
- Python - https://github.com/pinecone-io/pinecone-python-client/blob/8b7e7de50e72248de683a24ada024cf18e6cdce3/pinecone/grpc/base.py#L87-#L89
- Go - https://github.com/pinecone-io/go-pinecone/blob/a9af78e2e83f4d3a2506314de7519b483dbd426e/pinecone/index_connection.go#L39
Would it make sense to just do the same thing here?
Also @haruska made changes to the Python SDK recently to support allowing users to provide their own port if necessary: pinecone-io/pinecone-python-client#362
@emily-emily had mentioned it sounds like tonic complains if there's not a schema applied, which seems odd. 🤔
@@ -0,0 +1,404 @@ | |||
syntax = "proto3"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This may be a question for @haruska. How would we approach moving the proto file itself out of the codebase in the future?
From what I understand, the Rust code is generated from protos as a part of cargo build
, so we're not checking the generated code into source like we do with the other clients. Would there be some way to do that with Rust rather than having it done as part of cargo build
? Is it preferable to leave it as-is?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm hoping we publish proto files and this becomes a non-issue.
We could also have a git submodule to the APIs repo. This is not ideal as the project will not build except for our own GH actions and for folks with access to that private repo.
Another option is to move the tonic_build process to its own binary and only run it on an updated proto file. There is an option to set the codegen output folder. We could simply check in the generated code similar to other SDKs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We currently do have a submodule to the APIs repo that we use for the OpenAPI code, but since we build protos through cargo build
we need access in the CI. We weren't sure how to authenticate for it other than including somebody's personal access token.
Checking in the generated code could work, should we experiment with that in a different PR or try to fix it here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think experimenting with checking in the generated output in a future PR would be fine, you could create a ticket to keep tabs on that as well. 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice work on this, I really like this approach for allowing configuration of the index connection. Overall I think we're ready to move forward from here, great work again getting this all established.
pinecone_sdk/src/pinecone/data.rs
Outdated
namespace: namespace.unwrap_or_default(), | ||
}; | ||
|
||
let response = match self.connection.upsert(request).await { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to use .map_err
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agree, also not sure we need the clone?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
get_ref()
returns a reference to the inner object; if I understand correctly, this won't work as the original variable with ownership of the whole thing goes out of scope at the end of this function and our returned reference won't be valid anymore. Are there performance concerns with the clone?
pinecone_sdk/src/pinecone/data.rs
Outdated
pub async fn index( | ||
&self, | ||
host: &str, | ||
secure: Option<bool>, | ||
port: Option<u32>, | ||
) -> Result<Index, PineconeError> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function may be a good candidate for some unit test coverage to validate all the different permutations of manipulating the host if we don't have any.
Could be done as a separate piece of work in a follow up.
pinecone_sdk/src/pinecone/data.rs
Outdated
let endpoint = if endpoint.contains(":") { | ||
endpoint.to_string() | ||
} else { | ||
format!("{}:{}", endpoint, port) | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on previous, won't endpoint always contain a :
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe endpoint isn't the right name, is there a different name that would communicate it better? This is supposed to store a working copy of something between the host and the endpoint where we add the scheme or port if they aren't provided. The final endpoint should always contain a :
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just mean that the end result of running this:
let endpoint = if endpoint.contains("://") {
endpoint.to_string()
} else {
let scheme = if secure { "https" } else { "http" };
format!("{}://{}", scheme, endpoint)
};
will result in endpoint
definitely having a :
in it, because it's ensuring that there is a scheme with ://
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh I see, thanks for clarifying. I'm thinking of checking for two colons, since after adding the scheme we know there will be at least 1 colon; if the port was specified, there would now be 2 of them. Does that make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic makes sense, but it doesn't really "feel" like the right implementation to me. Using ends_with
would be an improvement: https://doc.rust-lang.org/std/primitive.str.html#method.ends_with
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and starts_with would probably be better in the other case
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added some code to test these with regex ("^[a-zA-Z]+://" and ":\d+$")
pinecone_sdk/src/pinecone/data.rs
Outdated
impl PineconeClient { | ||
/// Target an index for data operations. | ||
/// | ||
/// ### Arguments | ||
/// * `host: &str` - The host of the index to target. | ||
/// * `secure: Option<bool>` - Whether to use a secure connection. If not provided, defaults to `true`. | ||
/// * `port: Option<u32>` - The port of the index to target. If not provided, defaults to `443`. | ||
/// | ||
/// ### Return | ||
/// * `Result<Index, PineconeError>` - A Pinecone index object. | ||
/// | ||
/// ### Example | ||
/// | ||
/// ```no_run | ||
/// use pinecone_sdk::pinecone::PineconeClient; | ||
/// # use pinecone_sdk::utils::errors::PineconeError; | ||
/// | ||
/// # #[tokio::main] | ||
/// # async fn main() -> Result<(), PineconeError>{ | ||
/// let pinecone = PineconeClient::new(None, None, None, None).unwrap(); | ||
/// | ||
/// let index = pinecone.index("index-name", None, None).await.unwrap(); | ||
/// # Ok(()) | ||
/// # } | ||
/// ``` | ||
pub async fn index( | ||
&self, | ||
host: &str, | ||
secure: Option<bool>, | ||
port: Option<u32>, | ||
) -> Result<Index, PineconeError> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you remind me, what is the situation when endpoint either will or won't already be constructed with a scheme? And what is the use case for not using a secure connection?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling describe_index returns the host without the scheme, so I think it would make sense for us to accommodate that along with the case that the scheme is included anyway.
As for the secure connection, I believe this is mainly for Pinecone local. We haven't been able to connect to prod with http but it sounds like there is a use case for it in a local dev setting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha. Since this is the primary interface users will use to connect to Pinecone, it might be confusing to present this as a choice they have to make when 99.9% of the time they should just connect securely.
How do we handle this in other SDKs? I vaguely remember we all discussed this last week with @austin-denoble and @haruska so apologies if this is a rehash of a decision that was made.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We had a discussion about changing the signature to take the host, and along with the host we included secure
and port
. Would it make sense to remove secure
and port
since they can be included in host
? @austin-denoble @haruska
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like Emily mentioned, the secure
flag is primarily to allow development against something like Pinecone local. I think you're right though that this is probably not an option we want to provide more generally at the moment.
In Python there's some similar logic, including a secure
flag which is used to configure the gRPC client if you pass a config: https://github.com/pinecone-io/pinecone-python-client/blob/8b7e7de50e72248de683a24ada024cf18e6cdce3/pinecone/grpc/base.py#L101
However, this is kind of buried, and I don't think we really expect users to pass a custom gRPC config. I know Jen has also mentioned wanting to remove it from the interface entirely.
@haruska also added logic for allowing a custom port: https://github.com/pinecone-io/pinecone-python-client/blob/main/pinecone/grpc/base.py#L86-L90
I think we may want to go with something similar to what Jason implemented here if possible.
pinecone_sdk/src/pinecone/data.rs
Outdated
let endpoint = Channel::from_shared(host.to_string()) | ||
.map_err(|e| PineconeError::ConnectionError { inner: Box::new(e) })? | ||
.tls_config(tls_config) | ||
.map_err(|e| PineconeError::ConnectionError { inner: Box::new(e) })?; | ||
|
||
let channel = endpoint | ||
.connect() | ||
.await | ||
.map_err(|e| PineconeError::ConnectionError { inner: Box::new(e) })?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
very nice
pinecone_sdk/src/pinecone/data.rs
Outdated
.get_ref() | ||
.clone(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we need to clone?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without the clone this would return &UpsertError
, and I'm not sure that the reference would still be valid after the response object goes out of scope. Is there another way around this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can use into_inner
to avoid the clone and access the inner response object:
let response = self
.connection
.upsert(request)
.await
.map_err(|e| PineconeError::UpsertError { inner: Box::new(e) })?
.into_inner();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! Thanks for addressing all the feedback
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great, fantastic work! 🚢
Problem
Data plane was not set up.
Solution
Sets up the build process to generate gRPC code for data plane. We also have a basic implementation of
upsert
that calls the generated function.This PR also restructures some code to include the implementation of
PineconeClient
(data.rs
andcontrol.rs
) underpinecone
.The protobuf file for dataplane is currently committed to the repository due to difficulty with ci; it was originally referenced in a submodule but there is some tricky stuff with auth and submodules in GitHub Actions.
Some dependencies are included in
pinecone_sdk/imports/google/api
forprotoc
.Type of Change
Test Plan
Existing test cases pass.