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

Redis standalone #822

Open
wants to merge 53 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
bb04c46
Standalone redis implementation
May 21, 2024
519a9ab
Configure 'send_readonly' flag
May 21, 2024
f87f94e
redis standalone functional testing
May 21, 2024
76ccf48
Fix build error (header)
May 21, 2024
0d7d66b
ES-29941: After update userver, add missing include for log
Jun 11, 2024
2094618
Fix integration test running
Sep 2, 2024
78520a2
Use correct pimpl in subscription mode
Sep 3, 2024
8930408
Testsuite for pub/sub standalone
Sep 3, 2024
c7b6e5e
Move redis standalone fixtures to plugin
Sep 3, 2024
98576c8
Redis standalne plugin moved for sharing
Sep 3, 2024
741942a
Move shared redis standalone plugin in integration_tests
Sep 3, 2024
b7f2960
Merge commit 'c2f43b4e' into redis_standalone_3
Jan 6, 2025
2a43580
Use redis::RedisCreationSettings from outside
Oct 28, 2024
2672989
Remove unused variable
Nov 24, 2024
7f1b3ce
Merge remote-tracking branch 'ya/develop' into develop
Nov 29, 2024
c32a353
Merge remote-tracking branch 'upstream/develop' into develop
Dec 26, 2024
56b981c
Merge commit '14570b32' into redis_standalone_3
Jan 6, 2025
7cf5015
Fix namespace for key standalone
Dec 26, 2024
c26ca88
Fix namespace
Dec 27, 2024
5904031
added empty SetConnectionInfo for standalone impl
Dec 27, 2024
09ce128
Merge branch 'gh_dev' into redis_standalone_3
Jan 6, 2025
df2b628
Minor namespace fixes and warnings
Jan 6, 2025
6a79fee
Redis standalone plugin moved to pytest_userver.plugins
Jan 7, 2025
c6bbc7a
Cosmetics
Jan 7, 2025
26d4133
Do not specify PYTHONPATH for pubsub testsuite
Jan 7, 2025
87c35a0
Run redis standalone the same way as in ya testsuites
Jan 7, 2025
e1af661
Rename redis_standalone.py -> pytest_plugin.py
Jan 7, 2025
6336dcc
Do not take into account pytest config
Jan 7, 2025
0864b9a
Use pragma
Jan 7, 2025
88c4f68
Cosmetics
Jan 7, 2025
19b2cf1
Fix metrics for standalone impl
Jan 7, 2025
cb59bb2
Formattings
Jan 7, 2025
6670e47
Added TopologyHolderBase interface
Jan 11, 2025
ae8bfe4
StandaloneTopologyHolder implementation
Jan 12, 2025
87ecd94
Construct correct topology holder
Jan 12, 2025
3966234
IsClusterStrategy is part of KeyShardFactory
Jan 12, 2025
4e16a39
Remove StandaloneImpl
Jan 12, 2025
822ebc2
Revert RedisCreationSettings changes
Jan 12, 2025
b519df3
Start watcher signal
Jan 12, 2025
059e562
Revert "Revert RedisCreationSettings changes"
Jan 12, 2025
43835f7
Do not send readonly
Jan 12, 2025
0801331
Implement SetConnectionInfo
Jan 13, 2025
f007fc7
Move StandaloneTopologyHolder to separate file
Jan 13, 2025
1e615be
Lock node creation process
Jan 14, 2025
2e17414
is_nodes_received_ flag
Jan 14, 2025
ee3f72c
Build topology based on null check
Jan 15, 2025
887bd4a
Nodes received if the same info
Jan 15, 2025
c59e855
Remove logs
Jan 15, 2025
ba429a8
Merge remote-tracking branch 'gh/develop' into redis_standalone
Jan 15, 2025
3d95f79
Remove unnecessary forward declaration
Jan 17, 2025
c970a0d
Remove unnecessary header
Jan 17, 2025
82f1b84
Remove todo
Jan 17, 2025
3692208
Formatting
Jan 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions redis/functional_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}-metrics)

add_subdirectory(pubsub)
add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}-pubsub)

add_subdirectory(integration_tests)
add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}-integration)
2 changes: 1 addition & 1 deletion redis/functional_tests/integration_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
project(userver-redis-tests-basic-chaos CXX)
project(userver-redis-tests-integration CXX)

