Repository: dragonflydb/dragonfly
Pull request #4932: feat(server): Add support for command aliasing
Author: login: abhijat
Created: 2025-04-14T05:33:49Z | Merged: 2025-04-21T07:59:04Z

This is an effort to simplify previous PRs which introduced commits

https://github.com/dragonflydb/dragonfly/commit/91c27f1f18cb4378ca5daacbbe616855cce437fd
https://github.com/dragonflydb/dragonfly/commit/4539e3d024dd2348ebd60eeb73ef4fb04e110e8b

In this change the previous commits are reverted, and commands are cloned for each alias. An additional hidden opt is added for the cloned command. A new c-tor is added to clone commands cleanly.

FIXES https://github.com/dragonflydb/dragonfly/issues/4857


Diff:
diff --git a/src/server/acl/acl_family.cc b/src/server/acl/acl_family.cc
index 157c2024e0cd..f05c6bea8475 100644
--- a/src/server/acl/acl_family.cc
+++ b/src/server/acl/acl_family.cc
@@ -591,7 +591,7 @@ void AclFamily::DryRun(CmdArgList args, const CommandContext& cmd_cntx) {
 
   string command = absl::AsciiStrToUpper(ArgS(args, 1));
   auto* cid = cmd_registry_->Find(command);
-  if (!cid) {
+  if (!cid || cid->IsAlias()) {
     auto error = absl::StrCat("Command '", command, "' not found");
     rb->SendError(error);
     return;
@@ -1062,7 +1062,7 @@ std::pair<AclFamily::OptCommand, bool> AclFamily::MaybeParseAclCommand(
     std::string_view command) const {
   if (absl::StartsWith(command, "+")) {
     auto res = cmd_registry_->Find(command.substr(1));
-    if (!res) {
+    if (!res || res->IsAlias()) {
       return {};
     }
     std::pair<size_t, uint64_t> cmd{res->GetFamily(), res->GetBitIndex()};
@@ -1071,7 +1071,7 @@ std::pair<AclFamily::OptCommand, bool> AclFamily::MaybeParseAclCommand(
 
   if (absl::StartsWith(command, "-")) {
     auto res = cmd_registry_->Find(command.substr(1));
-    if (!res) {
+    if (!res || res->IsAlias()) {
       return {};
     }
     std::pair<size_t, uint64_t> cmd{res->GetFamily(), res->GetBitIndex()};
diff --git a/src/server/acl/acl_family_test.cc b/src/server/acl/acl_family_test.cc
index 7e06bf514242..cfed0578f9cd 100644
--- a/src/server/acl/acl_family_test.cc
+++ b/src/server/acl/acl_family_test.cc
@@ -19,6 +19,7 @@
 using namespace testing;
 
 ABSL_DECLARE_FLAG(std::vector<std::string>, rename_command);
+ABSL_DECLARE_FLAG(std::vector<std::string>, command_alias);
 
 namespace dfly {
 
@@ -29,6 +30,7 @@ class AclFamilyTest : public BaseFamilyTest {
 class AclFamilyTestRename : public BaseFamilyTest {
   void SetUp() override {
     absl::SetFlag(&FLAGS_rename_command, {"ACL=ROCKS"});
+    absl::SetFlag(&FLAGS_command_alias, {"___SET=SET"});
     ResetService();
   }
 };
@@ -538,4 +540,22 @@ TEST_F(AclFamilyTest, TestPubSub) {
   EXPECT_THAT(vec[9], "resetchannels &foo");
 }
 
+TEST_F(AclFamilyTest, TestAlias) {
+  auto resp = Run({"ACL", "SETUSER", "luke", "+___SET"});
+  EXPECT_THAT(resp, ErrArg("ERR Unrecognized parameter +___SET"));
+
+  resp = Run({"ACL", "SETUSER", "leia", "-___SET"});
+  EXPECT_THAT(resp, ErrArg("ERR Unrecognized parameter -___SET"));
+
+  resp = Run({"ACL", "SETUSER", "anakin", "+SET"});
+  EXPECT_EQ(resp, "OK");
+
+  resp = Run({"ACL", "SETUSER", "jarjar", "allcommands"});
+  EXPECT_EQ(resp, "OK");
+
+  resp = Run({"ACL", "DRYRUN", "jarjar", "___SET"});
+  EXPECT_THAT(resp, ErrArg("ERR Command '___SET' not found"));
+  EXPECT_EQ(Run({"ACL", "DRYRUN", "jarjar", "SET"}), "OK");
+}
+
 }  // namespace dfly
diff --git a/src/server/acl/validator.cc b/src/server/acl/validator.cc
index 941ca7ad5978..074fbd43b3bf 100644
--- a/src/server/acl/validator.cc
+++ b/src/server/acl/validator.cc
@@ -66,6 +66,10 @@ bool ValidateCommand(const std::vector<uint64_t>& acl_commands, const CommandId&
     return true;
   }
 
+  if (id.IsAlias()) {
+    return false;
+  }
+
   std::pair<bool, AclLog::Reason> auth_res;
 
   if (id.IsPubSub() || id.IsShardedPSub()) {
diff --git a/src/server/command_registry.cc b/src/server/command_registry.cc
index 896602c3dd07..ef992a451e1f 100644
--- a/src/server/command_registry.cc
+++ b/src/server/command_registry.cc
@@ -22,14 +22,16 @@ using namespace std;
 ABSL_FLAG(vector<string>, rename_command, {},
           "Change the name of commands, format is: <cmd1_name>=<cmd1_new_name>, "
           "<cmd2_name>=<cmd2_new_name>");
-ABSL_FLAG(vector<string>, command_alias, {},
-          "Add an alias for given commands, format is: <alias>=<original>, "
-          "<alias>=<original>");
 ABSL_FLAG(vector<string>, restricted_commands, {},
           "Commands restricted to connections on the admin port");
 
 ABSL_FLAG(vector<string>, oom_deny_commands, {},
           "Additinal commands that will be marked as denyoom");
+
+ABSL_FLAG(vector<string>, command_alias, {},
+          "Add an alias for given command(s), format is: <alias>=<original>, <alias>=<original>. "
+          "Aliases must be set identically on replicas, if applicable");
+
 namespace dfly {
 
 using namespace facade;
@@ -75,16 +77,17 @@ uint32_t ImplicitAclCategories(uint32_t mask) {
   return out;
 }
 
-absl::flat_hash_map<std::string, std::string> ParseCmdlineArgMap(
-    const absl::Flag<std::vector<std::string>>& flag, const bool allow_duplicates = false) {
+using CmdLineMapping = absl::flat_hash_map<std::string, std::string>;
+
+CmdLineMapping ParseCmdlineArgMap(const absl::Flag<std::vector<std::string>>& flag) {
   const auto& mappings = absl::GetFlag(flag);
-  absl::flat_hash_map<std::string, std::string> parsed_mappings;
+  CmdLineMapping parsed_mappings;
   parsed_mappings.reserve(mappings.size());
 
   for (const std::string& mapping : mappings) {
-    std::vector<std::string_view> kv = absl::StrSplit(mapping, '=');
+    absl::InlinedVector<std::string_view, 2> kv = absl::StrSplit(mapping, '=');
     if (kv.size() != 2) {
-      LOG(ERROR) << "Malformed command " << mapping << " for " << flag.Name()
+      LOG(ERROR) << "Malformed command '" << mapping << "' for " << flag.Name()
                  << ", expected key=value";
       exit(1);
     }
@@ -97,8 +100,7 @@ absl::flat_hash_map<std::string, std::string> ParseCmdlineArgMap(
       exit(1);
     }
 
-    const bool inserted = parsed_mappings.emplace(std::move(key), std::move(value)).second;
-    if (!allow_duplicates && !inserted) {
+    if (!parsed_mappings.emplace(std::move(key), std::move(value)).second) {
       LOG(ERROR) << "Duplicate insert to " << flag.Name() << " not allowed";
       exit(1);
     }
@@ -106,6 +108,19 @@ absl::flat_hash_map<std::string, std::string> ParseCmdlineArgMap(
   return parsed_mappings;
 }
 
+CmdLineMapping OriginalToAliasMap() {
+  CmdLineMapping original_to_alias;
+  CmdLineMapping alias_to_original = ParseCmdlineArgMap(FLAGS_command_alias);
+  original_to_alias.reserve(alias_to_original.size());
+  std::for_each(std::make_move_iterator(alias_to_original.begin()),
+                std::make_move_iterator(alias_to_original.end()),
+                [&original_to_alias](auto&& pair) {
+                  original_to_alias.emplace(std::move(pair.second), std::move(pair.first));
+                });
+
+  return original_to_alias;
+}
+
 }  // namespace
 
 CommandId::CommandId(const char* name, uint32_t mask, int8_t arity, int8_t first_key,
@@ -115,6 +130,17 @@ CommandId::CommandId(const char* name, uint32_t mask, int8_t arity, int8_t first
   implicit_acl_ = !acl_categories.has_value();
 }
 
+CommandId CommandId::Clone(const std::string_view name) const {
+  CommandId cloned =
+      CommandId{name.data(), opt_mask_, arity_, first_key_, last_key_, acl_categories_};
+  cloned.handler_ = handler_;
+  cloned.opt_mask_ = opt_mask_ | CO::HIDDEN;
+  cloned.acl_categories_ = acl_categories_;
+  cloned.implicit_acl_ = implicit_acl_;
+  cloned.is_alias_ = true;
+  return cloned;
+}
+
 bool CommandId::IsTransactional() const {
   if (first_key_ > 0 || (opt_mask_ & CO::GLOBAL_TRANS) || (opt_mask_ & CO::NO_KEY_TRANSACTIONAL))
     return true;
@@ -130,8 +156,7 @@ bool CommandId::IsMultiTransactional() const {
   return CO::IsTransKind(name()) || CO::IsEvalKind(name());
 }
 
-uint64_t CommandId::Invoke(CmdArgList args, const CommandContext& cmd_cntx,
-                           std::string_view orig_cmd_name) const {
+uint64_t CommandId::Invoke(CmdArgList args, const CommandContext& cmd_cntx) const {
   int64_t before = absl::GetCurrentTimeNanos();
   handler_(args, cmd_cntx);
   int64_t after = absl::GetCurrentTimeNanos();
@@ -139,7 +164,7 @@ uint64_t CommandId::Invoke(CmdArgList args, const CommandContext& cmd_cntx,
   ServerState* ss = ServerState::tlocal();  // Might have migrated thread, read after invocation
   int64_t execution_time_usec = (after - before) / 1000;
 
-  auto& ent = command_stats_[ss->thread_index()][orig_cmd_name];
+  auto& ent = command_stats_[ss->thread_index()];
 
   ++ent.first;
   ent.second += execution_time_usec;
@@ -169,7 +194,6 @@ optional<facade::ErrorReply> CommandId::Validate(CmdArgList tail_args) const {
 
 CommandRegistry::CommandRegistry() {
   cmd_rename_map_ = ParseCmdlineArgMap(FLAGS_rename_command);
-  cmd_aliases_ = ParseCmdlineArgMap(FLAGS_command_alias, true);
 
   for (string name : GetFlag(FLAGS_restricted_commands)) {
     restricted_cmds_.emplace(AsciiStrToUpper(name));
@@ -181,9 +205,20 @@ CommandRegistry::CommandRegistry() {
 }
 
 void CommandRegistry::Init(unsigned int thread_count) {
+  const CmdLineMapping original_to_alias = OriginalToAliasMap();
+  absl::flat_hash_map<std::string, CommandId> alias_to_command_id;
+  alias_to_command_id.reserve(original_to_alias.size());
   for (auto& [_, cmd] : cmd_map_) {
     cmd.Init(thread_count);
+    if (auto it = original_to_alias.find(cmd.name()); it != original_to_alias.end()) {
+      auto alias_cmd = cmd.Clone(it->second);
+      alias_cmd.Init(thread_count);
+      alias_to_command_id.insert({it->second, std::move(alias_cmd)});
+    }
   }
+  std::copy(std::make_move_iterator(alias_to_command_id.begin()),
+            std::make_move_iterator(alias_to_command_id.end()),
+            std::inserter(cmd_map_, cmd_map_.end()));
 }
 
 CommandRegistry& CommandRegistry::operator<<(CommandId cmd) {
@@ -212,7 +247,7 @@ CommandRegistry& CommandRegistry::operator<<(CommandId cmd) {
 
   if (!is_sub_command || absl::StartsWith(cmd.name(), "ACL")) {
     cmd.SetBitIndex(1ULL << bit_index_);
-    family_of_commands_.back().push_back(std::string(k));
+    family_of_commands_.back().emplace_back(k);
     ++bit_index_;
   } else {
     DCHECK(absl::StartsWith(k, family_of_commands_.back().back()));
@@ -266,10 +301,6 @@ std::pair<const CommandId*, ArgSlice> CommandRegistry::FindExtended(string_view
   return {res, tail_args};
 }
 
-bool CommandRegistry::IsAlias(std::string_view cmd) const {
-  return cmd_aliases_.contains(cmd);
-}
-
 namespace CO {
 
 const char* OptName(CO::CommandOpt fl) {
diff --git a/src/server/command_registry.h b/src/server/command_registry.h
index af22f05ced7b..949ebc7263be 100644
--- a/src/server/command_registry.h
+++ b/src/server/command_registry.h
@@ -71,9 +71,8 @@ static_assert(!IsEvalKind(""));
 
 };  // namespace CO
 
-// Per thread vector of command stats. Each entry is:
-// command invocation string -> {cmd_calls, cmd_latency_agg in usec}.
-using CmdCallStats = absl::flat_hash_map<std::string, std::pair<uint64_t, uint64_t>>;
+// Per thread vector of command stats. Each entry is {cmd_calls, cmd_latency_agg in usec}.
+using CmdCallStats = std::pair<uint64_t, uint64_t>;
 
 struct CommandContext {
   CommandContext(Transaction* _tx, facade::SinkReplyBuilder* _rb, ConnectionContext* cntx)
@@ -94,6 +93,8 @@ class CommandId : public facade::CommandId {
 
   CommandId(CommandId&&) = default;
 
+  [[nodiscard]] CommandId Clone(std::string_view name) const;
+
   void Init(unsigned thread_count) {
     command_stats_ = std::make_unique<CmdCallStats[]>(thread_count);
   }
@@ -103,10 +104,8 @@ class CommandId : public facade::CommandId {
   using ArgValidator = fu2::function_base<true, true, fu2::capacity_default, false, false,
                                           std::optional<facade::ErrorReply>(CmdArgList) const>;
 
-  // Invokes the command handler. Returns the invoke time in usec. The invoked_by parameter is set
-  // to the string passed in by user, if available. If not set, defaults to command name.
-  uint64_t Invoke(CmdArgList args, const CommandContext& cmd_cntx,
-                  std::string_view orig_cmd_name) const;
+  // Returns the invoke time in usec.
+  uint64_t Invoke(CmdArgList args, const CommandContext& cmd_cntx) const;
 
   // Returns error if validation failed, otherwise nullopt
   std::optional<facade::ErrorReply> Validate(CmdArgList tail_args) const;
@@ -144,7 +143,7 @@ class CommandId : public facade::CommandId {
   }
 
   void ResetStats(unsigned thread_index) {
-    command_stats_[thread_index].clear();
+    command_stats_[thread_index] = {0, 0};
   }
 
   CmdCallStats GetStats(unsigned thread_index) const {
@@ -156,11 +155,16 @@ class CommandId : public facade::CommandId {
       acl_categories_ |= mask;
   }
 
+  bool IsAlias() const {
+    return is_alias_;
+  }
+
  private:
   bool implicit_acl_;
   std::unique_ptr<CmdCallStats[]> command_stats_;
   Handler3 handler_;
   ArgValidator validator_;
+  bool is_alias_{false};
 };
 
 class CommandRegistry {
@@ -172,16 +176,8 @@ class CommandRegistry {
   CommandRegistry& operator<<(CommandId cmd);
 
   const CommandId* Find(std::string_view cmd) const {
-    if (const auto it = cmd_map_.find(cmd); it != cmd_map_.end()) {
-      return &it->second;
-    }
-
-    if (const auto it = cmd_aliases_.find(cmd); it != cmd_aliases_.end()) {
-      if (const auto alias_lookup = cmd_map_.find(it->second); alias_lookup != cmd_map_.end()) {
-        return &alias_lookup->second;
-      }
-    }
-    return nullptr;
+    auto it = cmd_map_.find(cmd);
+    return it == cmd_map_.end() ? nullptr : &it->second;
   }
 
   CommandId* Find(std::string_view cmd) {
@@ -203,17 +199,13 @@ class CommandRegistry {
     }
   }
 
-  void MergeCallStats(
-      unsigned thread_index,
-      std::function<void(std::string_view, const CmdCallStats::mapped_type&)> cb) const {
-    for (const auto& [_, cmd_id] : cmd_map_) {
-      for (const auto& [cmd_name, call_stats] : cmd_id.GetStats(thread_index)) {
-        if (call_stats.first == 0) {
-          continue;
-        }
-
-        cb(cmd_name, call_stats);
-      }
+  void MergeCallStats(unsigned thread_index,
+                      std::function<void(std::string_view, const CmdCallStats&)> cb) const {
+    for (const auto& k_v : cmd_map_) {
+      auto src = k_v.second.GetStats(thread_index);
+      if (src.first == 0)
+        continue;
+      cb(k_v.second.name(), src);
     }
   }
 
@@ -227,16 +219,9 @@ class CommandRegistry {
   std::pair<const CommandId*, facade::ArgSlice> FindExtended(std::string_view cmd,
                                                              facade::ArgSlice tail_args) const;
 
-  bool IsAlias(std::string_view cmd) const;
-
  private:
   absl::flat_hash_map<std::string, CommandId> cmd_map_;
   absl::flat_hash_map<std::string, std::string> cmd_rename_map_;
-  // Stores a mapping from alias to original command. During the find operation, the first lookup is
-  // done in the cmd_map_, then in the alias map. This results in two lookups but only for commands
-  // which are not in original map, ie either typos or aliases. While it would be faster, we cannot
-  // store iterators into cmd_map_ here as they may be invalidated on rehashing.
-  absl::flat_hash_map<std::string, std::string> cmd_aliases_;
   absl::flat_hash_set<std::string> restricted_cmds_;
   absl::flat_hash_set<std::string> oomdeny_cmds_;
 
diff --git a/src/server/dragonfly_test.cc b/src/server/dragonfly_test.cc
index 6ceb891422b8..8e7e80584b71 100644
--- a/src/server/dragonfly_test.cc
+++ b/src/server/dragonfly_test.cc
@@ -9,7 +9,7 @@ extern "C" {
 
 #include <absl/strings/ascii.h>
 #include <absl/strings/str_join.h>
-#include <absl/strings/str_split.h>
+#include <absl/strings/strip.h>
 #include <gmock/gmock.h>
 #include <reflex/matcher.h>
 
@@ -17,6 +17,7 @@ extern "C" {
 #include "base/gtest.h"
 #include "base/logging.h"
 #include "facade/facade_test.h"
+#include "server/conn_context.h"
 #include "server/main_service.h"
 #include "server/test_utils.h"
 
@@ -24,9 +25,9 @@ ABSL_DECLARE_FLAG(float, mem_defrag_threshold);
 ABSL_DECLARE_FLAG(float, mem_defrag_waste_threshold);
 ABSL_DECLARE_FLAG(uint32_t, mem_defrag_check_sec_interval);
 ABSL_DECLARE_FLAG(std::vector<std::string>, rename_command);
-ABSL_DECLARE_FLAG(std::vector<std::string>, command_alias);
 ABSL_DECLARE_FLAG(bool, lua_resp2_legacy_float);
 ABSL_DECLARE_FLAG(double, eviction_memory_budget_threshold);
+ABSL_DECLARE_FLAG(std::vector<std::string>, command_alias);
 
 namespace dfly {
 
@@ -118,7 +119,8 @@ class DflyRenameCommandTest : public DflyEngineTest {
         &FLAGS_rename_command,
         std::vector<std::string>({"flushall=myflushall", "flushdb=", "ping=abcdefghijklmnop"}));
   }
-  absl::FlagSaver saver_;
+
+  absl::FlagSaver _saver;
 };
 
 TEST_F(DflyRenameCommandTest, RenameCommand) {
@@ -848,9 +850,7 @@ TEST_F(DflyEngineTest, CommandMetricLabels) {
 class DflyCommandAliasTest : public DflyEngineTest {
  protected:
   DflyCommandAliasTest() {
-    // Test an interaction of rename and alias, where we rename and then add an alias on the rename
-    absl::SetFlag(&FLAGS_rename_command, {"ping=gnip"});
-    absl::SetFlag(&FLAGS_command_alias, {"___set=set", "___ping=gnip"});
+    absl::SetFlag(&FLAGS_command_alias, {"___set=set", "___ping=ping"});
   }
 
   absl::FlagSaver saver_;
@@ -861,19 +861,26 @@ TEST_F(DflyCommandAliasTest, Aliasing) {
   EXPECT_EQ(Run({"___SET", "a", "b"}), "OK");
   EXPECT_EQ(Run({"GET", "foo"}), "bar");
   EXPECT_EQ(Run({"GET", "a"}), "b");
-  // test the alias
   EXPECT_EQ(Run({"___ping"}), "PONG");
-  // test the rename
-  EXPECT_EQ(Run({"gnip"}), "PONG");
-  // the original command is not accessible
-  EXPECT_THAT(Run({"PING"}), ErrArg("unknown command `PING`"));
 
-  const Metrics metrics = GetMetrics();
+  Metrics metrics = GetMetrics();
   const auto& stats = metrics.cmd_stats_map;
+
   EXPECT_THAT(stats, Contains(Pair("___set", Key(1))));
   EXPECT_THAT(stats, Contains(Pair("set", Key(1))));
   EXPECT_THAT(stats, Contains(Pair("___ping", Key(1))));
   EXPECT_THAT(stats, Contains(Pair("get", Key(2))));
+
+  // test stats within multi-exec
+  EXPECT_EQ(Run({"multi"}), "OK");
+  EXPECT_EQ(Run({"___set", "a", "x"}), "QUEUED");
+  EXPECT_EQ(Run({"exec"}), "OK");
+
+  metrics = GetMetrics();
+  EXPECT_THAT(metrics.cmd_stats_map, Contains(Pair("___set", Key(2))));
+  EXPECT_THAT(metrics.cmd_stats_map, Contains(Pair("set", Key(1))));
+  EXPECT_THAT(metrics.cmd_stats_map, Contains(Pair("multi", Key(1))));
+  EXPECT_THAT(metrics.cmd_stats_map, Contains(Pair("exec", Key(1))));
 }
 
 }  // namespace dfly
diff --git a/src/server/main_service.cc b/src/server/main_service.cc
index 27c321539bdd..dc78fdd2edf2 100644
--- a/src/server/main_service.cc
+++ b/src/server/main_service.cc
@@ -4,6 +4,7 @@
 
 #include "server/main_service.h"
 
+#include "absl/strings/str_split.h"
 #include "facade/resp_expr.h"
 #include "util/fibers/synchronization.h"
 
@@ -1235,13 +1236,7 @@ void Service::DispatchCommand(ArgSlice args, SinkReplyBuilder* builder,
 
   dfly_cntx->cid = cid;
 
-  // If cmd is an alias, pass it to Invoke so the stats are updated against the alias. By defaults
-  // stats will be updated for cid.name
-  std::optional<std::string_view> orig_cmd_name = std::nullopt;
-  if (registry_.IsAlias(cmd)) {
-    orig_cmd_name = cmd;
-  }
-  if (!InvokeCmd(cid, args_no_cmd, builder, dfly_cntx, orig_cmd_name)) {
+  if (!InvokeCmd(cid, args_no_cmd, builder, dfly_cntx)) {
     builder->SendError("Internal Error");
     builder->CloseConnection();
   }
@@ -1302,7 +1297,7 @@ OpResult<void> OpTrackKeys(const OpArgs slice_args, const facade::Connection::We
 }
 
 bool Service::InvokeCmd(const CommandId* cid, CmdArgList tail_args, SinkReplyBuilder* builder,
-                        ConnectionContext* cntx, std::optional<std::string_view> orig_cmd_name) {
+                        ConnectionContext* cntx) {
   DCHECK(cid);
   DCHECK(!cid->Validate(tail_args));
 
@@ -1351,8 +1346,7 @@ bool Service::InvokeCmd(const CommandId* cid, CmdArgList tail_args, SinkReplyBui
   auto last_error = builder->ConsumeLastError();
   DCHECK(last_error.empty());
   try {
-    invoke_time_usec = cid->Invoke(tail_args, CommandContext{tx, builder, cntx},
-                                   orig_cmd_name.value_or(cid->name()));
+    invoke_time_usec = cid->Invoke(tail_args, CommandContext{tx, builder, cntx});
   } catch (std::exception& e) {
     LOG(ERROR) << "Internal error, system probably unstable " << e.what();
     return false;
diff --git a/src/server/main_service.h b/src/server/main_service.h
index f174c37cb9cc..32a7386deca8 100644
--- a/src/server/main_service.h
+++ b/src/server/main_service.h
@@ -45,8 +45,7 @@ class Service : public facade::ServiceInterface {
 
   // Check VerifyCommandExecution and invoke command with args
   bool InvokeCmd(const CommandId* cid, CmdArgList tail_args, facade::SinkReplyBuilder* builder,
-                 ConnectionContext* reply_cntx,
-                 std::optional<std::string_view> orig_cmd_name = std::nullopt);
+                 ConnectionContext* reply_cntx);
 
   // Verify command can be executed now (check out of memory), always called immediately before
   // execution
diff --git a/src/server/server_family.cc b/src/server/server_family.cc
index 73cac4598ff8..f4a7b88a8129 100644
--- a/src/server/server_family.cc
+++ b/src/server/server_family.cc
@@ -2212,8 +2212,7 @@ Metrics ServerFamily::GetMetrics(Namespace* ns) const {
 
   uint64_t start = absl::GetCurrentTimeNanos();
 
-  auto cmd_stat_cb = [&dest = result.cmd_stats_map](string_view name,
-                                                    const CmdCallStats::mapped_type& stat) {
+  auto cmd_stat_cb = [&dest = result.cmd_stats_map](string_view name, const CmdCallStats& stat) {
     auto& [calls, sum] = dest[absl::AsciiStrToLower(name)];
     calls += stat.first;
     sum += stat.second;
diff --git a/tests/dragonfly/replication_test.py b/tests/dragonfly/replication_test.py
index b351325122f0..841d8667e76c 100644
--- a/tests/dragonfly/replication_test.py
+++ b/tests/dragonfly/replication_test.py
@@ -2959,6 +2959,7 @@ async def test_preempt_in_atomic_section_of_heartbeat(df_factory: DflyInstanceFa
     await fill_task
 
 
+@pytest.mark.skip("temporarily skipped")
 async def test_bug_in_json_memory_tracking(df_factory: DflyInstanceFactory):
     """
     This test reproduces a bug in the JSON memory tracking.


Reviews:
user: login: abhijat
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T07:39:13Z
user: login: romange
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-16T20:15:13Z
user: login: romange
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-16T20:17:33Z
user: login: romange
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-16T20:18:01Z
user: login: romange
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-16T20:24:37Z
user: login: abhijat
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T04:14:21Z
user: login: abhijat
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T04:17:43Z
user: login: abhijat
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T04:27:04Z
user: login: abhijat
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T04:30:49Z
user: login: abhijat
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T04:33:06Z
user: login: abhijat
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T05:31:49Z
user: login: abhijat
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T05:32:25Z
user: login: romange
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-17T05:35:52Z
user: login: romange
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-17T05:39:08Z
user: login: romange
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-17T05:40:16Z
user: login: romange
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-17T05:47:33Z
user: login: abhijat
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T06:54:24Z
user: login: abhijat
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T06:57:33Z
user: login: kostasrim
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T09:49:25Z
user: login: kostasrim
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T09:57:50Z
user: login: abhijat
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T10:46:15Z
user: login: kostasrim
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T11:21:06Z
user: login: abhijat
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T07:27:13Z
user: login: abhijat
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T08:10:07Z
user: login: abhijat
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T08:10:40Z
user: login: adiholden
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-20T09:09:54Z
user: login: adiholden
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-20T09:10:43Z
user: login: abhijat
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T03:21:01Z
user: login: abhijat
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T03:21:07Z
user: login: adiholden
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T06:43:28Z
user: login: adiholden
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T07:21:14Z

Review comments:
diff_hunk: @@ -191,35 +204,66 @@ CommandRegistry& CommandRegistry::operator<<(CommandId cmd) {
 
   absl::InlinedVector<std::string_view, 2> maybe_subcommand = StrSplit(cmd.name(), " ");
   const bool is_sub_command = maybe_subcommand.size() == 2;
-  if (const auto it = cmd_rename_map_.find(maybe_subcommand.front()); it != cmd_rename_map_.end()) {
+  auto it = cmd_rename_map_.find(maybe_subcommand.front());
+  if (it != cmd_rename_map_.end()) {
     if (it->second.empty()) {
       return *this;  // Incase of empty string we want to remove the command from registry.
     }
     k = is_sub_command ? absl::StrCat(it->second, " ", maybe_subcommand[1]) : it->second;
   }
 
+  std::optional<CommandId> cloned = std::nullopt;
+  for (const auto& [alias, target] : alias_map_) {
+    if (target == k) {
+      cloned.emplace(cmd.Clone(alias));
+      alias_map_.erase(alias);
+      break;
+    }
+  }
+
   if (restricted_cmds_.find(k) != restricted_cmds_.end()) {
     cmd.SetRestricted(true);
+    if (cloned.has_value()) {
+      cloned->SetRestricted(true);
+    }
   }
 
   if (oomdeny_cmds_.find(k) != oomdeny_cmds_.end()) {
     cmd.SetFlag(CO::DENYOOM);
+    if (cloned.has_value()) {
+      cloned->SetFlag(CO::DENYOOM);
+    }
   }
 
   cmd.SetFamily(family_of_commands_.size() - 1);
-  if (acl_category_)
+  if (cloned.has_value()) {
+    cloned->SetFamily(cmd.GetFamily());
+  }
+  if (acl_category_) {
     cmd.SetAclCategory(*acl_category_);
+    if (cloned.has_value()) {
+      cloned->SetAclCategory(*acl_category_);
+    }
+  }
 
   if (!is_sub_command || absl::StartsWith(cmd.name(), "ACL")) {
     cmd.SetBitIndex(1ULL << bit_index_);
-    family_of_commands_.back().push_back(std::string(k));
+    family_of_commands_.back().emplace_back(k);
     ++bit_index_;
+
+    if (cloned) {
+      cloned->SetBitIndex(1ULL << bit_index_);
+      family_of_commands_.back().emplace_back(cloned->name());
+      ++bit_index_;
+    }
   } else {
     DCHECK(absl::StartsWith(k, family_of_commands_.back().back()));
     cmd.SetBitIndex(1ULL << (bit_index_ - 1));
   }
path: src/server/command_registry.cc
user: login: abhijat
body: This code path does not seem to be taken by any commands in the tree afaics (is sub command and is not acl)
created_at: 2025-04-15T07:39:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -227,16 +217,10 @@ class CommandRegistry {
   std::pair<const CommandId*, facade::ArgSlice> FindExtended(std::string_view cmd,
                                                              facade::ArgSlice tail_args) const;
 
-  bool IsAlias(std::string_view cmd) const;
-
  private:
   absl::flat_hash_map<std::string, CommandId> cmd_map_;
   absl::flat_hash_map<std::string, std::string> cmd_rename_map_;
-  // Stores a mapping from alias to original command. During the find operation, the first lookup is
-  // done in the cmd_map_, then in the alias map. This results in two lookups but only for commands
-  // which are not in original map, ie either typos or aliases. While it would be faster, we cannot
-  // store iterators into cmd_map_ here as they may be invalidated on rehashing.
-  absl::flat_hash_map<std::string, std::string> cmd_aliases_;
+  absl::flat_hash_map<std::string, std::string> alias_map_;
path: src/server/command_registry.h
user: login: romange
body: why do you need it as a class variable?
created_at: 2025-04-16T20:15:13Z
author_association: COLLABORATOR
diff_hunk: @@ -191,35 +204,66 @@ CommandRegistry& CommandRegistry::operator<<(CommandId cmd) {
 
   absl::InlinedVector<std::string_view, 2> maybe_subcommand = StrSplit(cmd.name(), " ");
   const bool is_sub_command = maybe_subcommand.size() == 2;
-  if (const auto it = cmd_rename_map_.find(maybe_subcommand.front()); it != cmd_rename_map_.end()) {
+  auto it = cmd_rename_map_.find(maybe_subcommand.front());
+  if (it != cmd_rename_map_.end()) {
     if (it->second.empty()) {
       return *this;  // Incase of empty string we want to remove the command from registry.
     }
     k = is_sub_command ? absl::StrCat(it->second, " ", maybe_subcommand[1]) : it->second;
   }
 
+  std::optional<CommandId> cloned = std::nullopt;
path: src/server/command_registry.cc
user: login: romange
body: why to do it here and not in `CommandRegistry::Init` when all commands are registered?
created_at: 2025-04-16T20:17:33Z
author_association: COLLABORATOR
diff_hunk: @@ -191,35 +204,66 @@ CommandRegistry& CommandRegistry::operator<<(CommandId cmd) {
 
   absl::InlinedVector<std::string_view, 2> maybe_subcommand = StrSplit(cmd.name(), " ");
   const bool is_sub_command = maybe_subcommand.size() == 2;
-  if (const auto it = cmd_rename_map_.find(maybe_subcommand.front()); it != cmd_rename_map_.end()) {
+  auto it = cmd_rename_map_.find(maybe_subcommand.front());
+  if (it != cmd_rename_map_.end()) {
     if (it->second.empty()) {
       return *this;  // Incase of empty string we want to remove the command from registry.
     }
     k = is_sub_command ? absl::StrCat(it->second, " ", maybe_subcommand[1]) : it->second;
   }
 
+  std::optional<CommandId> cloned = std::nullopt;
+  for (const auto& [alias, target] : alias_map_) {
path: src/server/command_registry.cc
user: login: romange
body: what's the reason for doing `O(n)` lookup here?
created_at: 2025-04-16T20:18:00Z
author_association: COLLABORATOR
diff_hunk: @@ -115,6 +116,19 @@ CommandId::CommandId(const char* name, uint32_t mask, int8_t arity, int8_t first
   implicit_acl_ = !acl_categories.has_value();
 }
 
+CommandId::CommandId(const char* name, uint32_t mask, int8_t arity, int8_t first_key,
path: src/server/command_registry.cc
user: login: romange
body: you do not need this c'-tor
created_at: 2025-04-16T20:24:37Z
author_association: COLLABORATOR
diff_hunk: @@ -191,35 +204,66 @@ CommandRegistry& CommandRegistry::operator<<(CommandId cmd) {
 
   absl::InlinedVector<std::string_view, 2> maybe_subcommand = StrSplit(cmd.name(), " ");
   const bool is_sub_command = maybe_subcommand.size() == 2;
-  if (const auto it = cmd_rename_map_.find(maybe_subcommand.front()); it != cmd_rename_map_.end()) {
+  auto it = cmd_rename_map_.find(maybe_subcommand.front());
+  if (it != cmd_rename_map_.end()) {
     if (it->second.empty()) {
       return *this;  // Incase of empty string we want to remove the command from registry.
     }
     k = is_sub_command ? absl::StrCat(it->second, " ", maybe_subcommand[1]) : it->second;
   }
 
+  std::optional<CommandId> cloned = std::nullopt;
path: src/server/command_registry.cc
user: login: abhijat
body: By the time `CommandRegistry::Init` is called, `AclFamily::Register` has already run where the acl index is built. We have to add the cloned command to it as far as I can see. 

Another problem with adding the clone after all the families are registered is that we need the correct family and bit index for the cloned command. These are reset after each new family is started.

We can get the original command's family but we need a unique bit index for the cloned command which will be valid for the family. The safest way is to add the cloned command during registration where a new bit index is calculated for each command.
created_at: 2025-04-17T04:14:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -227,16 +217,10 @@ class CommandRegistry {
   std::pair<const CommandId*, facade::ArgSlice> FindExtended(std::string_view cmd,
                                                              facade::ArgSlice tail_args) const;
 
-  bool IsAlias(std::string_view cmd) const;
-
  private:
   absl::flat_hash_map<std::string, CommandId> cmd_map_;
   absl::flat_hash_map<std::string, std::string> cmd_rename_map_;
-  // Stores a mapping from alias to original command. During the find operation, the first lookup is
-  // done in the cmd_map_, then in the alias map. This results in two lookups but only for commands
-  // which are not in original map, ie either typos or aliases. While it would be faster, we cannot
-  // store iterators into cmd_map_ here as they may be invalidated on rehashing.
-  absl::flat_hash_map<std::string, std::string> cmd_aliases_;
+  absl::flat_hash_map<std::string, std::string> alias_map_;
path: src/server/command_registry.h
user: login: abhijat
body: Since we are looking up aliases during command registration per command, to avoid doing `GetFlag` each time a command is registered I used a class variable here. 

I think it is also possible to use a function with a block scope static variable which is initialized with `GetFlag` once. I will try this and remove the field.
created_at: 2025-04-17T04:17:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -115,6 +116,19 @@ CommandId::CommandId(const char* name, uint32_t mask, int8_t arity, int8_t first
   implicit_acl_ = !acl_categories.has_value();
 }
 
+CommandId::CommandId(const char* name, uint32_t mask, int8_t arity, int8_t first_key,
path: src/server/command_registry.cc
user: login: abhijat
body: The reason for adding this is that if I reuse the original constructor and pass the original command's `opt_mask_` and `acl_categories_` to it, then there are two problems:

* `ImplicitCategories(mask)` is called on the input mask for the clone. This means we are calling the same function twice. The first output was used as mask in original, and is now used as input for second call whose result is used as mask for the clone. Right now `ImplicitCategories` looks safe to call multiple times but this can cause bugs later if the function becomes non idempotent.
* The `acl_categories_` value passed into the c-tor causes `implicit_acl_` to always be false in the cloned command where it could be true in the original. This is incorrect behavior. 

For the second point we could figure out if originally an `acl_categories_` was passed or not by checking `implicit_acl_` of the original, but I think it is simpler to add this new constructor with passes through all values unchanged, rather than doing these elaborate and error prone checks. 
created_at: 2025-04-17T04:27:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -191,35 +204,66 @@ CommandRegistry& CommandRegistry::operator<<(CommandId cmd) {
 
   absl::InlinedVector<std::string_view, 2> maybe_subcommand = StrSplit(cmd.name(), " ");
   const bool is_sub_command = maybe_subcommand.size() == 2;
-  if (const auto it = cmd_rename_map_.find(maybe_subcommand.front()); it != cmd_rename_map_.end()) {
+  auto it = cmd_rename_map_.find(maybe_subcommand.front());
+  if (it != cmd_rename_map_.end()) {
     if (it->second.empty()) {
       return *this;  // Incase of empty string we want to remove the command from registry.
     }
     k = is_sub_command ? absl::StrCat(it->second, " ", maybe_subcommand[1]) : it->second;
   }
 
+  std::optional<CommandId> cloned = std::nullopt;
+  for (const auto& [alias, target] : alias_map_) {
path: src/server/command_registry.cc
user: login: abhijat
body: I thought of using a `command -> alias` map originally which will be `O(1)` but if user aliases a command to multiple keywords, which seems a valid use case, then only the last alias will be picked up. If we assume and assert that one command is aliased only once then we can have a constant time lookup. 

Otherwise we can add another map with the reverse lookup or use a map with `command -> vector of aliases`. I will look into fixing this.
created_at: 2025-04-17T04:30:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -191,35 +204,66 @@ CommandRegistry& CommandRegistry::operator<<(CommandId cmd) {
 
   absl::InlinedVector<std::string_view, 2> maybe_subcommand = StrSplit(cmd.name(), " ");
   const bool is_sub_command = maybe_subcommand.size() == 2;
-  if (const auto it = cmd_rename_map_.find(maybe_subcommand.front()); it != cmd_rename_map_.end()) {
+  auto it = cmd_rename_map_.find(maybe_subcommand.front());
+  if (it != cmd_rename_map_.end()) {
     if (it->second.empty()) {
       return *this;  // Incase of empty string we want to remove the command from registry.
     }
     k = is_sub_command ? absl::StrCat(it->second, " ", maybe_subcommand[1]) : it->second;
   }
 
+  std::optional<CommandId> cloned = std::nullopt;
+  for (const auto& [alias, target] : alias_map_) {
path: src/server/command_registry.cc
user: login: abhijat
body: Actually the current behavior is incorrect anyway. If we have multiple aliases mapped to one command, then in the lookup we only process the first alias and then break. So this won't process subsequent mappings. I will change this. 
created_at: 2025-04-17T04:33:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -227,16 +217,10 @@ class CommandRegistry {
   std::pair<const CommandId*, facade::ArgSlice> FindExtended(std::string_view cmd,
                                                              facade::ArgSlice tail_args) const;
 
-  bool IsAlias(std::string_view cmd) const;
-
  private:
   absl::flat_hash_map<std::string, CommandId> cmd_map_;
   absl::flat_hash_map<std::string, std::string> cmd_rename_map_;
-  // Stores a mapping from alias to original command. During the find operation, the first lookup is
-  // done in the cmd_map_, then in the alias map. This results in two lookups but only for commands
-  // which are not in original map, ie either typos or aliases. While it would be faster, we cannot
-  // store iterators into cmd_map_ here as they may be invalidated on rehashing.
-  absl::flat_hash_map<std::string, std::string> cmd_aliases_;
+  absl::flat_hash_map<std::string, std::string> alias_map_;
path: src/server/command_registry.h
user: login: abhijat
body: I moved the map to a static variable and removed from class, I did have to add a helper to reset the map, as otherwise the tests fail because the map is initialized only once in other tests and by the time alias unit test runs the map is not updated. 
created_at: 2025-04-17T05:31:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -191,35 +204,66 @@ CommandRegistry& CommandRegistry::operator<<(CommandId cmd) {
 
   absl::InlinedVector<std::string_view, 2> maybe_subcommand = StrSplit(cmd.name(), " ");
   const bool is_sub_command = maybe_subcommand.size() == 2;
-  if (const auto it = cmd_rename_map_.find(maybe_subcommand.front()); it != cmd_rename_map_.end()) {
+  auto it = cmd_rename_map_.find(maybe_subcommand.front());
+  if (it != cmd_rename_map_.end()) {
     if (it->second.empty()) {
       return *this;  // Incase of empty string we want to remove the command from registry.
     }
     k = is_sub_command ? absl::StrCat(it->second, " ", maybe_subcommand[1]) : it->second;
   }
 
+  std::optional<CommandId> cloned = std::nullopt;
+  for (const auto& [alias, target] : alias_map_) {
path: src/server/command_registry.cc
user: login: abhijat
body: Changed it to map command to alias, only one alias per command is allowed. The lookup will be constant time now.
created_at: 2025-04-17T05:32:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -115,6 +116,19 @@ CommandId::CommandId(const char* name, uint32_t mask, int8_t arity, int8_t first
   implicit_acl_ = !acl_categories.has_value();
 }
 
+CommandId::CommandId(const char* name, uint32_t mask, int8_t arity, int8_t first_key,
path: src/server/command_registry.cc
user: login: romange
body: you still copy the handler function outside of the c'tor - your Clone function can fix whatever needs to be fixed anyways, there is no need for another special function imho.
created_at: 2025-04-17T05:35:52Z
author_association: COLLABORATOR
diff_hunk: @@ -191,35 +204,66 @@ CommandRegistry& CommandRegistry::operator<<(CommandId cmd) {
 
   absl::InlinedVector<std::string_view, 2> maybe_subcommand = StrSplit(cmd.name(), " ");
   const bool is_sub_command = maybe_subcommand.size() == 2;
-  if (const auto it = cmd_rename_map_.find(maybe_subcommand.front()); it != cmd_rename_map_.end()) {
+  auto it = cmd_rename_map_.find(maybe_subcommand.front());
+  if (it != cmd_rename_map_.end()) {
     if (it->second.empty()) {
       return *this;  // Incase of empty string we want to remove the command from registry.
     }
     k = is_sub_command ? absl::StrCat(it->second, " ", maybe_subcommand[1]) : it->second;
   }
 
+  std::optional<CommandId> cloned = std::nullopt;
path: src/server/command_registry.cc
user: login: romange
body: Please note that aliases will be used only internally by management plane. We do not care of ACL semantics as long as they can run on the admin port. the only purpose of aliases to be able to filter out commands coming from CP from all the other commands. @kostasrim  can we somehow omit/shortcircuit the ACL here?
created_at: 2025-04-17T05:39:08Z
author_association: COLLABORATOR
diff_hunk: @@ -227,16 +217,10 @@ class CommandRegistry {
   std::pair<const CommandId*, facade::ArgSlice> FindExtended(std::string_view cmd,
                                                              facade::ArgSlice tail_args) const;
 
-  bool IsAlias(std::string_view cmd) const;
-
  private:
   absl::flat_hash_map<std::string, CommandId> cmd_map_;
   absl::flat_hash_map<std::string, std::string> cmd_rename_map_;
-  // Stores a mapping from alias to original command. During the find operation, the first lookup is
-  // done in the cmd_map_, then in the alias map. This results in two lookups but only for commands
-  // which are not in original map, ie either typos or aliases. While it would be faster, we cannot
-  // store iterators into cmd_map_ here as they may be invalidated on rehashing.
-  absl::flat_hash_map<std::string, std::string> cmd_aliases_;
+  absl::flat_hash_map<std::string, std::string> alias_map_;
path: src/server/command_registry.h
user: login: romange
body: if we move the logic to Init function - it could be local variable. Let's see if we can solve the ACL stuff
created_at: 2025-04-17T05:40:16Z
author_association: COLLABORATOR
diff_hunk: @@ -191,35 +204,66 @@ CommandRegistry& CommandRegistry::operator<<(CommandId cmd) {
 
   absl::InlinedVector<std::string_view, 2> maybe_subcommand = StrSplit(cmd.name(), " ");
   const bool is_sub_command = maybe_subcommand.size() == 2;
-  if (const auto it = cmd_rename_map_.find(maybe_subcommand.front()); it != cmd_rename_map_.end()) {
+  auto it = cmd_rename_map_.find(maybe_subcommand.front());
+  if (it != cmd_rename_map_.end()) {
     if (it->second.empty()) {
       return *this;  // Incase of empty string we want to remove the command from registry.
     }
     k = is_sub_command ? absl::StrCat(it->second, " ", maybe_subcommand[1]) : it->second;
   }
 
+  std::optional<CommandId> cloned = std::nullopt;
path: src/server/command_registry.cc
user: login: romange
body: @kostasrim  can we do something here so that we would be able to clone aliases commands in CommandRegistry::Init  ?
created_at: 2025-04-17T05:47:33Z
author_association: COLLABORATOR
diff_hunk: @@ -191,35 +204,66 @@ CommandRegistry& CommandRegistry::operator<<(CommandId cmd) {
 
   absl::InlinedVector<std::string_view, 2> maybe_subcommand = StrSplit(cmd.name(), " ");
   const bool is_sub_command = maybe_subcommand.size() == 2;
-  if (const auto it = cmd_rename_map_.find(maybe_subcommand.front()); it != cmd_rename_map_.end()) {
+  auto it = cmd_rename_map_.find(maybe_subcommand.front());
+  if (it != cmd_rename_map_.end()) {
     if (it->second.empty()) {
       return *this;  // Incase of empty string we want to remove the command from registry.
     }
     k = is_sub_command ? absl::StrCat(it->second, " ", maybe_subcommand[1]) : it->second;
   }
 
+  std::optional<CommandId> cloned = std::nullopt;
path: src/server/command_registry.cc
user: login: abhijat
body: Perhaps the bit index for the cloned command can be the same as that for the original? We set the bit index in the acl "category to command map", so that bit would be set twice which should be ok? 

If we can share the bit index between original and cloned, we can get all the information from original command getters, and do the cloning right before this line https://github.com/dragonflydb/dragonfly/blob/129c92c1a3a58daa5228508725e2f2e50cac74f9/src/server/main_service.cc#L2708 in `Service::RegisterCommands` by looping over currently built command map. 
created_at: 2025-04-17T06:54:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -115,6 +116,19 @@ CommandId::CommandId(const char* name, uint32_t mask, int8_t arity, int8_t first
   implicit_acl_ = !acl_categories.has_value();
 }
 
+CommandId::CommandId(const char* name, uint32_t mask, int8_t arity, int8_t first_key,
path: src/server/command_registry.cc
user: login: abhijat
body: removed
created_at: 2025-04-17T06:57:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -191,35 +204,66 @@ CommandRegistry& CommandRegistry::operator<<(CommandId cmd) {
 
   absl::InlinedVector<std::string_view, 2> maybe_subcommand = StrSplit(cmd.name(), " ");
   const bool is_sub_command = maybe_subcommand.size() == 2;
-  if (const auto it = cmd_rename_map_.find(maybe_subcommand.front()); it != cmd_rename_map_.end()) {
+  auto it = cmd_rename_map_.find(maybe_subcommand.front());
+  if (it != cmd_rename_map_.end()) {
     if (it->second.empty()) {
       return *this;  // Incase of empty string we want to remove the command from registry.
     }
     k = is_sub_command ? absl::StrCat(it->second, " ", maybe_subcommand[1]) : it->second;
   }
 
+  std::optional<CommandId> cloned = std::nullopt;
path: src/server/command_registry.cc
user: login: kostasrim
body: @romange @abhijat 

Each family has 64 bits available for each command in the family. Under any scenario, we should not extend the bit per family for each cloned command as it's a scarce resource and I want to keep it empty for future additions. In other words, that `++bit_index` for cloned commands is problematic not only because it consumes what's left for the future but also is unbounded (what if somebody created 100 aliases ? Not realistic but still it will overflow the bits.

I agree with @romange that we should ignore altogether those aliases unless they are called from the admin port.
So:

* We `skip acl validation` on admin port (e.g. when IsPriveledged() is true we set `skip_acl_validation` in `ConnectionContext` ctor). This is already handled -- there is no ACL auth on admin port!

* We add a flag `is_cloned` on cid to denote that the command is a clone

* Within `IsUserAllowedToInvokeCmd()` we check if `cid->IsClone()` and we reject it (we first check if `skip_acl_validation==true` upon which we return true, see the implementation it's already hadled).

* There are 3 uses of `registry` inside acl family. Namely when we parse acl's (when user issues ACL SETUSER). We need to reject the clones. This is easy, just add an extra check `cid->isClone()` and reject the command if it's a clone during any call of any of the acl family commands. 

Unless I am missing something the above should give us the right semantics:

* reject clones/aliases unless they run on admin port


created_at: 2025-04-17T09:49:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -191,35 +204,66 @@ CommandRegistry& CommandRegistry::operator<<(CommandId cmd) {
 
   absl::InlinedVector<std::string_view, 2> maybe_subcommand = StrSplit(cmd.name(), " ");
   const bool is_sub_command = maybe_subcommand.size() == 2;
-  if (const auto it = cmd_rename_map_.find(maybe_subcommand.front()); it != cmd_rename_map_.end()) {
+  auto it = cmd_rename_map_.find(maybe_subcommand.front());
+  if (it != cmd_rename_map_.end()) {
     if (it->second.empty()) {
       return *this;  // Incase of empty string we want to remove the command from registry.
     }
     k = is_sub_command ? absl::StrCat(it->second, " ", maybe_subcommand[1]) : it->second;
   }
 
+  std::optional<CommandId> cloned = std::nullopt;
path: src/server/command_registry.cc
user: login: kostasrim
body: p.s. now we can do whatever we want in `CommandRegistry::Init` since we bypass the acl subsystem
created_at: 2025-04-17T09:57:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -191,35 +204,66 @@ CommandRegistry& CommandRegistry::operator<<(CommandId cmd) {
 
   absl::InlinedVector<std::string_view, 2> maybe_subcommand = StrSplit(cmd.name(), " ");
   const bool is_sub_command = maybe_subcommand.size() == 2;
-  if (const auto it = cmd_rename_map_.find(maybe_subcommand.front()); it != cmd_rename_map_.end()) {
+  auto it = cmd_rename_map_.find(maybe_subcommand.front());
+  if (it != cmd_rename_map_.end()) {
     if (it->second.empty()) {
       return *this;  // Incase of empty string we want to remove the command from registry.
     }
     k = is_sub_command ? absl::StrCat(it->second, " ", maybe_subcommand[1]) : it->second;
   }
 
+  std::optional<CommandId> cloned = std::nullopt;
path: src/server/command_registry.cc
user: login: abhijat
body: Should a log be added in `IsUserAllowedToInvokeCommand` like it is in other code paths, if the command is clone, or is it an early `false` return?
created_at: 2025-04-17T10:46:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -191,35 +204,66 @@ CommandRegistry& CommandRegistry::operator<<(CommandId cmd) {
 
   absl::InlinedVector<std::string_view, 2> maybe_subcommand = StrSplit(cmd.name(), " ");
   const bool is_sub_command = maybe_subcommand.size() == 2;
-  if (const auto it = cmd_rename_map_.find(maybe_subcommand.front()); it != cmd_rename_map_.end()) {
+  auto it = cmd_rename_map_.find(maybe_subcommand.front());
+  if (it != cmd_rename_map_.end()) {
     if (it->second.empty()) {
       return *this;  // Incase of empty string we want to remove the command from registry.
     }
     k = is_sub_command ? absl::StrCat(it->second, " ", maybe_subcommand[1]) : it->second;
   }
 
+  std::optional<CommandId> cloned = std::nullopt;
path: src/server/command_registry.cc
user: login: kostasrim
body: no log, we should return false
created_at: 2025-04-17T11:21:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -191,35 +204,66 @@ CommandRegistry& CommandRegistry::operator<<(CommandId cmd) {
 
   absl::InlinedVector<std::string_view, 2> maybe_subcommand = StrSplit(cmd.name(), " ");
   const bool is_sub_command = maybe_subcommand.size() == 2;
-  if (const auto it = cmd_rename_map_.find(maybe_subcommand.front()); it != cmd_rename_map_.end()) {
+  auto it = cmd_rename_map_.find(maybe_subcommand.front());
+  if (it != cmd_rename_map_.end()) {
     if (it->second.empty()) {
       return *this;  // Incase of empty string we want to remove the command from registry.
     }
     k = is_sub_command ? absl::StrCat(it->second, " ", maybe_subcommand[1]) : it->second;
   }
 
+  std::optional<CommandId> cloned = std::nullopt;
path: src/server/command_registry.cc
user: login: abhijat
body: I have implemented checks in most of these code paths. I will add some tests where I can.
created_at: 2025-04-18T07:27:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -227,16 +217,10 @@ class CommandRegistry {
   std::pair<const CommandId*, facade::ArgSlice> FindExtended(std::string_view cmd,
                                                              facade::ArgSlice tail_args) const;
 
-  bool IsAlias(std::string_view cmd) const;
-
  private:
   absl::flat_hash_map<std::string, CommandId> cmd_map_;
   absl::flat_hash_map<std::string, std::string> cmd_rename_map_;
-  // Stores a mapping from alias to original command. During the find operation, the first lookup is
-  // done in the cmd_map_, then in the alias map. This results in two lookups but only for commands
-  // which are not in original map, ie either typos or aliases. While it would be faster, we cannot
-  // store iterators into cmd_map_ here as they may be invalidated on rehashing.
-  absl::flat_hash_map<std::string, std::string> cmd_aliases_;
+  absl::flat_hash_map<std::string, std::string> alias_map_;
path: src/server/command_registry.h
user: login: abhijat
body: moved everything to init function and local variable derived from absl flag
created_at: 2025-04-18T08:10:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -191,35 +204,66 @@ CommandRegistry& CommandRegistry::operator<<(CommandId cmd) {
 
   absl::InlinedVector<std::string_view, 2> maybe_subcommand = StrSplit(cmd.name(), " ");
   const bool is_sub_command = maybe_subcommand.size() == 2;
-  if (const auto it = cmd_rename_map_.find(maybe_subcommand.front()); it != cmd_rename_map_.end()) {
+  auto it = cmd_rename_map_.find(maybe_subcommand.front());
+  if (it != cmd_rename_map_.end()) {
     if (it->second.empty()) {
       return *this;  // Incase of empty string we want to remove the command from registry.
     }
     k = is_sub_command ? absl::StrCat(it->second, " ", maybe_subcommand[1]) : it->second;
   }
 
+  std::optional<CommandId> cloned = std::nullopt;
path: src/server/command_registry.cc
user: login: abhijat
body: I have added a test for acl setuser and dryrun code paths
created_at: 2025-04-18T08:10:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -181,9 +203,19 @@ CommandRegistry::CommandRegistry() {
 }
 
 void CommandRegistry::Init(unsigned int thread_count) {
+  const auto alias_mappings = AliasMap();
path: src/server/command_registry.cc
user: login: adiholden
body: please use CmdLineMapping instead of auto
Also I would rename the 
alias_mappings -> origing_to_alias_cmd
and AliasMap -> GetOriginToAliasMap
created_at: 2025-04-20T09:09:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -97,15 +99,26 @@ absl::flat_hash_map<std::string, std::string> ParseCmdlineArgMap(
       exit(1);
     }
 
-    const bool inserted = parsed_mappings.emplace(std::move(key), std::move(value)).second;
-    if (!allow_duplicates && !inserted) {
+    if (!parsed_mappings.emplace(std::move(key), std::move(value)).second) {
       LOG(ERROR) << "Duplicate insert to " << flag.Name() << " not allowed";
       exit(1);
     }
   }
   return parsed_mappings;
 }
 
+CmdLineMapping AliasMap() {
+  CmdLineMapping alias_map;
+  CmdLineMapping orig_map = ParseCmdlineArgMap(FLAGS_command_alias);
path: src/server/command_registry.cc
user: login: adiholden
body: rename
orig_map -> alias_to_origin_cmd
alias_map -> origin_to_alias_cmd
created_at: 2025-04-20T09:10:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -181,9 +203,19 @@ CommandRegistry::CommandRegistry() {
 }
 
 void CommandRegistry::Init(unsigned int thread_count) {
+  const auto alias_mappings = AliasMap();
path: src/server/command_registry.cc
user: login: abhijat
body: fixed
created_at: 2025-04-21T03:21:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -97,15 +99,26 @@ absl::flat_hash_map<std::string, std::string> ParseCmdlineArgMap(
       exit(1);
     }
 
-    const bool inserted = parsed_mappings.emplace(std::move(key), std::move(value)).second;
-    if (!allow_duplicates && !inserted) {
+    if (!parsed_mappings.emplace(std::move(key), std::move(value)).second) {
       LOG(ERROR) << "Duplicate insert to " << flag.Name() << " not allowed";
       exit(1);
     }
   }
   return parsed_mappings;
 }
 
+CmdLineMapping AliasMap() {
+  CmdLineMapping alias_map;
+  CmdLineMapping orig_map = ParseCmdlineArgMap(FLAGS_command_alias);
path: src/server/command_registry.cc
user: login: abhijat
body: fixed
created_at: 2025-04-21T03:21:07Z
author_association: CONTRIBUTOR

PR comments:
user: login: abhijat
created_at: 2025-04-14T05:51:10Z
body: ~~This simple approach might not work as is, because each command goes into a family and here we just add new commands based on aliases after all families have been created.~~

Changed to register cloned command while the original is being registered. The clone gets its own bit index in the same family as the original. 
author_association: CONTRIBUTOR
user: login: abhijat
created_at: 2025-04-18T08:16:20Z
body: This current implementation of alias does not work across renamed commands, e.g. if a command is renamed and then alias is created for it then it would not match the original name, alias would have to be created against the rename, but after the discussion of scope about aliases I think this should be fine. 

If we do want to support rename + alias at some point in the future, we can do a lookup in the rename map when adding cloned alias commands.
author_association: CONTRIBUTOR
user: login: adiholden
created_at: 2025-04-20T09:12:01Z
body: with this changes we break replication for alias command if they are not configured in the replica the same way
I would add a comment on this in the flag name that replica needs to be configured the same way
author_association: CONTRIBUTOR
user: login: abhijat
created_at: 2025-04-21T03:23:38Z
body: > with this changes we break replication for alias command if they are not configured in the replica the same way I would add a comment on this in the flag name that replica needs to be configured the same way

Added in the flag comment
author_association: CONTRIBUTOR
user: login: adiholden
created_at: 2025-04-21T06:46:21Z
body: @abhijat please mark the test test_bug_in_json_memory_tracking in your PR. I am investigating this lsn issue and will supply a fix in another PR and will revert the skip once this is fixed
author_association: CONTRIBUTOR
user: login: abhijat
created_at: 2025-04-21T07:04:54Z
body: > test_bug_in_json_memory_tracking

marked as skipped
author_association: CONTRIBUTOR