Skip to content
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

Grpc Endpoint Caching #42

Merged
merged 14 commits into from
May 18, 2022
Merged

Grpc Endpoint Caching #42

merged 14 commits into from
May 18, 2022

Conversation

philipjames44
Copy link
Contributor

@philipjames44 philipjames44 commented May 3, 2022

This PR adds in a grpc endpoint cache that adds:
1.) Option to read and write from file based cache
2.) Option for in ephemeral in-memory caching
3.) Option to have no caching whatsoever

This PR should be followed by #23 to actually utilize the cache. This is split into 2 issues to separate actual cache implementation and utilization bc the latter will require a bit of a refractor.

@philipjames44 philipjames44 changed the title Phil/grpc endpt optimizations Grpc Endpoint Caching May 3, 2022
@philipjames44 philipjames44 self-assigned this May 3, 2022
@philipjames44 philipjames44 linked an issue May 3, 2022 that may be closed by this pull request
@philipjames44 philipjames44 marked this pull request as ready for review May 4, 2022 16:28
@philipjames44 philipjames44 requested a review from cbrit May 4, 2022 16:28

// Expect Tx error b/c unit test has no network connectivity; do string matching b/c exact error type matching is messy
assert_eq!(&err[..35], "chain client error: transport error");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had to clean up these tests here; unrelated to caching PR, they were just in a broken state, error messages aren't always consistent, hence the change.

ocular/src/chain/client/cache.rs Outdated Show resolved Hide resolved
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct GrpcEndpoint<'a> {
pub address: &'a str,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a particular reason we need a string slice instead of just using String?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not that I recall, we can change it if there's a preference

Copy link
Member

@cbrit cbrit May 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are having to specify a lifetime because we want to make a slice last a long time, that's part of the reason a String exists, so let's do that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

override_if_exists: bool,
) -> Result<Cache, CacheError> {
// If none, create at default: (e.g. ~/.ocular/grpc_endpoints.toml)
let path: String = if let Some(file_path) = file_path {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: match is more appropriate and readable here imo, and we also use it elsewhere in the codebase in these situations

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, will update.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

Comment on lines 60 to 67
dirs::home_dir()
.unwrap()
.into_os_string()
.into_string()
.unwrap()
+ DEFAULT_FILE_CACHE_DIR
+ "/"
+ DEFAULT_FILE_CACHE_NAME
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would probably be safer to use the PathBuf methods (home_dir().unwrap() returns a PathBuf) to build this path. Like push("/" + ...) or something:
https://doc.rust-lang.org/nightly/std/path/struct.PathBuf.html

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I'll update that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

Comment on lines 73 to 75
if !path.ends_with(".toml") {
return Err(CacheError::Initialization(String::from(
"Only toml files supported.",
)));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe also check that it is a file and not a directory

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

Comment on lines 214 to 216
if !self.endpoints.remove(&item) {
return Err(CacheError::DoesNotExist(item));
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need an error for this case since the goal is to make sure it's not in the cache and it already isn't. A debug log might be good indicating that it was attempted but didn't exist?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I'm open to that. I think as long as we agree that this isn't actually a failure mode we can omit DNE and already exist cache errors. In theory trying to remove something that isn't present isn't logically correct, but it also might not really matter. What do you think the better user experience would be?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think since it's a cache we don't care about the bool returned by the HashSet for remove(). I don't think the user will care that it didn't exist already.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

Comment on lines 238 to 244
for endpt in &old_toml.endpoint {
if endpt.address.trim() != item.as_str() {
toml.endpoint.push(GrpcEndpoint {
address: endpt.address,
});
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I think something like this will also

Suggested change
for endpt in &old_toml.endpoint {
if endpt.address.trim() != item.as_str() {
toml.endpoint.push(GrpcEndpoint {
address: endpt.address,
});
}
}
toml.endpoint = old_toml.endpoint
.iter()
.filter(|ep| ep.address.trim() != item.as_str())
.collect();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

Comment on lines +256 to +253
fn get_all_items(&self) -> Result<HashSet<String>, CacheError> {
Ok(self.endpoints.clone())
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about how this fits into the health check: is the idea that we would randomly select one of these and then remove the unhealthy ones as we go?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, that's what I was thinking.

#[derive(Debug, Default, Serialize, Deserialize)]
pub struct GrpcEndpointToml<'a> {
#[serde(borrow)]
pub endpoint: Vec<GrpcEndpoint<'a>>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe you intended for this to be "endpoints" (plural).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

Comment on lines 272 to 277
fn add_item(&mut self, item: String) -> Result<(), CacheError> {
match self.endpoints.insert(item.clone()) {
true => Ok(()),
false => Err(CacheError::AlreadyExists(item)),
}
}

fn remove_item(&mut self, item: String) -> Result<(), CacheError> {
match self.endpoints.remove(&item.clone()) {
true => Ok(()),
false => Err(CacheError::DoesNotExist(item)),
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once again, i think we can ignore the bool returned here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

@philipjames44 philipjames44 requested a review from cbrit May 17, 2022 18:04
@philipjames44 philipjames44 force-pushed the phil/grpc-endpt-optimizations branch from 64fb85e to 2246d64 Compare May 17, 2022 18:09
dbg!(format!("Attempting to use path {:#?}", path));

// Verify path formatting
if path.extension().unwrap().to_str().unwrap() != "toml" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will panic if the path they provide has no file extension.

  1. probably want a path.extension().is_none() check that returns an error before this if true
  2. we can avoid the extra unwrap by making "toml" into an OsString

This covers both of those:

Suggested change
if path.extension().unwrap().to_str().unwrap() != "toml" {
if path.extension().is_some() && path.extension().unwrap() != OsString::new("toml") {

Then changing the error message on 74 to something like "Only files with extension .toml are supported"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

return Err(CacheError::Initialization(String::from(
"Only toml files supported.",
)));
} else if path.is_dir() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking this first would avoid the no-extension error

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, reordered

Comment on lines 84 to 89
let save_path = path.parent().unwrap();

let dir_res = save_path.metadata();

// Create dir if doesn't exist
if dir_res.is_err() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Path has an exists() method

Suggested change
let save_path = path.parent().unwrap();
let dir_res = save_path.metadata();
// Create dir if doesn't exist
if dir_res.is_err() {
let save_path = path.parent().unwrap();
// Create dir if doesn't exist
if !save_path.exists() {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

let mut endpoints = HashSet::new();

// Load endpoints if they exist
if std::path::Path::new(&path).exists() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pretty sure this can be path.exists()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

}

// Finally we can manipulate the actual file after checking the override settings
if override_if_exists || !std::path::Path::new(&path).exists() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

}
}

// If patch specified create
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup, removed.

}

#[cfg(test)]
mod tests {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests seem involved enough to warrant being in the integration tests dir and run in a docker env since they write to the file system

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved to docker integration tests

@philipjames44 philipjames44 requested a review from cbrit May 18, 2022 01:58
Copy link
Member

@cbrit cbrit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@philipjames44 philipjames44 merged commit 69137c1 into main May 18, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add in grpc caching mechanisms
2 participants