mirror of
https://github.com/neondatabase/neon.git
synced 2026-05-29 11:00:38 +00:00
proxy: For cancellation data replace HSET+EXPIRE/HGET with SET..EX/GET (#12553)
## Problem To store cancellation data we send two commands to redis because the redis server version doesn't support HSET with EX. Also, HSET is not really needed. ## Summary of changes * Replace the HSET + EXPIRE command pair with one SET .. EX command. * Replace HGET with GET. * Leave a workaround for old keys set with HSET. * Replace some anyhow errors with specific errors to surface the WRONGTYPE error from redis.
This commit is contained in:
@@ -4,11 +4,12 @@ use std::time::Duration;
|
||||
|
||||
use futures::FutureExt;
|
||||
use redis::aio::{ConnectionLike, MultiplexedConnection};
|
||||
use redis::{ConnectionInfo, IntoConnectionInfo, RedisConnectionInfo, RedisResult};
|
||||
use redis::{ConnectionInfo, IntoConnectionInfo, RedisConnectionInfo, RedisError, RedisResult};
|
||||
use tokio::task::AbortHandle;
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
use super::elasticache::CredentialsProvider;
|
||||
use crate::redis::elasticache::CredentialsProviderError;
|
||||
|
||||
enum Credentials {
|
||||
Static(ConnectionInfo),
|
||||
@@ -26,6 +27,14 @@ impl Clone for Credentials {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum ConnectionProviderError {
|
||||
#[error(transparent)]
|
||||
Redis(#[from] RedisError),
|
||||
#[error(transparent)]
|
||||
CredentialsProvider(#[from] CredentialsProviderError),
|
||||
}
|
||||
|
||||
/// A wrapper around `redis::MultiplexedConnection` that automatically refreshes the token.
|
||||
/// Provides PubSub connection without credentials refresh.
|
||||
pub struct ConnectionWithCredentialsProvider {
|
||||
@@ -86,15 +95,18 @@ impl ConnectionWithCredentialsProvider {
|
||||
}
|
||||
}
|
||||
|
||||
async fn ping(con: &mut MultiplexedConnection) -> RedisResult<()> {
|
||||
redis::cmd("PING").query_async(con).await
|
||||
async fn ping(con: &mut MultiplexedConnection) -> Result<(), ConnectionProviderError> {
|
||||
redis::cmd("PING")
|
||||
.query_async(con)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub(crate) fn credentials_refreshed(&self) -> bool {
|
||||
self.credentials_refreshed.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub(crate) async fn connect(&mut self) -> anyhow::Result<()> {
|
||||
pub(crate) async fn connect(&mut self) -> Result<(), ConnectionProviderError> {
|
||||
let _guard = self.mutex.lock().await;
|
||||
if let Some(con) = self.con.as_mut() {
|
||||
match Self::ping(con).await {
|
||||
@@ -141,7 +153,7 @@ impl ConnectionWithCredentialsProvider {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_connection_info(&self) -> anyhow::Result<ConnectionInfo> {
|
||||
async fn get_connection_info(&self) -> Result<ConnectionInfo, ConnectionProviderError> {
|
||||
match &self.credentials {
|
||||
Credentials::Static(info) => Ok(info.clone()),
|
||||
Credentials::Dynamic(provider, addr) => {
|
||||
@@ -160,7 +172,7 @@ impl ConnectionWithCredentialsProvider {
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_client(&self) -> anyhow::Result<redis::Client> {
|
||||
async fn get_client(&self) -> Result<redis::Client, ConnectionProviderError> {
|
||||
let client = redis::Client::open(self.get_connection_info().await?)?;
|
||||
self.credentials_refreshed.store(true, Ordering::Relaxed);
|
||||
Ok(client)
|
||||
|
||||
@@ -9,10 +9,12 @@ use aws_config::meta::region::RegionProviderChain;
|
||||
use aws_config::profile::ProfileFileCredentialsProvider;
|
||||
use aws_config::provider_config::ProviderConfig;
|
||||
use aws_config::web_identity_token::WebIdentityTokenCredentialsProvider;
|
||||
use aws_credential_types::provider::error::CredentialsError;
|
||||
use aws_sdk_iam::config::ProvideCredentials;
|
||||
use aws_sigv4::http_request::{
|
||||
self, SignableBody, SignableRequest, SignatureLocation, SigningSettings,
|
||||
self, SignableBody, SignableRequest, SignatureLocation, SigningError, SigningSettings,
|
||||
};
|
||||
use aws_sigv4::sign::v4::signing_params::BuildError;
|
||||
use tracing::info;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -40,6 +42,18 @@ impl AWSIRSAConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum CredentialsProviderError {
|
||||
#[error(transparent)]
|
||||
AwsCredentials(#[from] CredentialsError),
|
||||
#[error(transparent)]
|
||||
AwsSigv4Build(#[from] BuildError),
|
||||
#[error(transparent)]
|
||||
AwsSigv4Singing(#[from] SigningError),
|
||||
#[error(transparent)]
|
||||
Http(#[from] http::Error),
|
||||
}
|
||||
|
||||
/// Credentials provider for AWS elasticache authentication.
|
||||
///
|
||||
/// Official documentation:
|
||||
@@ -92,7 +106,9 @@ impl CredentialsProvider {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn provide_credentials(&self) -> anyhow::Result<(String, String)> {
|
||||
pub(crate) async fn provide_credentials(
|
||||
&self,
|
||||
) -> Result<(String, String), CredentialsProviderError> {
|
||||
let aws_credentials = self
|
||||
.credentials_provider
|
||||
.provide_credentials()
|
||||
|
||||
@@ -2,9 +2,18 @@ use std::time::Duration;
|
||||
|
||||
use futures::FutureExt;
|
||||
use redis::aio::ConnectionLike;
|
||||
use redis::{Cmd, FromRedisValue, Pipeline, RedisResult};
|
||||
use redis::{Cmd, FromRedisValue, Pipeline, RedisError, RedisResult};
|
||||
|
||||
use super::connection_with_credentials_provider::ConnectionWithCredentialsProvider;
|
||||
use crate::redis::connection_with_credentials_provider::ConnectionProviderError;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum RedisKVClientError {
|
||||
#[error(transparent)]
|
||||
Redis(#[from] RedisError),
|
||||
#[error(transparent)]
|
||||
ConnectionProvider(#[from] ConnectionProviderError),
|
||||
}
|
||||
|
||||
pub struct RedisKVClient {
|
||||
client: ConnectionWithCredentialsProvider,
|
||||
@@ -32,12 +41,13 @@ impl RedisKVClient {
|
||||
Self { client }
|
||||
}
|
||||
|
||||
pub async fn try_connect(&mut self) -> anyhow::Result<()> {
|
||||
pub async fn try_connect(&mut self) -> Result<(), RedisKVClientError> {
|
||||
self.client
|
||||
.connect()
|
||||
.boxed()
|
||||
.await
|
||||
.inspect_err(|e| tracing::error!("failed to connect to redis: {e}"))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub(crate) fn credentials_refreshed(&self) -> bool {
|
||||
@@ -47,7 +57,7 @@ impl RedisKVClient {
|
||||
pub(crate) async fn query<T: FromRedisValue>(
|
||||
&mut self,
|
||||
q: &impl Queryable,
|
||||
) -> anyhow::Result<T> {
|
||||
) -> Result<T, RedisKVClientError> {
|
||||
let e = match q.query(&mut self.client).await {
|
||||
Ok(t) => return Ok(t),
|
||||
Err(e) => e,
|
||||
|
||||
Reference in New Issue
Block a user