From 45ed60f406b4dac76012fcacb137a6adc7ffa913 Mon Sep 17 00:00:00 2001 From: Carlos Andres Bolanos Date: Fri, 23 May 2025 15:39:09 +0200 Subject: [PATCH 1/3] Update CI --- .github/workflows/ci.yml | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e784adc4..3625f6de 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,10 +3,10 @@ name: CI on: push: branches: - - master + - v2 pull_request: branches: - - master + - v2 jobs: nebulex_test: @@ -18,7 +18,7 @@ jobs: strategy: matrix: include: - - elixir: 1.17.x + - elixir: 1.18.x otp: 27.x os: 'ubuntu-latest' style: true @@ -27,20 +27,14 @@ jobs: dialyzer: true doctor: true - elixir: 1.17.x - otp: 26.x - os: 'ubuntu-latest' - - elixir: 1.16.x - otp: 26.x + otp: 27.x os: 'ubuntu-latest' - - elixir: 1.15.x + - elixir: 1.17.x otp: 25.x os: 'ubuntu-latest' - elixir: 1.14.x otp: 24.x os: 'ubuntu-latest' - - elixir: 1.12.x - otp: 23.x - os: 'ubuntu-20.04' env: GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' From 181cbed05af2b78e8aba274a6a34c4cbbd3aa5be Mon Sep 17 00:00:00 2001 From: rezigned Date: Tue, 27 May 2025 17:18:36 +0700 Subject: [PATCH 2/3] [#234] Fix data loss in Replicated cache when multiple nodes join sequentially (#235) --- lib/nebulex/adapters/replicated.ex | 23 ++++++--------- test/nebulex/adapters/replicated_test.exs | 36 +++++++++++++++++++++++ 2 files changed, 45 insertions(+), 14 deletions(-) diff --git a/lib/nebulex/adapters/replicated.ex b/lib/nebulex/adapters/replicated.ex index f10b5c6b..6eaca164 100644 --- a/lib/nebulex/adapters/replicated.ex +++ b/lib/nebulex/adapters/replicated.ex @@ -755,20 +755,15 @@ defmodule Nebulex.Adapters.Replicated.Bootstrap do nodes -> # Sync process: - # 1. Push a new generation on all cluster nodes to make the newer one - # empty. - # 2. Copy cached data from one of the cluster nodes; entries will be - # streamed from the older generation since the newer one should be - # empty. - # 3. Push a new generation on the current/new node to make it a mirror - # of the other cluster nodes. - # 4. Reset GC timer for ell cluster nodes (making the generation timer - # gap among cluster nodes as small as possible). - with :ok <- maybe_run_on_nodes(adapter_meta, nodes, :new_generation), - :ok <- copy_entries_from_nodes(adapter_meta, nodes), - :ok <- maybe_run_on_nodes(adapter_meta, [node()], :new_generation) do - maybe_run_on_nodes(adapter_meta, nodes, :reset_generation_timer) - end + # + # 1. Copy cached data from existing cluster nodes to the joining node. + # At this point, both the newer and older generations are still present, + # so data may be copied from both, ensuring no data is lost due to premature + # generation rotation. + # 2. Reset the generation GC timer on all nodes to synchronize their GC intervals, + # minimizing timer gaps and ensuring consistent generation rotation across the cluster. + :ok = copy_entries_from_nodes(adapter_meta, nodes) + maybe_run_on_nodes(adapter_meta, cluster_nodes, :reset_generation_timer) end end diff --git a/test/nebulex/adapters/replicated_test.exs b/test/nebulex/adapters/replicated_test.exs index c0629c52..b14674ad 100644 --- a/test/nebulex/adapters/replicated_test.exs +++ b/test/nebulex/adapters/replicated_test.exs @@ -219,6 +219,42 @@ defmodule Nebulex.Adapters.ReplicatedTest do :ok = stop_caches(node_pid_list) end) end + + test "cache data is not lost when multiple nodes join sequentially" do + # Start with existing nodes + assert Replicated.put(:a, 1) == :ok + assert Replicated.get(:a) == 1 + + # Add node 3 + node3 = [:"node3@127.0.0.1"] + pids = start_caches(node3, [{Replicated, [name: @cache_name]}]) + + wait_until(fn -> + assert Replicated.nodes() |> :lists.usort() == :lists.usort(cluster_nodes() ++ node3) + end) + + # Verify :a is still there on all nodes + wait_until(10, 1000, fn -> + assert_for_all_replicas(Replicated, :get, [:a], 1) + end) + + # Add node 4 + node4 = [:"node4@127.0.0.1"] + pids = pids ++ start_caches(node4, [{Replicated, [name: @cache_name]}]) + + wait_until(fn -> + assert Replicated.nodes() |> :lists.usort() == + :lists.usort(cluster_nodes() ++ node3 ++ node4) + end) + + # Now check that data is still present + wait_until(10, 1000, fn -> + assert_for_all_replicas(Replicated, :get, [:a], 1) + end) + + # Clean up + :ok = stop_caches(pids) + end end describe "write-like operations locked" do From 472480d0403f7b56a2143418320274b1e28730ea Mon Sep 17 00:00:00 2001 From: Carlos Andres Bolanos Date: Sun, 1 Jun 2025 14:53:10 +0200 Subject: [PATCH 3/3] Release v.2.6.5 --- .tool-versions | 4 ++-- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.tool-versions b/.tool-versions index bcfbf192..161e2669 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -elixir 1.17.3-otp-27 -erlang 27.1.1 +elixir 1.18.4-otp-27 +erlang 27.3.3 diff --git a/CHANGELOG.md b/CHANGELOG.md index 390e0d62..2531875a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [v2.6.5](https://github.com/elixir-nebulex/nebulex/tree/v2.6.5) (2025-06-01) + +[Full Changelog](https://github.com/elixir-nebulex/nebulex/compare/v2.6.4...v2.6.5) + +**Closed issues:** + +- Replicated cache deletion behavior when the 3rd node joins the cluster. + [#234](https://github.com/elixir-nebulex/nebulex/issues/234) + ## [v2.6.4](https://github.com/cabol/nebulex/tree/v2.6.4) (2024-10-19) [Full Changelog](https://github.com/cabol/nebulex/compare/v2.6.3...v2.6.4) diff --git a/mix.exs b/mix.exs index 5cb3d51c..acb363d0 100644 --- a/mix.exs +++ b/mix.exs @@ -2,7 +2,7 @@ defmodule Nebulex.MixProject do use Mix.Project @source_url "https://github.com/cabol/nebulex" - @version "2.6.4" + @version "2.6.5" def project do [