Skip to content

Implement failover and replicaof command - #282

Open
prateek-kumar-improving wants to merge 16 commits into
mainfrom
implement-replicaof-failover-commands
Open

Implement failover and replicaof command#282
prateek-kumar-improving wants to merge 16 commits into
mainfrom
implement-replicaof-failover-commands

Conversation

@prateek-kumar-improving

@prateek-kumar-improving prateek-kumar-improving commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

Implement REPLICAOF and FAILOVER commands for the standalone ValkeyGlide client.

Issue link

This Pull Request is linked to issue: #236

Features / Behaviour Changes

  • replicaof(?string $host = null, int $port = 6379) — Makes the server a replica of the specified primary. When called with null host, sends REPLICAOF NO ONE to promote the server to a primary.
  • failover(?array $to = null, bool $abort = false, int $timeout = 0) — Starts a coordinated failover from the connected primary to one of its replicas. Supports TO host port [FORCE], ABORT, and TIMEOUT options.
  • Both commands support OPT_REPLY_LITERAL (returns "OK" string instead of true).
  • Standalone-only commands — not exposed on ValkeyGlideCluster (matching PHPRedis behavior).

Implementation

  • Added execute_failover_command() and execute_replicaof_command() in valkey_glide_commands.c
  • Both use STANDARD_METHOD_IMPL macro pattern
  • Commands registered as FailOver (1127) and ReplicaOf (1151) request types in glide-core
  • Added to prepare_message_args switch case in valkey_glide_core_common.c (alphabetized)
  • Added independent Valkey server on port 6382 in CI for isolated testing

Limitations

  • FAILOVER success test performs best-effort topology restoration after failover completes. The glide client connection may become unstable after a role change.
  • Cluster mode does not support these commands (standalone-only).

Testing

All tests are skipped in cluster mode via overrides in ValkeyGlideClusterTest.

Checklist

Before submitting the PR make sure the following are checked:

  • This Pull Request is related to one issue.
  • Commit message has a detailed description of what changed and why.
  • Tests are added or updated.
  • CHANGELOG.md and documentation files are updated.
  • Destination branch is correct - main or release
  • Create merge commit if merging release branch into main, squash otherwise.

Signed-off-by: Prateek Kumar <prateek.kumar@improving.com>
Signed-off-by: Prateek Kumar <prateek.kumar@improving.com>
Signed-off-by: Prateek Kumar <prateek.kumar@improving.com>
Signed-off-by: Prateek Kumar <prateek.kumar@improving.com>
Signed-off-by: Prateek Kumar <prateek.kumar@improving.com>
Signed-off-by: Prateek Kumar <prateek.kumar@improving.com>
@prateek-kumar-improving
prateek-kumar-improving marked this pull request as ready for review July 28, 2026 17:55
Signed-off-by: prateek-kumar-improving <prateek.kumar@improving.com>
Signed-off-by: Prateek Kumar <prateek.kumar@improving.com>
@yipin-chen
yipin-chen requested review from currantw and nderraugh July 30, 2026 17:06
Signed-off-by: prateek-kumar-improving <prateek.kumar@improving.com>
Signed-off-by: Prateek Kumar <prateek.kumar@improving.com>
Signed-off-by: Prateek Kumar <prateek.kumar@improving.com>
Signed-off-by: Prateek Kumar <prateek.kumar@improving.com>
Signed-off-by: Prateek Kumar <prateek.kumar@improving.com>
Signed-off-by: Prateek Kumar <prateek.kumar@improving.com>
Signed-off-by: prateek-kumar-improving <prateek.kumar@improving.com>
Signed-off-by: Prateek Kumar <prateek.kumar@improving.com>
Comment thread tests/ValkeyGlideTest.php
$this->assertGTE(1, (int) $info['connected_slaves']);

// FAILOVER returns true (command accepted, failover is async)
$result = $client->failover();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Does anything need to get reset with the servers after this test succeeds or fails after an attempted failover so that other following tests aren't affected? i.e. something to run in a tear down-like phase to reset the servers.

Comment thread valkey_glide_commands.c

int arg_idx = 0;

if (abort) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

abort doesn't use the timeout parameter?

@nderraugh nderraugh Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If these parameters (host, abort, timeout) have conflicting permutations, it might be worth addressing them the same way it's done in other places.

i.e. @throws ValkeyGlideException If conflicting parameters are specified or connection fails.

Alternatively succeeding and ignoring the conflict might be worth logging. Or perhaps even splitting functions along the abort path.