add_executable(${PROJECT_NAME} "redis_service.cpp")
target_link_libraries(${PROJECT_NAME} userver-core userver-redis)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ int main(int argc, char* argv[]) {
const auto component_list = components::MinimalServerComponentList()
.Append<chaos::KeyValue>("handler-cluster")
.Append<chaos::KeyValue>("handler-sentinel")
.Append<chaos::KeyValue>("handler-standalone")
.Append<components::HttpClient>()
.Append<components::Secdist>()
.Append<components::DefaultSecdistProvider>()
Expand Down
9 changes: 9 additions & 0 deletions redis/functional_tests/integration_tests/static_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,22 @@ components_manager:
task_processor: main-task-processor
method: GET,DELETE,POST

handler-standalone:
db: redis-standalone
path: /redis-standalone
task_processor: main-task-processor
method: GET,DELETE,POST

key-value-database:
groups:
- config_name: redis-sentinel
db: redis-sentinel
- config_name: redis-cluster
db: redis-cluster
sharding_strategy: RedisCluster
- config_name: redis-standalone
db: redis-standalone
sharding_strategy: RedisStandalone
subscribe_groups: []
thread_pools:
redis_thread_pool_size: 2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@


@pytest.fixture(scope='session')
def service_env(redis_sentinels, redis_cluster_nodes, redis_cluster_replicas):
def service_env(redis_sentinels, redis_cluster_nodes, redis_cluster_replicas, redis_standalone):
cluster_shards = [
{'name': f'shard{idx}'}
for idx in range(
Expand All @@ -29,6 +29,11 @@ def service_env(redis_sentinels, redis_cluster_nodes, redis_cluster_replicas):
'sentinels': redis_sentinels,
'shards': [{'name': 'test_master1'}],
},
'redis-standalone': {
'password': '',
'sentinels': redis_standalone,
'shards': [{'name': 'test_master1'}],
},
},
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import asyncio

import redis
import pytest

KEYS_SEQ_LEN = 10 # enough sequential keys to test all shards
FAILOVER_DEADLINE_SEC = 30 # maximum time allowed to finish failover


@pytest.mark.xfail
async def test_happy_path(service_client):
post_reqs = [
service_client.post(
Expand Down Expand Up @@ -59,7 +60,7 @@ async def _assert_failover_completed(service_client, key_prefix, value):
await asyncio.sleep(1)
assert write_ok


@pytest.mark.xfail
async def test_failover(service_client, redis_cluster_store):
# Write enough different keys to have something in every shard
assert await _check_write_all_shards(service_client, 'hf_key1', 'abc')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import asyncio

import redis


KEYS_SEQ_LEN = 10 # enough sequential keys to test all shards
FAILOVER_DEADLINE_SEC = 30 # maximum time allowed to finish failover
URL_PATH = '/redis-standalone'

async def test_happy_path(service_client):
post_reqs = [
service_client.post(
URL_PATH, params={'key': f'key{i}', 'value': 'abc'},
)
for i in range(KEYS_SEQ_LEN)
]
assert all(res.status == 201 for res in await asyncio.gather(*post_reqs))

get_reqs = [
service_client.get(URL_PATH, params={'key': f'key{i}'})
for i in range(KEYS_SEQ_LEN)
]
assert all(
res.status == 200 and res.text == 'abc'
for res in await asyncio.gather(*get_reqs)
)
1 change: 1 addition & 0 deletions redis/functional_tests/pubsub/redis_service.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ int main(int argc, char* argv[]) {
.Append<chaos::ReadStoreReturn>("handler-cluster")
.Append<chaos::ReadStoreReturn>("handler-sentinel")
.Append<chaos::ReadStoreReturn>("handler-sentinel-with-master")
.Append<chaos::ReadStoreReturn>("handler-standalone")
.Append<components::HttpClient>()
.Append<components::Secdist>()
.Append<components::DefaultSecdistProvider>()
Expand Down
9 changes: 9 additions & 0 deletions redis/functional_tests/pubsub/static_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ components_manager:
task_processor: main-task-processor
method: GET,DELETE,PUT

handler-standalone:
db: redis-standalone
path: /redis-standalone
task_processor: main-task-processor
method: GET,DELETE,PUT

key-value-database:
groups: []
subscribe_groups:
Expand All @@ -30,6 +36,9 @@ components_manager:
- config_name: redis-sentinel
db: redis-sentinel-with-master
allow_reads_from_master: true
- config_name: redis-standalone
db: redis-standalone
sharding_strategy: RedisStandalone
thread_pools:
redis_thread_pool_size: 2
sentinel_thread_pool_size: 1
Expand Down
7 changes: 6 additions & 1 deletion redis/functional_tests/pubsub/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@


@pytest.fixture(scope='session')
def service_env(redis_sentinels, redis_cluster_nodes, redis_cluster_replicas):
def service_env(redis_sentinels, redis_cluster_nodes, redis_cluster_replicas, redis_standalone):
cluster_shards = [
{'name': f'shard{idx}'}
for idx in range(
Expand All @@ -29,6 +29,11 @@ def service_env(redis_sentinels, redis_cluster_nodes, redis_cluster_replicas):
'sentinels': redis_sentinels,
'shards': [{'name': 'test_master0'}],
},
'redis-standalone': {
'password': '',
'sentinels': redis_standalone,
'shards': [{'name': 'test_master1'}],
},
},
}
return {'SECDIST_CONFIG': json.dumps(secdist_config)}
14 changes: 14 additions & 0 deletions redis/functional_tests/pubsub/tests/test_redis_pubsub.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,17 @@ async def _test_pubsub(port_number, prefix):

if failed:
assert False, f'Failed after multiple retries: {failed}'

####################################################

async def test_happy_path_standalone(service_client, redis_standalone_store):
msg = 'sentinel_message'
assert await _validate_pubsub(redis_standalone_store, service_client, msg, 'standalone')

async def test_happy_path_standalone_with_resubscription(
service_client, redis_standalone_store
):
msg = 'sentinel_message'
response = await service_client.put(_get_url('standalone'))
assert response.status == 200
assert await _validate_pubsub(redis_standalone_store, service_client, msg, 'standalone')
7 changes: 4 additions & 3 deletions redis/src/storages/redis/component.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,6 @@ void Redis::Connect(
for (const auto& redis_group : subscribe_redis_groups) {
auto settings = GetSecdistSettings(secdist_component, redis_group);

bool is_cluster_mode = storages::redis::impl::IsClusterStrategy(redis_group.sharding_strategy);
storages::redis::CommandControl cc{};
cc.allow_reads_from_master = redis_group.allow_reads_from_master;

Expand All @@ -245,7 +244,7 @@ void Redis::Connect(
redis_group.config_name,
config_source,
redis_group.db,
is_cluster_mode,
redis_group.sharding_strategy,
cc,
testsuite_redis_control
);
Expand Down Expand Up @@ -375,13 +374,14 @@ additionalProperties: false
description: name to refer to the cluster in components::Redis::GetClient()
sharding_strategy:
type: string
description: one of RedisCluster, KeyShardCrc32, KeyShardTaximeterCrc32 or KeyShardGpsStorageDriver
description: one of RedisStandalone, RedisCluster, KeyShardCrc32, KeyShardTaximeterCrc32 or KeyShardGpsStorageDriver
defaultDescription: "KeyShardTaximeterCrc32"
enum:
- RedisCluster
- KeyShardCrc32
- KeyShardTaximeterCrc32
- KeyShardGpsStorageDriver
- RedisStandalone
allow_reads_from_master:
type: boolean
description: allows read requests from master instance
Expand Down Expand Up @@ -415,6 +415,7 @@ additionalProperties: false
enum:
- RedisCluster
- KeyShardTaximeterCrc32
- RedisStandalone
allow_reads_from_master:
type: boolean
description: allows subscriptions to master instance to distribute load
Expand Down
2 changes: 2 additions & 0 deletions redis/src/storages/redis/impl/keyshard.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "keyshard_impl.hpp"
#include "keyshard_standalone_impl.hpp"

#include <algorithm>
#include <cassert>
Expand Down Expand Up @@ -106,6 +107,7 @@ std::unique_ptr<KeyShard> KeyShardFactory::operator()(size_t nshards) {
if (type_ == "KeyShardTaximeterCrc32") return std::make_unique<KeyShardTaximeterCrc32>(nshards);
if (type_ == KeyShardCrc32::kName) return std::make_unique<KeyShardCrc32>(nshards);
if (type_ == kRedisCluster) return nullptr;
if (type_ == KeyShardStandalone::kName) return std::make_unique<KeyShardStandalone>();

return std::make_unique<KeyShardTaximeterCrc32>(nshards);
}
Expand Down
22 changes: 22 additions & 0 deletions redis/src/storages/redis/impl/keyshard_standalone_impl.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once

#include <userver/storages/redis/impl/keyshard.hpp>

#include <limits>

USERVER_NAMESPACE_BEGIN

namespace storages::redis::impl {

class KeyShardStandalone : public KeyShard {
public:
static constexpr char kName[] = "RedisStandalone";
static constexpr std::size_t kUnknownShard = std::numeric_limits<std::size_t>::max();

size_t ShardByKey(const std::string&) const override { return kUnknownShard; }
bool IsGenerateKeysForShardsEnabled() const override { return true; }
};

} // namespace storages::redis::impl

USERVER_NAMESPACE_END
13 changes: 5 additions & 8 deletions redis/src/storages/redis/impl/redis_connection_holder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ RedisConnectionHolder::RedisConnectionHolder(
Password password,
CommandsBufferingSettings buffering_settings,
ReplicationMonitoringSettings replication_monitoring_settings,
utils::RetryBudgetSettings retry_budget_settings
)
utils::RetryBudgetSettings retry_budget_settings,
redis::RedisCreationSettings redis_creation_settings)
: commands_buffering_settings_(std::move(buffering_settings)),
replication_monitoring_settings_(std::move(replication_monitoring_settings)),
retry_budget_settings_(std::move(retry_budget_settings)),
Expand All @@ -26,7 +26,8 @@ RedisConnectionHolder::RedisConnectionHolder(
ev_thread_,
[this] { EnsureConnected(); },
kCheckRedisConnectedInterval
) {
),
redis_creation_settings_(redis_creation_settings) {
// https://github.com/boostorg/signals2/issues/59
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDelete)
CreateConnection();
Expand All @@ -50,11 +51,7 @@ void RedisConnectionHolder::EnsureConnected() {
}

void RedisConnectionHolder::CreateConnection() {
RedisCreationSettings settings;
/// Here we allow read from replicas possibly stale data.
/// This does not affect connections to masters
settings.send_readonly = true;
auto instance = std::make_shared<Redis>(redis_thread_pool_, settings);
auto instance = std::make_shared<Redis>(redis_thread_pool_, redis_creation_settings_);
instance->signal_state_change.connect([weak_ptr{weak_from_this()}](Redis::State state) {
const auto ptr = weak_ptr.lock();
if (!ptr) return;
Expand Down
12 changes: 10 additions & 2 deletions redis/src/storages/redis/impl/redis_connection_holder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <storages/redis/impl/cluster_sentinel_impl.hpp>
#include <storages/redis/impl/redis.hpp>
#include <storages/redis/impl/sentinel.hpp>
#include <storages/redis/impl/redis_creation_settings.hpp>
#include <userver/concurrent/variable.hpp>
#include <userver/rcu/rcu.hpp>

Expand All @@ -16,6 +17,12 @@ namespace storages::redis::impl {
/// disconnected
class RedisConnectionHolder : public std::enable_shared_from_this<RedisConnectionHolder> {
public:
static constexpr redis::RedisCreationSettings makeDefaultRedisCreationSettings() {
/// Here we allow read from replicas possibly stale data.
/// This does not affect connections to masters
return redis::RedisCreationSettings{ConnectionSecurity::kNone, true};
}

RedisConnectionHolder(
const engine::ev::ThreadControl& sentinel_thread_control,
const std::shared_ptr<engine::ev::ThreadPool>& redis_thread_pool,
Expand All @@ -24,8 +31,8 @@ class RedisConnectionHolder : public std::enable_shared_from_this<RedisConnectio
Password password,
CommandsBufferingSettings buffering_settings,
ReplicationMonitoringSettings replication_monitoring_settings,
utils::RetryBudgetSettings retry_budget_settings
);
utils::RetryBudgetSettings retry_budget_settings,
redis::RedisCreationSettings redis_creation_settings = makeDefaultRedisCreationSettings());
~RedisConnectionHolder();
RedisConnectionHolder(const RedisConnectionHolder&) = delete;
RedisConnectionHolder& operator=(const RedisConnectionHolder&) = delete;
Expand Down Expand Up @@ -56,6 +63,7 @@ class RedisConnectionHolder : public std::enable_shared_from_this<RedisConnectio
const Password password_;
rcu::Variable<std::shared_ptr<Redis>, rcu::BlockingRcuTraits> redis_;
engine::ev::PeriodicWatcher connection_check_timer_;
const RedisCreationSettings redis_creation_settings_;
};

} // namespace storages::redis::impl
Expand Down
15 changes: 15 additions & 0 deletions redis/src/storages/redis/impl/sentinel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
#include <storages/redis/impl/subscribe_sentinel.hpp>

#include "command_control_impl.hpp"
#include <storages/redis/impl/standalone_impl.hpp>
#include "keyshard_standalone_impl.hpp"

USERVER_NAMESPACE_BEGIN

Expand Down Expand Up @@ -102,6 +104,19 @@ Sentinel::Sentinel(
dynamic_config_source,
mode
);
} else if(dynamic_cast<KeyShardStandalone*>(key_shard.get())) {
UASSERT_MSG(conns.size() == 1, "In standalone mode we expect exactly one redis node to connect!");
impl_ = std::make_unique<StandaloneImpl>(
*sentinel_thread_control_,
thread_pools_->GetRedisThreadPool(),
conns.front(),
std::move(shard_group_name),
client_name, password,
connection_security,
std::move(ready_callback),
dynamic_config_source,
mode
);
} else {
impl_ = std::make_unique<SentinelImpl>(
*sentinel_thread_control_,
Expand Down
Loading