Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,7 @@ LABEL org.opencontainers.image.title="ember" \
org.opencontainers.image.description="low-latency distributed cache" \
org.opencontainers.image.source="https://github.com/kacy/ember"

HEALTHCHECK --interval=10s --timeout=3s --start-period=5s --retries=3 \
CMD wget -qO- http://localhost:9100/health || exit 1

ENTRYPOINT ["ember-server"]
3 changes: 2 additions & 1 deletion crates/ember-cluster/src/topology.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,8 @@ impl std::fmt::Display for ClusterHealth {

impl ClusterState {
/// Creates a new cluster state for a single-node cluster.
pub fn single_node(local_node: ClusterNode) -> Self {
pub fn single_node(mut local_node: ClusterNode) -> Self {
local_node.config_epoch = 1;
let local_id = local_node.id;
let slot_map = SlotMap::single_node(local_id);
let mut nodes = HashMap::new();
Expand Down
32 changes: 22 additions & 10 deletions crates/ember-server/src/cluster.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,16 @@ impl ClusterCoordinator {
})?;
let gossip_addr = SocketAddr::new(bind_addr.ip(), gossip_port);

let gossip = GossipEngine::new(local_id, gossip_addr, gossip_config, event_tx);
let mut gossip = GossipEngine::new(local_id, gossip_addr, gossip_config, event_tx);

let state = if bootstrap {
let mut node = ClusterNode::new_primary_with_offset(local_id, bind_addr, port_offset);
node.set_myself();
ClusterState::single_node(node)
let cs = ClusterState::single_node(node);
// Populate local_slots so Welcome replies correctly advertise all owned slots
// instead of sending an empty list and triggering a stale SlotsChanged event.
gossip.set_local_slots(cs.slot_map.slots_for_node(local_id));
cs
} else {
let mut cs = ClusterState::new(local_id);
let mut node = ClusterNode::new_primary_with_offset(local_id, bind_addr, port_offset);
Expand Down Expand Up @@ -431,6 +435,16 @@ impl ClusterCoordinator {
}
};

// Insert placeholder before sending UDP so the gossip receive task always
// finds an entry during MemberJoined resolution. Without this, the Welcome
// reply can arrive and be processed before we add the placeholder, leaving
// both a real entry and a stale placeholder in state.nodes.
{
let mut state = self.state.write().await;
let node = ClusterNode::new_primary_with_offset(new_id, addr, self.gossip_port_offset);
state.add_node(node);
}

// send join message via UDP
{
let socket = self.udp_socket.lock().await;
Expand All @@ -444,14 +458,6 @@ impl ClusterCoordinator {
}
}

// add a placeholder to the routing table so MOVED redirects work
// immediately — the real node ID arrives via gossip and replaces this
{
let mut state = self.state.write().await;
let node = ClusterNode::new_primary_with_offset(new_id, addr, self.gossip_port_offset);
state.add_node(node);
}

self.save_config().await;

Frame::Simple("OK".into())
Expand Down Expand Up @@ -1617,6 +1623,11 @@ impl ClusterCoordinator {
false
}
GossipEvent::SlotsChanged(id, slots) => {
// The local node is authoritative for its own slot ownership;
// external gossip about it must never overwrite canonical state.
if id == coordinator.local_id {
false
} else {
debug!(
"cluster: node {} slots changed ({} ranges)",
id,
Expand All @@ -1640,6 +1651,7 @@ impl ClusterCoordinator {
}
state.update_health();
true
}
}
GossipEvent::RoleChanged(id, is_primary, replicates) => {
debug!(
Expand Down
6 changes: 3 additions & 3 deletions scripts/dev-cluster.sh
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,12 @@ cmd_start() {
"$CLI" -p "$p1" cluster meet 127.0.0.1 "$p3" >/dev/null

echo "assigning slots..."
"$CLI" -p "$p1" cluster addslotsrange 0 5460 >/dev/null
# node-1 already owns slots 0-5460 via bootstrap; nodes 2 and 3 take their ranges
"$CLI" -p "$p2" cluster addslotsrange 5461 10922 >/dev/null
"$CLI" -p "$p3" cluster addslotsrange 10923 16383 >/dev/null

# give gossip a moment to converge
sleep 0.5
# give gossip time to fully converge before printing status
sleep 1.5

echo ""
"$CLI" -p "$p1" cluster info
Expand Down
Loading