Comment thread valkey_glide_commands.c
zval* z_host = zend_hash_str_find(Z_ARRVAL_P(to_arr), "host", 4);
zval* z_port = zend_hash_str_find(Z_ARRVAL_P(to_arr), "port", 4);

if (!z_host || Z_TYPE_P(z_host) != IS_STRING || !z_port) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Similar issue around validation here.

Existing validation patterns look like this

Suggested change
if (!z_host || Z_TYPE_P(z_host) != IS_STRING || !z_port) {
if (!z_host || Z_TYPE_P(z_host) != IS_STRING || !z_port) {
VALKEY_LOG_ERROR("command_validation", "'to' array must contain string 'host' and 'port'");
zend_throw_exception(get_valkey_glide_exception_ce(),
"'to' array must contain string 'host' and 'port'", 0);

I think it makes sense to use that here. Otherwise a malformed $to fails silently with no hint about the problem.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Might be worth thinking about whether this can be done cohesively around lines 737-740.

Comment thread valkey_glide_commands.c
core_args.args[1].data.string_arg.len = 3;
core_args.arg_count = 2;
} else {
/* REPLICAOF host port */

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Do we need to do any validation on host here? e.g. should '' be normalized or rejected (and logged iwth explanation) or something else?

@currantw currantw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed to 49822f9.

Comment thread valkey_glide.stub.php
/**
* Starts a coordinated failover from the connected primary to one of its replicas.
*
* @param array|null $to Target replica as ['host' => string, 'port' => int], or null for auto-selection.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit.

Suggested change
* @param array|null $to Target replica as ['host' => string, 'port' => int], or null for auto-selection.
* @param array|null $to Target replica as ['host' => string, 'port' => int], or null for auto-selection.

Comment thread valkey_z_php_methods.c
/* }}} */

/* {{{ proto string ValkeyGlide::bgSave([string mode]) */
/* {{{ proto bool ValkeyGlide::bgSave([string mode]) */

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for fixing 💪

Comment thread tests/ValkeyGlideTest.php
Comment on lines +3298 to +3300
// Verify it's currently a master
$info = $client->info('REPLICATION');
$this->assertEquals('master', $info['role']);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can we use waitForRole here?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

(Or assertRole comments suggested in other comment)

Comment thread tests/ValkeyGlideTest.php
Comment on lines +3313 to +3315
// Verify role changed back to master
$info = $client->info('REPLICATION');
$this->assertEquals('master', $info['role']);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can we use waitForRole here?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

(Or assertRole comments suggested in other comment)

Comment thread tests/ValkeyGlideTest.php
Comment on lines +3327 to +3341
$client->setOption(ValkeyGlide::OPT_REPLY_LITERAL, true);
try {
$result = $client->replicaof('127.0.0.1', 6379);
$this->assertEquals('OK', $result);

// Wait for replication to establish
$client->setOption(ValkeyGlide::OPT_REPLY_LITERAL, false);
$this->waitForRole($client, 'slave');
$client->setOption(ValkeyGlide::OPT_REPLY_LITERAL, true);

$result = $client->replicaof();
$this->assertEquals('OK', $result);
} finally {
$client->setOption(ValkeyGlide::OPT_REPLY_LITERAL, false);
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Didn't we add a helper to simplify the OPT_REPLY_LITERAL cases?

Comment thread tests/ValkeyGlideTest.php
Comment on lines +3343 to +3346
// Verify role restored to master
$info = $client->info('REPLICATION');
$this->assertEquals('master', $info['role']);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can we use waitForRole here?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

(Or assertRole comments suggested in other comment)

Comment thread tests/ValkeyGlideTest.php
$this->assertFalse($result);
} finally {
$client->setOption(ValkeyGlide::OPT_REPLY_LITERAL, false);
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Similar comment about using OPT_REPLY_LITERAL helper.

Comment thread tests/ValkeyGlideTest.php
Comment on lines +3400 to +3401
$info = $client->info('REPLICATION');
$this->assertEquals('master', $info['role']);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Use waitForRole here?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

(Or assertRole comments suggested in other comment)

Comment thread tests/ValkeyGlideTest.php
/**
* Poll until a server reaches the expected role, or fail after timeout.
*/
protected function waitForRole(ValkeyGlide $client, string $expectedRole, int $timeoutMs = 5000): void

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Do we have a generic waitFor base method that this could use? We have something similar in the other clients. If not, I think it makes sense to add it?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We should also add an assertRole method. This could be used for cases where we don't need to wait and passed to waitFor for the case where we do?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants